静态分析so小计
源APK
https://github.com/eternalsakura/ctf_pwn/blob/master/android%E9%80%86%E5%90%91/mobicrackNDK.apk
jadx
通过源码发现关键函数在
public native boolean testFlag(String str);
static {
System.loadLibrary("mobicrackNDK");
}
所以要看native 也就是so的代码进行分析
可惜ida实在是难搞,所以浅尝下hopper,下载:https://www.hopperapp.com/download.html?
把so丢进去即可。
点左上角的转换
搜索testFlag
这一块地址有点奇怪,无法转化为c,但是肉眼可见的可读性还行。
双击后面的代码可以跳转到指定的基地址,估计就是函数
点上面的可以返回
因此照着这个思路可以看到主要的代码逻辑在
abcdefghijklmn
这个里面
直接看汇编
str r0, [sp, #0xc8 + var_C0] (把r0的字数据转移到sp+200+var_C0这个地址上)
blx strlen@PLT ; strlen(blx是跳转到strlen函数地址)
movs r4, #0x0 (赋值,r4现在的值是0)
cmp r0, #0x10 (比较r0和16的大小,这里没太懂为啥是r0的长度)
beq loc_102e(b是跳转,eq是=0的意思,就是以上面如果条件是等于则跳转)
loc_102e
adds r6, r4, #0x0 (r6 = r4 + 0 = 0 + 0 = 0)
然后线性往下走loc_1030
loc_1030:
ldr r2, [sp, #0xc8 + var_C0](sp+200+var_C0这个地址上的数据读出来放到r2上,也就是上面的r0的数据,r0是长度为10的字符串,这里应该是r2现在是r0的首位)
add r1, sp, #0x14(r1 = sp + 20)
ldrb r3, [r2, r6] (将地址为r2+r6的数据读出来放到r3上,由于r6后面在+1所以r3就是r2的每一位,也就是input[i])
subs r3, r3, r6 (r3 = r3 - r6)(那就是input[i] = input[i]-i)
strb r3, [r6, r1] (将r3的数据转移到r6+r1的地址上,首先我们看r1是不变的,r6在递增,所以也就是input[i]-i会逐位的放到r3上)
adds r6, #0x1 (r6 = r6 + 1)
cmp r6, #0x8 (r6 和 8 的数据进行比较)
bne loc_1030 (b是跳转,ne是不等于0时跳转,这里我们注意到loc_1030依旧是这个代码块逻辑,所以这是一个循环)
也就是
for (int i = 0; i < 8; i++)
{
r3[i] = input[i] - i;
}
当跳出循环之后再线性往下走就行
ldr r3, =0x2f3e ; 0x113c (伪指令,相当于mov r3,0x2f3e)
movs r4, #0x0 (这里r4 = 0 )
strb r4, [r1, #0x8] (把r4的数据放到r1+8上面)
add r3, pc ; dword_3f88 (r3 = r3 + pc)
ldr r3, [r3] ; dword_3f88,seed (r3 = 读r3这个地址上的值)
ldr r0, [r3] ; argument "__s1" for method strcmp@PLT, "QflMn`fH",seed (r0 = 读r3这个地址上的值)
blx strcmp@PLT ; strcmp (strcmp)
cmp r0, r4 (比较r0 = r4的值,这里注意r3是上一个循环处理过的字符串,r0是seed)
bne loc_111e(如果不等于0 则跳转loc_111e,相等则往下线性走)
看上去是找java层的Calc
ldr r0, [r5]
ldr r1, =0x1756 ; 0x1140,0x1756
ldr r3, [r0]
add r1, pc ; "com/example/mobicrackndk/Calc"
ldr r3, [r3, #0x18]
blx r3
str r0, [sp, #0xc8 + var_C4]
cmp r0, r4
bne loc_1070
package com.example.mobicrackndk;
/* loaded from: classes.dex */
public class Calc {
public static String key;
public static void calcKey() {
StringBuffer sb = new StringBuffer("c7^WVHZ,");
key = sb.reverse().toString();
}
}
找到了calcKey这个函数
loc_1070:
ldr r0, [r5] ; CODE XREF=abcdefghijklmn+128
ldr r2, =0x1763 ; 0x114c,0x1763
ldr r3, =0x1767 ; 0x1150,0x1767
ldr r4, [r0]
movs r1, #0xe2
lsls r1, r1, #0x1
add r2, pc ; "calcKey"
ldr r4, [r4, r1]
add r3, pc ; "()V"
ldr r1, [sp, #0xc8 + var_C4]
blx r4
subs r2, r0, #0x0
bne loc_109e
取calcKey这个函数的methodid,定位到key
loc_109e:
ldr r0, [r5] ; argument #1 for method _ZN7_JNIEnv20CallStaticVoidMethodEP7_jclassP10_jmethodIDz, CODE XREF=abcdefghijklmn+164
ldr r1, [sp, #0xc8 + var_C4] ; argument #2 for method _ZN7_JNIEnv20CallStaticVoidMethodEP7_jclassP10_jmethodIDz
bl _ZN7_JNIEnv20CallStaticVoidMethodEP7_jclassP10_jmethodIDz ; _JNIEnv::CallStaticVoidMethod(_jclass*, _jmethodID*, ...)
ldr r0, [r7]
ldr r2, =0x1747 ; 0x115c,0x1747
ldr r3, =0x1749 ; 0x1160,0x1749
movs r1, #0x90
lsls r1, r1, #0x2
ldr r4, [r0, r1]
add r2, pc ; "key"
add r3, pc ; "Ljava/lang/String;"
adds r0, r7, #0x0
ldr r1, [sp, #0xc8 + var_C4]
blx r4
subs r4, r0, #0x0
bne loc_10ce
loc_10ce:
ldr r2, [r7] ; CODE XREF=abcdefghijklmn+218
movs r3, #0x91
lsls r3, r3, #0x2
ldr r3, [r2, r3]
ldr r1, [sp, #0xc8 + var_C4]
adds r2, r4, #0x0
adds r0, r7, #0x0
blx r3
adds r1, r0, #0x0
ldr r0, [r5]
movs r2, #0xa9
lsls r2, r2, #0x2
ldr r3, [r0]
add r4, sp, #0x20
ldr r3, [r3, r2]
movs r2, #0x0
blx r3
adds r5, r0, #0x0
b loc_1102
注意这块的位置loc_1102 -> loc_10f4 往下走又是loc_1102
loc_10f4:
000010f4 ldr r1, [sp, #0xc8 + var_C0] (r1现在又是我们输入的字符串的首位) ; CODE XREF=abcdefghijklmn+296
000010f6 adds r3, r4, r6 ( r3 = r4 + r6 由于r6上面到8 所以是从8 开始)
000010f8 subs r3, #0x8 (r3 = r3 - 8)
000010fa ldrb r2, [r1, r6] (r1 + r6 的值也就是input[i] 给 r2)
000010fc subs r2, r2, r6 (r2 = r2-r6 也就是input[i] - i)
000010fe strb r2, [r3] 将r2的值存到r3
00001100 adds r6, #0x1 (r6 = r6 + 1 )
loc_1102:
00001102 adds r0, r5, #0x0 这里r0 = r5 +0 ; argument "__s" for method strlen@PLT, CODE XREF=abcdefghijklmn+270
00001104 blx strlen@PLT ; strlen
00001108 adds r0, #0x8 (这里r0 = len(r0) + 8)
0000110a cmp r6, r0
0000110c blo loc_10f4 (如果r6< len(r0) + 8 则跳转到loc_10f4,这里的r0应该就是上面calc函数返回的key的值)
所以这里转换出来就是
for (int i = 8; i < 16; i++)
{
s3[i - 8] = input[i] - i;
}
所以输入
所以输入就可以变成
input_str = "QflMn`fH,ZHVW^7c"
output_str = ""
num = 0
for i in input_str:
output_str += i + num
num += 1
然后就是init初始话的时候对seed进行了操作
loc_117e:
0000117e ldrb r3, [r4, r7] (seed从第0位开始) ; CODE XREF=__init_my+36
00001180 subs r3, #0x3 (绕r3 = r3 -3 = seed[i] - 3)
00001182 strb r3, [r6, r7] 把r3存起来
00001184 adds r7, #0x1 (+1递增)
loc_1186:
00001186 ldr r4, [r5] (r4就是seed) ; CODE XREF=__init_my+16
00001188 adds r0, r4, #0x0 ; argument "__s" for method strlen@PLT
0000118a blx strlen@PLT ; strlen
0000118e cmp r7, r0 ( r7从0开始)
00001190 blo loc_117e
seed先做一层-3处理,所以前8位再-3即可,然后适配下python脚本
input_str = "QflMn`fH,ZHVW^7c"
output_str = ""
num = 0
for i in input_str:
if num < 8:
output_str += chr(ord(i) + num - 3)
else:
output_str += chr(ord(i) + num)
num += 1
print(output_str)
后话
静态分析native原生程序,初步了解汇编,后续还需要多读代码。印象比较深刻的有如下:
- ldr是读
- strb是存
- mov是赋值
- 循环大概是什么样子
参考链接
https://ctf-wiki.org/android/basic_reverse/static/so-example/
https://github.com/wnagzihxa1n/CTF-Mobile-Tutorial/blob/master/2015%E6%B5%B7%E5%B3%A1%E4%B8%A4%E5%B2%B8CTF/%E4%B8%80%E4%B8%AAAPK%EF%BC%8C%E9%80%86%E5%90%91%E8%AF%95%E8%AF%95%E5%90%A7ndk/WriteUp.md