-
-
[原创]移动安全挑战赛解题思路提交
-
发表于: 2015-1-28 17:13 5923
-
by zTrix
第一题
第一题可以直接用 apktool d AliCrackme_1.apk 命令解包。dex2jar 也没问题,使用 jd-gui 打开 classes_dex2jar.jar 之后,发现有两个比较重要的函数却反编译不出来,是 getTableFromPic 和getPwdFromPic。
于是看了一下 smali 汇编,发现是从图片中两个 offset 提取的,直接读出来即可。
后面的 bytesToAliSmsCode 函数就比较简单了,直接使用 python 写出对应代码
直接运行就得到答案 581026
第二题
第二题比较复杂一点,解包之后,分析逻辑
可以发现,检查输入的逻辑在 so 里面:
使用 IDA 打开这个 so,仔细分析这个函数,可以看到这个函数最后和一个原来内容为 wojiushidaan 的字符串做了比较,但是 wojiushidaan 是骗人的,它不是最终答案!于是猜测在 JNI_Load 里面,这个字符串内容被做了修改。
考虑到静态分析比较复杂,这里可以直接修改 so 指令,把运行时的实际字符串内容打印出来即可。使用 __android_log_print 函数即可。
修改之后的指令如下:
修改之后的 diff
再运行,盯着 adb logcat 看,就能找到答案 aiyou,bucuoo
第三题
第三题又要复杂不少,简单的分析可以知道,最外面的 apk 和dex 都只是一个壳,真正的内容在 so 加载之后才会出来。
于是希望真正的 dex 在内存中是完整的。开启 gdb 直连,结果发现不能 attach,应该是开了反调试。尝试 ls /proc/<pid>/task 之后去 attach 线程,结果程序直接 stop 退出。看来还 monitor 了 proc fs。
于是只好祭出我的大杀器!
其实不用知道 thread pid,观察就知道 thread pid 一般在 pid+2 ~ pid+15 之间,而且比较连续,直接猜一个很容易猜中。
所以直接 ps | grep crackme 得到 pid 之后,加上个 4 去 gdb attach,一般都是对的,再通过 cmdline 检查一下是不是对的进程,如下
之后发现内存中有一个 libmobsecx.so[deleted],感觉应该就是它,因为最外层的 dex 里面就曾经 if x86 system.loadlibrary('libmobsecx.so') 过,估计是调试代码。
直接 dump memory,使用 baksmali 尝试反汇编,结果咣当一声响,baksmali 居然也崩溃了!
无奈,只好看一下 exception 原因,大概是 Encountered valid uleb128 that is out of range at offset
进一步研究,发现 dex 里面很多 code offset 都不对,把这个值打印出来一看,居然都是负数。莫非把代码段放到了 dex 上面的内存?
抱着这个猜测,把 dex 段前面一段内存也 dump 了出来,然后拼接在 dex 后面,再修改 baksmali 代码,diff 如下
gradle build 一下,再运行 baksmali -x -d system/framework,这次终于不再咣当一声抛 exception 了。
拿到 smali 之后,懒得看汇编,直接在 smali out 得到 dex, dex2jar 得到 jar,结果又咣当一声,dex2jar 也跪了。
突然发现 smali 里面居然有一个 test_dex2jar_crash 的东西,直接删除。
再次 smali out , dex2jar ,jd-gui 打开,嗯,科学合理了。
接着分析,大概是把输入当作一个 morse code ,然后解析之后,做了一大段逻辑,最后 sendEmptyMessage 让 Activity 显示一段话。
这里不得不怒吐一槽,一大堆 AES, SHA1, base64 在前面,分析了半天都没用,最后的答案居然在 这么一段代码里面
暴力一遍,跑出来答案前两个字符是 s5 似乎。
后面4 个字符都在 annotation 里面,一个是 e 的 annotation
另外一个是 a 的 annotation
拼接起来就得到了最终答案。噢,还要再用 morse code 转回去才行,直接提交这个字符串不行的。
第一题
第一题可以直接用 apktool d AliCrackme_1.apk 命令解包。dex2jar 也没问题,使用 jd-gui 打开 classes_dex2jar.jar 之后,发现有两个比较重要的函数却反编译不出来,是 getTableFromPic 和getPwdFromPic。
于是看了一下 smali 汇编,发现是从图片中两个 offset 提取的,直接读出来即可。
后面的 bytesToAliSmsCode 函数就比较简单了,直接使用 python 写出对应代码
passwd = '\xe4\xb9\x89\xe5\xbc\x93\xe4\xb9\x88\xe4\xb8\xb8\xe5\xb9\xbf\xe4\xb9\x8b'.decode('utf-8') table = '\xe4\xb8\x80\xe4\xb9\x99\xe4\xba\x8c\xe5\x8d\x81\xe4\xb8\x81\xe5\x8e\x82\xe4\xb8\x83\xe5\x8d\x9c\xe4\xba\xba\xe5\x85\xa5\xe5\x85\xab\xe4\xb9\x9d\xe5\x87\xa0\xe5\x84\xbf\xe4\xba\x86\xe5\x8a\x9b\xe4\xb9\x83\xe5\x88\x80\xe5\x8f\x88\xe4\xb8\x89\xe4\xba\x8e\xe5\xb9\xb2\xe4\xba\x8f\xe5\xa3\xab\xe5\xb7\xa5\xe5\x9c\x9f\xe6\x89\x8d\xe5\xaf\xb8\xe4\xb8\x8b\xe5\xa4\xa7\xe4\xb8\x88\xe4\xb8\x8e\xe4\xb8\x87\xe4\xb8\x8a\xe5\xb0\x8f\xe5\x8f\xa3\xe5\xb7\xbe\xe5\xb1\xb1\xe5\x8d\x83\xe4\xb9\x9e\xe5\xb7\x9d\xe4\xba\xbf\xe4\xb8\xaa\xe5\x8b\xba\xe4\xb9\x85\xe5\x87\xa1\xe5\x8f\x8a\xe5\xa4\x95\xe4\xb8\xb8\xe4\xb9\x88\xe5\xb9\xbf\xe4\xba\xa1\xe9\x97\xa8\xe4\xb9\x89\xe4\xb9\x8b\xe5\xb0\xb8\xe5\xbc\x93\xe5\xb7\xb1\xe5\xb7\xb2\xe5\xad\x90\xe5\x8d\xab\xe4\xb9\x9f\xe5\xa5\xb3\xe9\xa3\x9e\xe5\x88\x83\xe4\xb9\xa0\xe5\x8f\x89\xe9\xa9\xac\xe4\xb9\xa1\xe4\xb8\xb0\xe7\x8e\x8b\xe4\xba\x95\xe5\xbc\x80\xe5\xa4\xab\xe5\xa4\xa9\xe6\x97\xa0\xe5\x85\x83\xe4\xb8\x93\xe4\xba\x91\xe6\x89\x8e\xe8\x89\xba\xe6\x9c\xa8\xe4\xba\x94\xe6\x94\xaf\xe5\x8e\x85\xe4\xb8\x8d\xe5\xa4\xaa\xe7\x8a\xac\xe5\x8c\xba\xe5\x8e\x86\xe5\xb0\xa4\xe5\x8f\x8b\xe5\x8c\xb9\xe8\xbd\xa6\xe5\xb7\xa8\xe7\x89\x99\xe5\xb1\xaf\xe6\xaf\x94\xe4\xba\x92\xe5\x88\x87\xe7\x93\xa6\xe6\xad\xa2\xe5\xb0\x91\xe6\x97\xa5\xe4\xb8\xad\xe5\x86\x88\xe8\xb4\x9d\xe5\x86\x85\xe6\xb0\xb4\xe8\xa7\x81\xe5\x8d\x88\xe7\x89\x9b\xe6\x89\x8b\xe6\xaf\x9b\xe6\xb0\x94\xe5\x8d\x87\xe9\x95\xbf\xe4\xbb\x81\xe4\xbb\x80\xe7\x89\x87\xe4\xbb\x86\xe5\x8c\x96\xe4\xbb\x87\xe5\xb8\x81\xe4\xbb\x8d\xe4\xbb\x85\xe6\x96\xa4\xe7\x88\xaa\xe5\x8f\x8d\xe4\xbb\x8b\xe7\x88\xb6\xe4\xbb\x8e\xe4\xbb\x8a\xe5\x87\xb6\xe5\x88\x86\xe4\xb9\x8f\xe5\x85\xac\xe4\xbb\x93\xe6\x9c\x88\xe6\xb0\x8f\xe5\x8b\xbf\xe6\xac\xa0\xe9\xa3\x8e\xe4\xb8\xb9\xe5\x8c\x80\xe4\xb9\x8c\xe5\x87\xa4\xe5\x8b\xbe\xe6\x96\x87\xe5\x85\xad\xe6\x96\xb9\xe7\x81\xab\xe4\xb8\xba\xe6\x96\x97\xe5\xbf\x86\xe8\xae\xa2\xe8\xae\xa1\xe6\x88\xb7\xe8\xae\xa4\xe5\xbf\x83\xe5\xb0\xba\xe5\xbc\x95\xe4\xb8\x91\xe5\xb7\xb4\xe5\xad\x94\xe9\x98\x9f\xe5\x8a\x9e\xe4\xbb\xa5\xe5\x85\x81\xe4\xba\x88\xe5\x8a\x9d\xe5\x8f\x8c\xe4\xb9\xa6\xe5\xb9\xbb\xe7\x8e\x89\xe5\x88\x8a\xe7\xa4\xba\xe6\x9c\xab\xe6\x9c\xaa\xe5\x87\xbb\xe6\x89\x93\xe5\xb7\xa7\xe6\xad\xa3\xe6\x89\x91\xe6\x89\x92\xe5\x8a\x9f\xe6\x89\x94\xe5\x8e\xbb\xe7\x94\x98\xe4\xb8\x96\xe5\x8f\xa4\xe8\x8a\x82\xe6\x9c\xac\xe6\x9c\xaf\xe5\x8f\xaf\xe4\xb8\x99\xe5\xb7\xa6\xe5\x8e\x89\xe5\x8f\xb3\xe7\x9f\xb3\xe5\xb8\x83\xe9\xbe\x99\xe5\xb9\xb3\xe7\x81\xad\xe8\xbd\xa7\xe4\xb8\x9c\xe5\x8d\xa1\xe5\x8c\x97\xe5\x8d\xa0\xe4\xb8\x9a\xe6\x97\xa7\xe5\xb8\x85\xe5\xbd\x92\xe4\xb8\x94\xe6\x97\xa6\xe7\x9b\xae\xe5\x8f\xb6\xe7\x94\xb2\xe7\x94\xb3\xe5\x8f\xae\xe7\x94\xb5\xe5\x8f\xb7\xe7\x94\xb0\xe7\x94\xb1\xe5\x8f\xb2\xe5\x8f\xaa\xe5\xa4\xae\xe5\x85\x84\xe5\x8f\xbc\xe5\x8f\xab\xe5\x8f\xa6\xe5\x8f\xa8\xe5\x8f\xb9\xe5\x9b\x9b\xe7\x94\x9f\xe5\xa4\xb1\xe7\xa6\xbe\xe4\xb8\x98\xe4\xbb\x98\xe4\xbb\x97\xe4\xbb\xa3\xe4\xbb\x99\xe4\xbb\xac\xe4\xbb\xaa\xe7\x99\xbd\xe4\xbb\x94\xe4\xbb\x96\xe6\x96\xa5\xe7\x93\x9c\xe4\xb9\x8e\xe4\xb8\x9b\xe4\xbb\xa4\xe7\x94\xa8\xe7\x94\xa9\xe5\x8d\xb0\xe4\xb9\x90'.decode('utf-8') #print len(passwd) #print len(table) a = [] for i in range(len(passwd)): a.append(chr(table.find(passwd[i]))) print ''.join(a)
直接运行就得到答案 581026
第二题
第二题比较复杂一点,解包之后,分析逻辑
public void onClick(View paramAnonymousView) { String str = MainActivity.this.inputCode.getText().toString(); if (MainActivity.this.securityCheck(str)) { Intent localIntent = new Intent(MainActivity.this, ResultActivity.class); MainActivity.this.startActivity(localIntent); return; } Toast.makeText(MainActivity.this.getApplicationContext(), "验证码校验失败", 0).show(); }
可以发现,检查输入的逻辑在 so 里面:
public native securityCheck(Ljava/lang/String;)Z
使用 IDA 打开这个 so,仔细分析这个函数,可以看到这个函数最后和一个原来内容为 wojiushidaan 的字符串做了比较,但是 wojiushidaan 是骗人的,它不是最终答案!于是猜测在 JNI_Load 里面,这个字符串内容被做了修改。
考虑到静态分析比较复杂,这里可以直接修改 so 指令,把运行时的实际字符串内容打印出来即可。使用 __android_log_print 函数即可。
修改之后的指令如下:
.text:000012A8 .text:000012A8 loc_12A8 ; Rd = Op2 .text:000012A8 020 04 00 A0 E3 MOV R0, #4 .text:000012AC 020 02 10 A0 E1 MOV R1, R2 ; Rd = Op2 .text:000012B0 020 87 FF FF EB BL __android_log_print ; Branch with Link .text:000012B4 020 06 00 00 EA B loc_12D4 ; Branc
修改之后的 diff
$ bindiff libcrackme.so libcrackme.so.ori 000012A8 04 00 000012A9 00 30 000012AA A0 D2 000012AB E3 E5 000012AC 02 00 000012AE A0 D0 000012AF E1 E5 000012B0 87 01 000012B1 FF 00 000012B2 FF 53 000012B3 EB E1 000012B4 06 05 000012B7 EA 1A
再运行,盯着 adb logcat 看,就能找到答案 aiyou,bucuoo
I/InputDispatcher( 741): Delivering touch to: action: 0x1 I/yaotong ( 8605): SecurityCheck Started... I/aiyou,bucuoo( 8605): aiyou,bucuoo I/power ( 741): *** acquire_dvfs_lock : lockType : 1 freq : 1350000 D/SSRMv2:CustomFrequencyManagerService( 741): acquireDVFSLockLocked : type : DVFS_MIN_LIMIT frequency : 1350000 uid : 1000 pid : 741 pkgName : ACTIVITY_R
第三题
第三题又要复杂不少,简单的分析可以知道,最外面的 apk 和dex 都只是一个壳,真正的内容在 so 加载之后才会出来。
于是希望真正的 dex 在内存中是完整的。开启 gdb 直连,结果发现不能 attach,应该是开了反调试。尝试 ls /proc/<pid>/task 之后去 attach 线程,结果程序直接 stop 退出。看来还 monitor 了 proc fs。
于是只好祭出我的大杀器!
其实不用知道 thread pid,观察就知道 thread pid 一般在 pid+2 ~ pid+15 之间,而且比较连续,直接猜一个很容易猜中。
所以直接 ps | grep crackme 得到 pid 之后,加上个 4 去 gdb attach,一般都是对的,再通过 cmdline 检查一下是不是对的进程,如下
gdb> info proc maps process 15732 cmdline = 'crackme.a3' cwd = '/' exe = '/system/bin/app_process' gdb> info proc map Mapped address spaces: Start Addr End Addr Size Offset objfile 0x4004d000 0x4004f000 0x2000 0 /system/bin/app_process 0x4004f000 0x40050000 0x1000 0x1000 /system/bin/app_process 0x40050000 0x40051000 0x1000 0 0x40051000 0x40060000 0xf000 0 /system/bin/linker 0x40060000 0x40061000 0x1000 0xe000 /system/bin/linker 0x40061000 0x40062000 0x1000 0xf000 /system/bin/linker 0x40062000 0x40063000 0x1000 0 0x40063000 0x40064000 0x1000 0 0x40064000 0x40065000 0x1000 0 0x40065000 0x40071000 0xc000 0 /system/lib/libcutils.so ... gdb> dump memory /sdcard/my.dump 0x40000000 0x50000000
之后发现内存中有一个 libmobsecx.so[deleted],感觉应该就是它,因为最外层的 dex 里面就曾经 if x86 system.loadlibrary('libmobsecx.so') 过,估计是调试代码。
直接 dump memory,使用 baksmali 尝试反汇编,结果咣当一声响,baksmali 居然也崩溃了!
无奈,只好看一下 exception 原因,大概是 Encountered valid uleb128 that is out of range at offset
进一步研究,发现 dex 里面很多 code offset 都不对,把这个值打印出来一看,居然都是负数。莫非把代码段放到了 dex 上面的内存?
抱着这个猜测,把 dex 段前面一段内存也 dump 了出来,然后拼接在 dex 后面,再修改 baksmali 代码,diff 如下
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/BaseDexReader.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/BaseDexReader.java index 96645b8..73c10d2 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/BaseDexReader.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/BaseDexReader.java @@ -116,14 +116,27 @@ public class BaseDexReader<T extends BaseDexBuffer> { throw new ExceptionWithContext( "Invalid uleb128 integer encountered at offset 0x%x", offset); } else if ((currentByteValue & 0xf) > 0x07) { - if (!allowLarge) { - // for non-large uleb128s, we assume most significant bit of the result will not be - // set, so that it can fit into a signed integer without wrapping - throw new ExceptionWithContext( - "Encountered valid uleb128 that is out of range at offset 0x%x", offset); + result |= currentByteValue << 28; + if (result < 0) { + System.err.printf("changing result %d -> %d\n", result, 73728 + result); + result = 286720 + result; } + /* + if (result < 0) { + result = -result; + System.err.printf("changing result -> %d\n", result); + } else { + if (!allowLarge) { + // for non-large uleb128s, we assume most significant bit of the result will not be + // set, so that it can fit into a signed integer without wrapping + throw new ExceptionWithContext( + "Encountered valid uleb128 that is out of range at offset 0x%x, result = 0x%x, value = 0x%x", offset, result, currentByteValue); + } + }*/ + + } else { + result |= currentByteValue << 28; } - result |= currentByteValue << 28; } } }
gradle build 一下,再运行 baksmali -x -d system/framework,这次终于不再咣当一声抛 exception 了。
拿到 smali 之后,懒得看汇编,直接在 smali out 得到 dex, dex2jar 得到 jar,结果又咣当一声,dex2jar 也跪了。
突然发现 smali 里面居然有一个 test_dex2jar_crash 的东西,直接删除。
再次 smali out , dex2jar ,jd-gui 打开,嗯,科学合理了。
接着分析,大概是把输入当作一个 morse code ,然后解析之后,做了一大段逻辑,最后 sendEmptyMessage 让 Activity 显示一段话。
这里不得不怒吐一槽,一大堆 AES, SHA1, base64 在前面,分析了半天都没用,最后的答案居然在 这么一段代码里面
char[] arrayOfChar = str1.toCharArray(); int i = str1.substring(0, 2).hashCode(); if (i > 3904) { this.b.sendEmptyMessage(4); return; } if ((i == 3618) && (arrayOfChar[0] + arrayOfChar[1] == 168))
暴力一遍,跑出来答案前两个字符是 s5 似乎。
后面4 个字符都在 annotation 里面,一个是 e 的 annotation
@f(a="7e") public class e
另外一个是 a 的 annotation
@f(a="1p") public class a
拼接起来就得到了最终答案。噢,还要再用 morse code 转回去才行,直接提交这个字符串不行的。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
赞赏
看原图
赞赏
雪币:
留言: