首页
社区
课程
招聘
[原创]看雪CTF2017第六题 Ericky-apk详细writeup(从一个安卓新手的角度)
发表于: 2017-6-13 15:40 7421

[原创]看雪CTF2017第六题 Ericky-apk详细writeup(从一个安卓新手的角度)

2017-6-13 15:40
7421

本题是安卓cm,目测肯定需要调试so。

准备工具:

将6-Ericky kanxue.apk拖进ApkIDE改之理,等待编译(没有加壳),ok。

在右侧树结构栏中,找到smali->android->com->miss->rfchen,列表中就是java层的主要函数。

点击MainActivity.smali,然后点击工具栏中jd-gui.exe,抓到java源码查看。

这混淆的函数名我也是醉了,但这都不重要。输入key之后,然后点击按钮,进入OnClick,调用了上面代码中第二个函数(什么?我怎么知道的,因为它们哪个...点号...的函数名相同!!)。

然后调用了utils.check来验证,成功提示!这里成功和错误提示的字符串做过变换,通过utils.dbcb解密,不细看了,不重要!

进入utils.java,看到加载了so,调用的是这个so的导出函数,看反编译目录lib/armeabi-v7a(只提供了arm的so,要有个x86的好了),知道这个so是librf-chen.so

那么重点来了,要分析librf-chen.so的check函数,才能搞定此题。

早上提前学习了一下so调试方法,找到了看雪安卓大神的教程,就是参考中的IDA动态调试技术,然后用上了,很好用!

下面开始照着做。

在界面中输入key,然后点击按钮,此时librf-chen.so才加载,然后ctrl+s,alt+t,输入librf找到librf-chen.so的基地址信息(记为base),记下来。

用另一个ida打开librf-chen.so,找到check导出函数的偏移地址00002814,计算base+00002814,然后g在IDA调试器中输入该地址,加上断点。

IDA基本调试快捷键和OD一样:

F9,跑起来,然后再次点击按钮,就断下来,进入了check。

下面就是跟和调试的过程了,看数据,看流程,分析算法!

得提前有个准备,看看arm指令,了解基本的指令,函数调用方式,下面列几个,更多的就看参考中的文章了

然后最主要的,函数调用的参数传递。arm默认使用的fastcall,通过r0,r1,r2,r3传递参数,超过4个参数,使用堆栈传递,r0也保存返回值。

在check断下之后,先是一段数据初始化,先滤过,然后blt sub_2874,进入关键函数

然后看到通过MOVS,STR将一些字符放入了内存。

接着就看让我恐惧的一幕,b loc_2898开始各种跳转,指令操作,然后刚跳完又是一个b xxx,接着各种跳转,毫无疑问,这是一段花指令了。

经过多次跟踪,恶心到快吐的时候,终于看出话指令的基本结构了:

特征:

所以根据特征,去除话指令也挺方便,我使用的IDA的patch功能手工去花的,脚本牛可以写个脚本。

所有花指令填充的00 bf(NOP),然后就可以F5了。

然后接着调试跟踪。

接着上面,后续会接着向该段内存填充字符(非直接填充,还有个段算法,根据初始话的0x20的值来做的),我没有仔细跟踪算法了,通过对些内存关键点下断,然后跳出循环位置下断,下面0000357A就是循环位置,如此多次之后,循环结束。

查看该内存数据:

接着跳过一段花之后,调用了bl sub_19FC,跟入,发现结果和刚才那段基本一直,也是将字符写入内存,并且内存就是刚才那段,只是每次都有一个1偏移。

同样,结束之后,查看内存,通过后面分析,知道这段字符就是key加密变换之后要对比的字符串。

子过程返回之后,接着b进入另一段。调了这么久,我们输入的key去哪里了?下面来了!

先来看看check接口:

check参数在刚进入就被保存了,现在在00003680位置取出来,返回了我们输入的key到R0中(看注释)。

然后,又调用了一个子过程来处理key,我这里先没有跟入,直解F8,看了返回值

