输入一个(可能为任意长度)的key,使app返回correct。本题需要提交DDCTF-{key}。出现多解的情况请把符合[0-9a-z]+格式的key提交。链接: https://pan.baidu.com/s/1T7fo0q0tYlsANQCcqpd4HQ 密码: j8gs

初看题目本以为跟RSA解密有关,但整体做下来关联性并不大,只是在最后用到一个知识点而已。

静态分析

工具:使用JEB 打开apk,对java层的逻辑进行分析,如下图:

jeb

在上图Libraries目录下,需要把libhello-libs.so库文件提取出来,拖入IDA进行分析,并且在Exports中找到关键函数,如下图:

双击进入分析函数,然后按F5,得到代码如下:

双击进入上图的关键函数,进行静态分析,只能得到输入的字符串长度等于32,后面的循环相对复杂,可能是题目故意弄的复杂,不过大概可以知道是将输入的数据和程序中的数据进行了异或,然后程序进行了什么操作就需要动态调试进行分析,如下图分析:

动态分析

在进行动态调试之前,需要做一些准备工作,由于JEB没有修改apk的功能,所以如果是windows系统,建议用androidkill这个软件打开apk,如果是mac系统,建议用Android Crack Tool打开apk,目的是在AndriodManifest.xml文件中加上debugger条件,然后进行重新编译,签名,之后等到的apk放到手机或模拟器上,然后用IDA打开这个新的apk的libhello-libs.so文件。

注:由于这个apk是arm架构的,导致我试过很多模拟器都没成功,各种错误,最后用的root权限的真机,系统版本Android5.0以上,所以建议不要用模拟器;

先学习下IDA动态调试so文件的步骤:

  1. 在IDA 6.X/7.0 中找到 android_server,一般在IDA安装目录下的dbgsrv文件夹里 ,push到手机内。

    1
    2
    adb devices #查看是否连接设备
    adb push android_server /data/local/tmp/ #把android_server出入手机/data/local/tmp/目录下
  2. 启动 Android 调试服务器 adb shell

  3. 转发IDA的调试通讯端口

    1
    adb forward tcp:23946 tcp:23946
  4. 调试模式启动 adb am start -D -n packagename/classname,等待调试器,成功之后手机显示等待调试提示

    1
    adb shell am start -D -n com.didictf.guesskey2018one/com.didictf.guesskey2018one.MainActivity
  5. 启动 monitor,一般在Android SDK安装目录下的tool文件夹里,查看是否挂起进程,并处于待调试状态;

  6. 启动 jdb,port是调试程序的jdwp号,可以不写8700,monitor中的显示

    1
    jdb -connect com.sun.jdi.SocketAttach:hostname=localhost,port=8700
  7. 接着设置IDA,之前已经用IDA打开这个新的apk的libhello-libs.so文件,选择Remote ARM Linux/Android debugger,点击菜单项Debugger中的Process option里设置这个,如下图:

  8. 菜单项Debugger中的Debugger option里设置如图:

  9. 菜单项Debugger中attach to process前,先下断点,断点设置在静态分析中关键函数的开始;

  10. 然后进行attach to process

  11. 最后运行F9,会提示加载的与attach process中的so文件是不是一直,选择same,就可以了,之后程序停在了库的加载点(之前debuggr options中有选),然后再F9运行,发现手机中的app等待我们输入字符串,随便输入32位字符串(我在这里输入的是32个1),点击test发现程序停在了第9步设定的断点,然后就可以一步步跟踪调试了,如图: (利用IDA动态调试so文件的整体的环境配置就到此结束。)

开始调试程序

在静态分析中已经预测有判断输入字符串长度的地方,所以我们单步执行F7,停在判断长度的地方:

因为我们输入的是32个字符,所以在继续执行的时候跳转到绿色线路上,如果输入的不是32位但也不想直接退出,则需要在判断完成之后把符号寄存器z位由0改成1,接下来运行到寻找之前的异或运算,观察R1和R2寄存器中的内容,如图:(此次异或运算是把我们输入的和程序中固定的数据逐位循环进行运算)

异或完之后来到一个很复杂的循环里,动态分析发现其效果就是判断arr是否等于arr[i+10],如果不等于就跳转退出,i从0到22,而arr就是上一步异或完后得到的字符串,断点设置如下图,需要跟踪就不难发现了:

如果不嫌麻烦可以每次手动改寄存器的值完成跳转,也可以直接在循环结束后的代码,右键设置Set IP,让程序跳过循环继续往下分析;

接下来是两个字符串加上两个复制的循环,直接跟有些复杂,其中有个函数j_j__ZNSt3mapIciSt4lessIcESaISt4pairIKciEEEixERS3_的作用是转为数字字符串,在整个循环结束后发现函数j_atoll(v27),直接在这里下断点拦截数据,获得核心数据,如图:

之后就是判断条件了,如下图,经过分析只有当10个字符转化的数字是上述核心数据的因数,且是较小的那个是,才能正确返回,否则错误;

最后就是唯一跟RSA有关的就是分解大数了,可以在http://factordb.com上进行分解得:
$$
1499419583 * 3927794789 = 5889412424631952987
$$
解密脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
unk_4DEF3=[0x52,0x5E,0x54,0x4C,0x52,0x57,0x50,0x58,0x0C,0x4A,0x43,0x50,0x0A,0x5C,0x44,0x03,0x53,0x40,0x5F,0x06,0x40,0x06,0x52,0x08,0x58,0x53,0x5D,0x06,0x01,0x55,0x5C,0x57]

#key=1499419583
key=[0x31,0x34,0x39,0x39,0x34,0x31,0x39,0x35,0x38,0x33]
j=0
str=''
for i in range(len(unk_4DEF3)):
unk_4DEF3[i]^=key[j]
j=(j+1)%10
str+=chr(unk_4DEF3[i])
print unk_4DEF3[i]

print str

总结:离大佬们的差距还是很大的,慢慢积累吧,一开始调试的时候遇到了很多问题,但最终还是坚持下来了,收获良多,感谢自己。