原帖地址:2016zctf_android1-200 writeup-【详细】http://www.52pojie.cn/thread-469259-1-1.html
(出处: 吾爱破解论坛)
楼主写的很详细,不过看上去就觉得有点复杂了,个人就想出了比较简单易懂的方法
看过原楼主帖子的应该知道这个软件有1处反调试和检测模拟器的小地方,原楼主通过修改流程中的返回值达到目标,不过呢,一般的情况下,往往我们需要更多的时间来尝试一些更加重要的操作,如dump内存,分析算法,不应该花太多的时间再操作反调试上
0x1 去除反调试
反调试,往往我们会在脱壳的时候遇到,一般情况下有三种处理手法
(1)直接修改apk程序中的必要文件,去除反调试
(2)IDA调试时,对ptrace关键处进行下断,修改返回值,部分代码达到去除反调试
(3)最直接的,修改手机rom,内核,从最底层直接去除反调试
我们最常接触的是第二种,当然也是这三个里面最简单易用的
[Java] 纯文本查看 复制代码 public boolean CheckOperatorNameAndroid(Context context) throws InterruptedException {
long v6 = 3500;
if(context.getSystemService("phone").getNetworkOperatorName().toLowerCase().equals(this.getString(
2131099675))) {
Toast.makeText(this.getApplicationContext(), this.getString(2131099678), 1).show();
new Timer().schedule(this.task, v6);
}
else {
Toast.makeText(this.getApplicationContext(), this.getString(2131099681), 1).show();
new Timer().schedule(this.task, v6);
}
return 0;
}
第一处是对模拟器进行检测,如果是则返回true,程序强制退出,导致我们后面测试中断,很简单,直接删除所有代码,留下return false即可
第二处,是在so里,看到程序一共有三个包,而往往我们调试的手机都是arm架构,那么直接可以删除armv7和x86,留下一个arm就行,反正不影响,能有多简单就多简单呗
[Java] 纯文本查看 复制代码 .text:00001AD8
.text:00001AD8 EXPORT Java_com_zctf_app_JNIclass_add
.text:00001AD8 Java_com_zctf_app_JNIclass_add
.text:00001AD8 PUSH {R3,LR}
.text:00001ADA BL sub_14B0
.text:00001ADE POP {R3,PC}
.text:00001ADE ; End of function Java_com_zctf_app_JNIclass_add
[Java] 纯文本查看 复制代码 .text:00001B78 LDR R1, [R4]
.text:00001B7A MOVS R3, #0x300
.text:00001B7E LDR R6, [R1,R3]
.text:00001B80 MOVS R0, R4
.text:00001B82 MOVS R1, R7
.text:00001B84 LDR R2, [SP,#0x40+src]
.text:00001B86 MOVS R3, #0
.text:00001B88 BLX R6
.text:00001B8A BL sub_14B0
.text:00001B8E STR R0, [SP,#0x40+src]
.text:00001B90 CMP R0, #0
.text:00001B92 BEQ loc_1BA4
.text:00001B94 LDR R2, [R4]
.text:00001B96 MOVS R1, #0x29C
在这两处都调用了sub_1480这个函数,而这个函数就是进行反调试的地方,进去看看
[Java] 纯文本查看 复制代码 int sub_14B0()
{
__pid_t v0; // r6@1
signed int v1; // r6@1
FILE *v2; // r5@1
int result; // r0@3
unsigned int v4; // [sp+4h] [bp-1F4h]@3
char v5; // [sp+8h] [bp-1F0h]@3
char s; // [sp+1Ch] [bp-1DCh]@2
char v7; // [sp+E4h] [bp-114h]@1
int v8; // [sp+1E4h] [bp-14h]@1
v8 = _stack_chk_guard;
v0 = j_j_getpid();
j_j_memset(&v7, 0, 0xFFu);
j_j_sprintf(&v7, "/proc/%d/status", v0);
v1 = 5;
v2 = j_j_fopen(&v7, "r");
do
{
--v1;
j_j_fgets(&s, 150, v2);
}
while ( v1 );
j_j_fscanf(v2, "%s %d", &v5, &v4);
j_j_fclose(v2);
result = (__PAIR__(v4, v4) - __PAIR__(v4 - 1, 1)) >> 32;
if ( v8 != _stack_chk_guard )
j_j___stack_chk_fail(result);
return result;
}
这里调用fopen打开数据流,并从中获取到状态码和tracerpid值,正常情况下,tracerpid默认为0,调试状态下为一直不为0的值,通过检测这个值可以到反调试的目的
那这里直接强制返回0即可,通过下面这个也可以知道
[Java] 纯文本查看 复制代码 if ( sub_14B0() )
{
v11 = *(int (__fastcall **)(int, const char *))(*(_DWORD *)v3 + 668);
v12 = v3;
v13 = "you miss sth";
}
不为0会直接走到错误流程上
那么我的做法是,直接返回0即可
代码
[Java] 纯文本查看 复制代码 .text:000014B0 PUSH {R4-R6,LR}
.text:000014B2 MOVS R0, #0
.text:000014B4 POP {R4-R6,PC}
对应16进制0020 70BD
打包后重新签名就可以正常的进行调试了
0x2 分析程序
我们看到app的assets文件夹下有一个db文件,观察后发现数据并未加密,直接用软件查看即可,省的通过代码分析 - -
查看后是zctf2016
接下来这个数据会做为des的key对数据进行加密,加密后的数据直接传入native层
[Java] 纯文本查看 复制代码 public int auth(Context context, String input, String password1) {
int v2;
byte[] v6 = Auth.encrypt(new StringBuffer(input).reverse().toString().getBytes(), password1);
try {
InputStream v3 = context.getAssets().open(context.getString(2131099679));
byte[] v0 = new byte[64];
do {
}
while(v3.read(v0) > 0);
v3.close();
v2 = 0;
while(true) {
label_18:
if(v2 >= v0.length) {
return 1;
}
if(v2 >= v6.length) {
return 1;
}
if(v2 < v0.length && v2 < v6.length && v0[v2] != v6[v2]) {
break;
}
goto label_31;
}
}
catch(IOException v1) {
goto label_34;
}
int v9 = 0;
return v9;
label_31:
++v2;
goto label_18;
label_34:
v1.printStackTrace();
return 1;
}
这里判断的是加密后的数据是否等于flag.bin文件数据,那数据直接用16进制编辑器查看就行,然后调用des的解密方法可以获取到未加密的值,由于调用了reverse()方法,所以需要对字符串反转一下才是真正的未加密的值,解密后是zctf{Notthis},由于逻辑上并没有对用户名长度进行限制,所以这里的用户名密码组合是任意的,原楼主这里没有交代清楚
0x3 寻找flag
先静态分析
[Java] 纯文本查看 复制代码 v14 = j_j_malloc(0x1460u);
v15 = v14;
if ( v14 )
{
j_j_memset(v14, 0, 0x1460u);
j_j_memcpy(v15, &top, 0x100u);
v16 = j_j_fopen("/data/data/com.zctf.app/files/bottom", "rb");
v17 = v16;
if ( v16 )
{
v20 = j_j_fread((char *)v15 + 256, 1u, 0x1360u, v16);
j_j_fclose(v17);
j_j_memset(&v26, 0, 0x10u);
v21 = *(_DWORD *)v10;
v22 = *((_DWORD *)v10 + 1);
v26 = v21;
v27 = v22;
v23 = j_j_malloc(0x1460u);
j_j_memset(v23, 0, 0x1460u);
DES_Decrypt(v15, v20 + 256, &v26, v23);
j_j_free(v15);
j_j_free(v23);
v18 = "System.out";
v19 = "Too late, Boy";
}
else
{
v18 = "System.out";
v19 = "Botton lost";
}
j_j___android_log_print(4, v18, v19);
}
此处读取了assets下的bottom文件,然后调用了des_decrypt对数据进行解密,解密key恰好就是我们传入native的数据,解密之后立刻就释放了内存,
所以这里我们直接在j_j_free(v15)处下断
IDA附加手机动态调试,下面就直接拿原楼主的图了
由于程序中提示的是图片损坏,那么相信真正的内存中的数据也是一个图片,切换到r5后发现头数据是89 50 4E 47 0D 0A 1A 0A 00
标准的png格式,那直接找结尾就行 AE 42 60 82 ,用IDC脚本直接抠出来,用stegsolve即可看到图片的flag
|