基本确认是加密函数,然后又把该结果和JPyjup3eCyJjlkV6DmSmGHQ=!!进行对比。

取出一个字符进行比较,不同则跳转,相同R4加1,继续比价直到超过0x18(也就是加密结果长度0x18),都相同了R0=1

看看不同时跳转的代码,sub_27C8是一个类似鱼strstr的代码,我本以为加密之后结果可以部分匹配也行,结果我错了,作者坑人,因为这个sub_27C8就算返回1,也就是部分匹配成功了,也会进入00003C26,R0=0。

所以加密结果必须是0x18,和JPyjup3eCyJjlkV6DmSmGHQ=!!完全匹配(0x18字节)

现在重新跟入加密子过程sub_19DA8,看看是怎么个算法。

先通过sub_1A31C子函数返回了一串字符199319124851!,算法和生成JPyjup3eCyJjlkV6DmSmGHQ=!!字符类似,不再细说。

然后分配了一段内存,用于保存第一次加密的key结果。 调用sub_55E4,将199310124851!通过变换放入一个8字节+0x100*4的数组(初始化为0-0x100)空间,挺绕的,由于这个函数跟key没有多大关系,所以咩必要细究是怎么做的,可以直接将计算后内存dump出来用后面的逆运算(其实我没用上)。

然后sub_467E进行第一次加密变换,将key和前面的8字节+0x100*4的数组组队的xor,细节直接看代码(完整的我会放idb):

这里我没有暂时没有渗入理解,直接进入第二次加密运算。

进入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,也就是增加额外的"="用于结果。

算法大致明白了,结果又是JPyjup3eCyJjlkV6DmSmGHQ=(取了0x18字节)。那么将第二次加密进行求逆。 先找JPyjup3eCyJjlkV6DmSmGHQ=每字节在'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='中的index。

结果是:

然后每4个index一组,来自于v19的4次右移,那么反过来4个一组,左移相加就是v19

得到结果:

然后我们又知道v19其实是v15拼接的,所以拆开就得到v15(第一次加密结果),可以看到key长度应该是17。

然后接着求第一次加密的逆运算,看代码,好多啊,怎么办,难道要求逆,好难! 好吧,不装了,其实不难,我们看前面说的第一次加密其实就是分组xor! xor好啊,xor好啊...我们知道xor两次会将结果还原,想到了什么?! 是的,既然我们拿到第一次加密结果,那让他再和哪个8字节+0x100*4的数组再xor一次不久可以了,但是要重写这个加密代码貌似也挺麻烦的,怎么办?!

这里我是这么做的,在调试中,第一次加密前,将key的值(本来是输入)修改为上面得到的第一次加密结果,然后开始第一次加密运算,这样不就完美的完成了一次求逆吗,哈哈!

具体操作,对1A13A下断,输入key(必须是17位,否则修改内存时可能会挂),确认,断下来,此时r2就是key

然后在hex窗口,f2修改内存,输入上面的24 fc...,然后f2确认修改。

然后f8。看看结果:

答案就是:madebyericky94528

只能写下此篇wp,希望对大家有帮助

参考:



[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

上传的附件:
收藏
免费 1
支持
分享
最新回复 (5)
雪    币: 7016
活跃值: (4227)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2017-6-13 18:45
0
雪    币: 598
活跃值: (282)
能力值: ( LV13,RANK:330 )
在线值:
发帖
回帖
粉丝
3
2017-6-14 15:35
0
雪    币: 1185
活跃值: (458)
能力值: ( LV13,RANK:360 )
在线值:
发帖
回帖
粉丝
4
学习了求逆  强大!
2017-6-14 17:50
0
雪    币: 2
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
学习了
2017-6-15 09:50
0
雪    币: 10072
活跃值: (3008)
能力值: ( LV15,RANK:515 )
在线值:
发帖
回帖
粉丝
6
Ericky 学习了求逆 强大!
大佬取笑了,我连算法是什么都没看出来...
2017-6-15 12:43
0
游客
登录 | 注册 方可回帖
返回
//