概述
题目入口:http://ctf.pediy.com/game-fight-36.htm
本题是安卓cm,目测肯定需要调试so。
准备工具:
- ApkIde改之理(其他类似的也行,能够反编译apk,得到jar,so等)
- IDA(用于调试so),需要6.x以上,忘了是x几,我用的6.6
- adb(ApkIde改之理就有)
反编译
将6-Ericky kanxue.apk拖进ApkIDE改之理,等待编译(没有加壳),ok。
在右侧树结构栏中,找到smali->android->com->miss->rfchen,列表中就是java层的主要函数。
点击MainActivity.smali,然后点击工具栏中jd-gui.exe,抓到java源码查看。
1 |
|
这混淆的函数名我也是醉了,但这都不重要。输入key之后,然后点击按钮,进入OnClick,调用了上面代码中第二个函数(什么?我怎么知道的,因为它们哪个…点号…的函数名相同!!)。
然后调用了utils.check来验证,成功提示!这里成功和错误提示的字符串做过变换,通过utils.dbcb解密,不细看了,不重要!
进入utils.java,看到加载了so,调用的是这个so的导出函数,看反编译目录lib/armeabi-v7a(只提供了arm的so,要有个x86的好了),知道这个so是librf-chen.so。
1 | //典型的NDK调用,查查就知道了! |
那么重点来了,要分析librf-chen.so的check函数,才能搞定此题。
准备调试
早上提前学习了一下so调试方法,找到了看雪安卓大神的教程,就是参考中的IDA动态调试技术,然后用上了,很好用!
跟着走
下面开始照着做。
- 连上手机(或者模拟器),使用adb devices看看成功连上没有
- adb push ../dbgsrv/android_server /sdcard/sv,教程是直接放入/data/data,一般权限不够
- 然后进入shell,adb shell,输入su,获得root权限,然后cp /sdcard/sv /data/data/sv
- 修改sv权限,chmod 777 /data/data/sv
- 运行sv,/data/data/sv,默认监听到23946端口,Listening on port #23946。这步有个细节,不能直接adb shell /data/data/sv,这样权限不够,无法读取到进程信息,需要adb shell; su; /data/data/sv
- 再开一个cmd,然后运行adb forward tcp:23946 tcp:23946
- 运行一个idaq.exe,然后在菜单debugger->attach->remote Armlinux/android debugger,输入localhost, 23946,ok
- 弹出进程框,按下Alt+T,输入chen,搜索到1808 [32] com.miss.rfchen,ok
- F9运行
1 | \ApkIDEz> .\adb.exe shell |
在界面中输入key,然后点击按钮,此时librf-chen.so才加载,然后ctrl+s,alt+t,输入librf找到librf-chen.so的基地址信息(记为base),记下来。
用另一个ida打开librf-chen.so,找到check导出函数的偏移地址00002814,计算base+00002814,然后g在IDA调试器中输入该地址,加上断点。
1 | check(_JNIEnv *,_jclass *,_jstring *) 00002814 |
IDA基本调试快捷键和OD一样:
1 | F9: 运行 |
F9,跑起来,然后再次点击按钮,就断下来,进入了check。
下面就是跟和调试的过程了,看数据,看流程,分析算法!
arm汇编基础
得提前有个准备,看看arm指令,了解基本的指令,函数调用方式,下面列几个,更多的就看参考中的文章了
1 | MOVS 同x86的mov |
然后最主要的,函数调用的参数传递。arm默认使用的fastcall,通过r0,r1,r2,r3传递参数,超过4个参数,使用堆栈传递,r0也保存返回值。
关键点跟踪
在check断下之后,先是一段数据初始化,先滤过,然后blt sub_2874,进入关键函数
然后看到通过MOVS,STR将一些字符放入了内存。
1 | .text:0000288A 000 01 60 STR R1, [R0] |
接着就看让我恐惧的一幕,b loc_2898开始各种跳转,指令操作,然后刚跳完又是一个b xxx,接着各种跳转,毫无疑问,这是一段花指令了。
1 | .text:0000289E B loc_2898 |
花指令结构
经过多次跟踪,恶心到快吐的时候,终于看出话指令的基本结构了:
1 | .text:00002BE8 PUSH.W {R4-R10,LR} |
特征:
- 每跳转一个分支,基本都要一段花(记为A段),就是从上面代码中注释开始的问题
- 进行几个跳转后,到了结束位置,跳入有效代码
- 有效代码开头一般也有加一段花(记为B段)
- 在A段话指令中,指令地址是向下增长的,也就是A开始往下拉一段,就能找到结束位置
- B端一般无跳转,但是对称代码有多又少
所以根据特征,去除话指令也挺方便,我使用的IDA的patch功能手工去花的,脚本牛可以写个脚本。
所有花指令填充的00 bf(NOP),然后就可以F5了。
关键点跟踪2
然后接着调试跟踪。
接着上面,后续会接着向该段内存填充字符(非直接填充,还有个段算法,根据初始话的0x20的值来做的),我没有仔细跟踪算法了,通过对些内存关键点下断,然后跳出循环位置下断,下面0000357A就是循环位置,如此多次之后,循环结束。
1 | .text:00003576 000 B4 F1 FF 3F CMP.W R4, #0xFFFFFFFF |
查看该内存数据:
1 | 5F019020 4A 00 79 00 75 00 33 00 43 00 4A 00 6C 00 56 00 J.y.u.3.C.J.l.V. |
接着跳过一段花之后,调用了bl sub_19FC,跟入,发现结果和刚才那段基本一直,也是将字符写入内存,并且内存就是刚才那段,只是每次都有一个1偏移。
1 | .text:0000364A 000 FE F7 D7 F9 BL sub_19FC |
同样,结束之后,查看内存,通过后面分析,知道这段字符就是key加密变换之后要对比的字符串。
1 | 5ED12020 4A 50 79 6A 75 70 33 65 43 79 4A 6A 6C 6B 56 36 JPyjup3eCyJjlkV6 |
子过程返回之后,接着b进入另一段。调了这么久,我们输入的key去哪里了?下面来了!
1 | text:00003680 000 D9 F8 00 00 LDR.W R0, [R9] 之前传入的参_JNIEnv |
先来看看check接口:
1 | check(_JNIEnv *,_jclass *,_jstring *) 00002814 |
check参数在刚进入就被保存了,现在在00003680位置取出来,返回了我们输入的key到R0中(看注释)。
1 | 5DC4BEC0 31 32 33 34 35 36 00 40 10 00 00 00 4B 00 00 00 123456.@....K... |
然后,又调用了一个子过程来处理key,我这里先没有跟入,直解F8,看了返回值
1 | .text:00003792 000 16 F0 09 FB BL sub_19DA8 |
1 | 65 4B 2F 30 36 38 71 52 00 00 00 00 C0 BE C4 5D eK/068qR |
基本确认是加密函数,然后又把该结果和JPyjup3eCyJjlkV6DmSmGHQ=!!进行对比。
1 | .text:000038CE 000 78 44 ADD R0, PC ; 保存了JPyjup3eCyJjlkV6DmSmGHQ=!! |
取出一个字符进行比较,不同则跳转,相同R4加1,继续比价直到超过0x18(也就是加密结果长度0x18),都相同了R0=1
看看不同时跳转的代码,sub_27C8是一个类似鱼strstr的代码,我本以为加密之后结果可以部分匹配也行,结果我错了,作者坑人,因为这个sub_27C8就算返回1,也就是部分匹配成功了,也会进入00003C26,R0=0。
1 | .text:00003A1A 000 78 44 ADD R0, PC ; result |
所以加密结果必须是0x18,和JPyjup3eCyJjlkV6DmSmGHQ=!!完全匹配(0x18字节)
算法
现在重新跟入加密子过程sub_19DA8,看看是怎么个算法。
1 | .text:00019DA8 sub_19DA8 ; CODE XREF: sub_2874+F1E |
先通过sub_1A31C子函数返回了一串字符199319124851!,算法和生成JPyjup3eCyJjlkV6DmSmGHQ=!!字符类似,不再细说。
1 | .text:00019F80 428 20 46 MOV R0, R4 ; size |
然后分配了一段内存,用于保存第一次加密的key结果。
调用sub_55E4,将199310124851!通过变换放入一个8字节+0x100*4的数组(初始化为0-0x100)空间,挺绕的,由于这个函数跟key没有多大关系,所以咩必要细究是怎么做的,可以直接将计算后内存dump出来用后面的逆运算(其实我没用上)。
1 | .text:0001A13A 428 EA F7 A0 FA BL sub_467E;第一次加密变换 |
然后sub_467E进行第一次加密变换,将key和前面的8字节+0x100*4的数组组队的xor,细节直接看代码(完整的我会放idb):
1 | v4 = p->unk_0; |
这里我没有暂时没有渗入理解,直接进入第二次加密运算。
1 | .text:0001A222 428 01 44 ADD R1, R0 ;长度 |
进入sub_5AFC,将key每3个字节一组,进行<<8
拼接,也就是a1<<16+a2<<8+a3
,举个例子0xaa,0xbb,0xcc=>0xaabbcc
然后拼接结果v15再左移,
如果是3个字符拼接的,这里v16是3,v19=v15 << 8 * (3 - v16)
也就左移0,也就是不左移;
如果是两个字符或者一个字符拼接的,这里就需要左移8或者16位,说白了就是需要构成0x112233的结构。
然后v19进行4次移位,取aAbcdefghijklmn字符放入结果内存中。其实就是v19按6位进行分割(分别右移0x12,0xc,0x6,0x0,&03f),分割的值作为index,去aAbcdefghijklmn中对应字符,保存。
如果v16<3
,也就是此次拼接没有3个字符,这里index=0x40
,也就是增加额外的”=”用于结果。
1 | if ( _R10 > 0 ) // len>0 |
逆向算法
算法大致明白了,结果又是JPyjup3eCyJjlkV6DmSmGHQ=(取了0x18字节)。那么将第二次加密进行求逆。
先找JPyjup3eCyJjlkV6DmSmGHQ=每字节在’ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=’中的index。
1 | k = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' |
结果是:
1 | 1: J 9 9 |
然后每4个index一组,来自于v19的4次右移,那么反过来4个一组,左移相加就是v19
1 | for i in range(0, len(idd), 4): |
得到结果:
1 | 0: 24fca3 |
然后我们又知道v19其实是v15拼接的,所以拆开就得到v15(第一次加密结果),可以看到key长度应该是17。
1 | 24 fc a3 ba 9d de 0b 22 63 96 45 7a 0e 64 a6 18 74 |
然后接着求第一次加密的逆运算,看代码,好多啊,怎么办,难道要求逆,好难!
好吧,不装了,其实不难,我们看前面说的第一次加密其实就是分组xor!
xor好啊,xor好啊…我们知道xor两次会将结果还原,想到了什么?!
是的,既然我们拿到第一次加密结果,那让他再和哪个8字节+0x100*4的数组再xor一次不久可以了,但是要重写这个加密代码貌似也挺麻烦的,怎么办?!
这里我是这么做的,在调试中,第一次加密前,将key的值(本来是输入)修改为上面得到的第一次加密结果,然后开始第一次加密运算,这样不就完美的完成了一次求逆吗,哈哈!
具体操作,对1A13A下断,输入key(必须是17位,否则修改内存时可能会挂),确认,断下来,此时r2就是key
1 | 5E127B20 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36 1234567890123456 |
然后在hex窗口,f2修改内存,输入上面的24 fc…,然后f2确认修改。
1 | 5E127B20 24 FC A3 BA 9D DE 0B 22 63 96 45 7A 0E 64 A6 18 $. |
然后f8。看看结果:
1 | 5E127B38 6D 61 64 65 62 79 65 72 69 63 6B 79 39 34 35 32 madebyericky9452 |
答案就是:madebyericky94528
转载请注明出处:https://anhkgg.github.io/kxctf2017-writeup6
参考:
- 安卓APP动态调试技术–以IDA为例
- http://luleimi.blog.163.com/blog/static/175219645201210922139272/
- http://blog.csdn.net/zhangmiaoping23/article/details/43445797
- http://www.cnblogs.com/liujiahi/archive/2011/03/22/2196401.html
- http://cncc.bingj.com/cache.aspx?q=arm++IT+EQ&d=4981012666125942&mkt=zh-CN&setlang=zh-CN&w=YEX3ioizXLDZGmlpVDBGFh_dhhHpfnYj