本次记录一下本菜鸡小小的分析这个送给最好的TA
我是直接暴力把 apk 后缀改成 zip 后打开的,没想到大佬们可以发现这个神奇的网站进行拆包,学到了,但是要关闭屏蔽广告的插件,毕竟不能靠爱发电
然后我就开始研究 classes.dex 源码了,先把 dex 转成 jar ,然后用jd-gui一顿 xjb 分析,大致看了一下有一个获取路径
1 2 3 4 5 6 7 8
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| 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
1 2 3
| 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 ,我们断定这只能是原字节,然后剩下的就能写出脚本了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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
1
| java -jar unluac_2015_06_13.jar main.lua > main_real.lua
|
终于完事了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| 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 分析实在太难了~~