安装运行后检测绕过
adb安装,打开发现:
image-20240326145930896
楽,估计是检测到了什么。
猜基本都是什么frida、idaserver什么的,先进shell重命名一下:
image-20240326150345827
再开发现还是会,个人觉得是这个android_server64
(ida远程调试服务器)被检测到了,删除后发现能够正常运行,不过我自己把ida_server放在子文件夹下没有被抓到(
image-20240326150610784
算是先过了一开始的检测,能够运行了。
用jadx对包体进行分析
image-20240326150858235
发现是使用Unity3D
包名:com.com.sec2023.rocketmouse.mouse
入口点:com.unity3d.player.UnityPlayerActivity
不过此前自己是没有逆向过Unity制作的安卓游戏的,上来直接挑战这个,楽
解压看so
image-20240326151240788
Unity的游戏都有着libil2cpp.so,github上有个开源的项目Il2CppDumper 可以获取实现获取符号表
解压进入目录中,拿到libil2cpp.so
与global-metadata.dat
。 libil2cpp.so
: \lib\armeabi-v7a\libil2cpp.so
global-metadata.dat
: \assets\bin\Data\Managed\Metadata\global-metadata.dat
我去,着IL2CppDumper还有调用选择文件的API,不用自己手写文件路径,好评。
不过和复现的WP一样是失败了:
image-20240326152711549
跑010模板也对的上,应该是没有加密的:
image-20240326152920430
至于libil2cpp.so,反编译也是依托:
image-20240326153239591
符号表除了一些API之外,剩下的就是纯乱,但是运行的时候不可能会这样的,准备使用frida把它自解密后的数据dump下来
dump数据
在开启frida_server之后,又出现一样的heck detect的提示,盲猜是检测了frida端口,关掉进程之后又能运行,尝试换端口启动
./frs16 -l 0.0.0.0:27041
能跑起来来了
根据多个师傅的WP,dump内存的方法多种多样,复现就是为了学习,全部做一遍:
gg修改器 dump
起点:
image-20240326161127735
终点
image-20240326161044469
再用IL2CppDump看看:
好!
能从exe目录下得到
image-20240326205619243
但是dump下来的文件没有导入导出函数的符号
而 il2cpp 符号的脚本又是针对于 dump 文件的
所以修复一下dump的文件头之类的
.....修不懂。。
挖个坑,自己PE没看完呢
Fallw1nd师傅那个dump下来怪怪的,接着这边了
修复dump数据
首先是先将每个program_table的每一个p_offset改成p_vaddr
同时也将每一个p_filesz改成p_memsz(因为原先是进行了加固,现在这个是在内存中dump下来的,现在对应的段已经解压到内存的对应地址)
image-20240403235731355
然后注意最后一个表,这个表的结尾地址就是SECTION_HEADER的开头,所以SECTION_HEADER本来就应该是0x13BC000+63352 = 0x13CB778,所以填到文件的地址
image-20240404000021360
修改完按 program_table 按一下 F5 就会重新分析了,也可以看到一片空白的 section_header:
image-20240404000107492
直接将原版的复制到dump下来的(Ctrl+shift+C Ctrl+shift+V)
再 F5 刷新
刷新后,发现section header table乱码,和旁边的完全不同
image-20240409213702661
这部分的值是根据header的e_shtrndx的值,去寻找section header对应的块,再去寻找对应的符号:
image-20240409213901334
这里的值是26,指的是section_table_element[26]
ection_header_table->section_table_element[26]
中s_offset
的值决定了section的名称将从1199370h
去索引
image-20240409214010117
就是分析的data块,section的所有名称都在这个地方,把原来的复制过去就行,再F5刷新一下就行
image-20240409214135598
但是模板还是没分析出来,接下来就要修复section的偏移:
节(section) 的位置和大小由节头表(secion_header_table)中这两个成员决定
s_addr
如果此 section 需要映射到进程空间,此成员指定映射的起始地址;如不需映射,此值为 0
s_offset
此 section 相对于文件开头的字节偏移量.如果 section 类型为 SHT_NOBITS
,表明该 section 在文件中不占空间,这时 sh_offset 没什么用
修正 节(section) 的偏移有两条规则
如果s_addr为0,无需修改s_offset
如果s_addr不为0,则将s_addr的值复制给s_offset
修正完成后,按下F5
重新运行模板ELF.bt
,可以发现section的名称已经恢复,同时也有了dynamic_symbol_table
image-20240409214517506
然后就可以丢到ida分析了
Frida hook dlopen
看懂了Fallw1nd师傅的脚本。。至福
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 function WriteMemToFile (addr, size, file_path ) { Java .perform (function ( ) { var prefix = '/storage/emulated/0/dump/' var mkdir = Module .findExportByName ('libc.so' , 'mkdir' ); var chmod = Module .findExportByName ('libc.so' , 'chmod' ); var fopen = Module .findExportByName ('libc.so' , 'fopen' ); var fwrite = Module .findExportByName ('libc.so' , 'fwrite' ); var fclose = Module .findExportByName ('libc.so' , 'fclose' ); var call_mkdir = new NativeFunction (mkdir, 'int' , ['pointer' , 'int' ]); var call_chmod = new NativeFunction (chmod, 'int' , ['pointer' , 'int' ]); var call_fopen = new NativeFunction (fopen, 'pointer' , ['pointer' , 'pointer' ]); var call_fwrite = new NativeFunction (fwrite, 'int' , ['pointer' , 'int' , 'int' , 'pointer' ]); var call_fclose = new NativeFunction (fclose, 'int' , ['pointer' ]); call_mkdir (Memory .allocUtf8String (prefix), 0x1FF ); call_chmod (Memory .allocUtf8String (prefix), 0x1FF ); var fp = call_fopen ( Memory .allocUtf8String (prefix + file_path), Memory .allocUtf8String ('wb' )); if (call_fwrite (addr, 1 , size, fp)) { console .log ('[+] Write file success, file path: ' + prefix + file_path); } else { console .log ('[x] Write file failed' ); } call_fclose (fp); }); } function HookLibWithCallback (name, callback ) { var dlopen = Module .findExportByName ('libdl.so' , 'dlopen' ); var detach_listener = Interceptor .attach (dlopen, { onEnter : function (args ) { var cur = args[0 ].readCString (); console .log ('[+] dlopen called, name: ' + cur); if (cur.indexOf (name) != -1 ) { this .hook = true ; } }, onLeave : function ( ) { if (this .hook ) { console .log ('[+] Hook Lib success, name:' , name); callback (); detach_listener.detach (); } } }); } function LogModule (module ) { console .log ('Module name: ' + module .name ); console .log ('Module base: ' + module .base ); console .log ('Module size: ' + module .size ); } function TraverseModules (mode, {name = '' , name_array = []} ) { if (mode == 'all' ) { var modules = Process .enumerateModules (); for (var i = 0 ; i < modules.length ; i++) { var module = modules[i]; } return modules; } else if (mode == 'single' ) { var module = Process .getModuleByName (name); LogModule (module ); return module ; } else if (mode == 'multiple' ) { var modules = Process .enumerateModules (); var target_modules = []; for (var i = 0 ; i < modules.length ; i++) { var module = modules[i]; if (name_array.indexOf (module .name ) != -1 ) { LogModule (module ); target_modules.push (module ); } } return target_modules; } } function DumpIL2CPP ( ) { var libil2cpp = TraverseModules ('single' , {name : 'libil2cpp.so' }); WriteMemToFile (libil2cpp.base , libil2cpp.size , 'libil2cpp.so' ); } function main ( ) { HookLibWithCallback ('libil2cpp.so' , DumpIL2CPP ); } main ();
根据自己的情况把dump的地址改了。
怪哦,这个好像dump的不多。
看到个WP脚本,牛了:
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 let dump_so_name = "libil2cpp.so" function dumpso ( ){ try { let modu=Process .getModuleByName (dump_so_name) var base_addr = modu.base var base_size = modu.size if (modu){ console .log ("name: " +modu.name ); console .log ("base: " +base_addr); console .log ("size: " +"0x" +base_size.toString (16 )); var file_path = "/data/data/com.com.sec2023.rocketmouse.mouse/" + modu.name + "_" + modu.base + ".so" ; var all_size = 0 var range_arry = Process .enumerateRanges ("r" ) var file_handle = new File (file_path, "wb" ); range_arry.forEach (range => { if ( (parseInt (range.base ,16 ) >= parseInt (base_addr)) && (parseInt (range.base ,16 ) <= parseInt (base_addr) + base_size )){ all_size += range.size var libso_buffer = ptr (range.base ).readByteArray (range.size ); file_handle.write (libso_buffer); file_handle.flush (); console .log (`[+] ${range.base} -${"0x" + range.size.toString(16 )} -${range.protection} ` ) } }) file_handle.close (); console .log ("[dump_size]:" , "0x" + all_size.toString (16 )); console .log ("[dump]:" , file_path); }else { console .log ('[x] ' + dump_so_name +'not found' ); } }catch (e){ console .log ("dump so error \t" + e) } }function hook_dlopen ( ) { var dlopen = Module .findExportByName (null , "dlopen" ); Interceptor .attach (dlopen, { onEnter : function (args ) { this .call_hook = false ; var so_name = ptr (args[0 ]).readCString (); if (so_name.indexOf (dump_so_name) >= 0 ) { console .log ("dlopen:" , ptr (args[0 ]).readCString ()); this .call_hook = true ; } }, onLeave : function (retval ) { if (this .call_hook ) { dumpso (); } } }); }hook_dlopen ();
dump下来,然后把数据 patch到被加密的 .so文件中。
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 import idc begin = 0x0000000002B6850 end = 0x0000000013CB778 data = idc.get_bytes(begin,end-begin) fp = open (r"F:/CTF/TXgame/2023腾讯游戏安全技术竞赛-安卓客户端安全-初赛题目/getdata" ,"wb" ) fp.write(data) fp.close()print ("finish" )import idcimport ida_bytes begin = 0x0000000002B6850 end = 0x0000000013CB778 fp = open (r"F:/CTF/TXgame/2023腾讯游戏安全技术竞赛-安卓客户端安全-初赛题目/getdata" ,"rb" ) data = fp.read()import idc import idaapidef upc (begin,end ): for i in range (begin,end): idc.del_items(i) for i in range (begin,end): idc.create_insn(i) for i in range (begin,end): idaapi.add_func(i) print ("Finish!!!" ) begin = 0x0000000002B6850 end = 0x000000000C4E42C upc(begin,end)
Zygisk-Il2CppDumper
去年打的是UE4,复现就搁置了。今年按照规律可能是Unity,复现复现。
改用Zygisk-Il2CppDumper了,看起来dump的还顺利:
好用, 就是每次都要编译一个(
image-20250326171242570
ida分析dump的libil2cpp
用新东西修完之后,使用Il2CppDumper
可以直接dump出相关文件
1 Il2CppDumper.exe .\libil2cpp.so .\global -metadata.dat <output-directory>
把ida_with_struct_py3
,il2cpp.h
script.json
全部导入ida中,即可恢复libil2cpp.so
的大部分符号
getFlag
查看dump.cs 可以看到相关符号地址:
不难发现:
1 2 private Void CollectCoin(Collider2D coinCollider ) { }
在ida里面查看附近:
image-20250327161329160
找Coin相关的:
image-20250327161502228
存在比较,hook修改即可:
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 function inline_hook ( ) { var soAddr = Module .findBaseAddress ("libil2cpp.so" ); var target = soAddr.add (0x4653cc ); Memory .patchCode (target,4 ,function (code ){ var writer = new Arm64Writer (code, {pc :target}); var res = hexToBytes ("1F000071" ); console .log (res); writer.putBytes (res); writer.flush (); console .log (hexdump (target, { offset : 0 , length : 64 , header : true , ansi : true , })); }) }function hexToBytes (str ) { var pos = 0 ; var len = str.length ; if (len % 2 != 0 ) { return null ; } len /= 2 ; var hexA = new Array (); for (var i = 0 ; i < len; i++) { var s = str.substr (pos, 2 ); var v = parseInt (s, 16 ); hexA.push (v); pos += 2 ; } return hexA; }inline_hook ()
得flag
image-20250327161623705
分析注册机
根据命名寻找一下,发现smallkeyboard类
image-20250327161818373