首页
社区
课程
招聘
[原创]移动安全挑战赛解题思路提交
发表于: 2015-1-28 17:13 5937

[原创]移动安全挑战赛解题思路提交

2015-1-28 17:13
5937
by zTrix

第一题

第一题可以直接用 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 转回去才行,直接提交这个字符串不行的。

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//