【Android逆向】反调试绕过
1. 拿到52pojie的反调试挑战apk
链接: https://www.52pojie.cn/thread-742686-1-1.html 的附件中
2. 项目进行安装,点开app,同时挑战成功,不慌
3. 使用IDA attach到目的进程观察,发现app立刻闪退,证明app必然存在反调试逻辑
4. apk拖入到JEB中观察到,有调用到一个native函数
public class MainActivity extends AppCompatActivity {
@Override // android.support.v7.app.AppCompatActivity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(0x7F04001B); // layout:activity_main
Toast.makeText(this, myJNI.checkport(), 0).show();
}
}
public class myJNI {
static {
System.loadLibrary("six");
}
public static native String checkport() {
}
}
那么基本定位到应该是在so层做了这些处理
5. 将apk解压开拿到libsix.so,并通过IDA进行反汇编分析
1. 在导出表中搜索checkport,发现没有,说明是动态注册的
2. 搜索JNI_OnLoad, F5查看反编译代码
jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
JNIEnv *env; // [sp+4h] [bp-14h] BYREF
_android_log_print(4, "JNI_LOG", byte_C860B588);
env = 0;
if ( !(*vm)->GetEnv(vm, (void **)&env, 65540) )
{
dword_C860D004 = (int)(*env)->FindClass(env, "demo2/jni/com/myapplication/myJNI");
SearchObjProcess();
if ( dword_C860D004 )
{
if ( (*env)->RegisterNatives(env, (jclass)dword_C860D004, (const JNINativeMethod *)off_C860CE44, 1) >= 0 )
{
printf("--------JNI_OnLoad-----");
return 65540;
}
puts("register native method failed!");
}
else
{
printf("cannot get class:%s\n", "demo2/jni/com/myapplication/myJNI");
}
}
return -1;
}
说明注册函数表在off_C860CE44中,双击进去
.data.rel.ro.local:C860CE44 ; ===========================================================================
.data.rel.ro.local:C860CE44
.data.rel.ro.local:C860CE44 ; Segment type: Pure data
.data.rel.ro.local:C860CE44 AREA .data.rel.ro.local, DATA
.data.rel.ro.local:C860CE44 ; ORG 0xC860CE44
.data.rel.ro.local:C860CE44 0D B6 60 C8 off_C860CE44 DCD aCheckport ; DATA XREF: JNI_OnLoad+50↑o
.data.rel.ro.local:C860CE44 ; JNI_OnLoad+54↑o
.data.rel.ro.local:C860CE44 ; .text:off_C860A4FC↑o
.data.rel.ro.local:C860CE44 ; "checkport"
.data.rel.ro.local:C860CE48 17 B6 60 C8 DCD aLjavaLangStrin ; "()Ljava/lang/String;"
.data.rel.ro.local:C860CE4C 75 A1 60 C8 DCD sub_C860A174+1
.data.rel.ro.local:C860CE4C ; .data.rel.ro.local ends
果然代码在.data.rel.ro.local段中,这里描述的是java方法名,java方法签名,和对应的native方法,双击 sub_C860A174 进入
FILE *__fastcall sub_C860A174(JNIEnv *env)
{
FILE *result; // r0
FILE *v3; // r5
char v4[4104]; // [sp+4h] [bp+0h] BYREF
_android_log_print(4, "JNI_LOG", byte_C860B494);
memset(v4, 0, 0x1000u);
result = popen("cat /proc/net/tcp |grep :5D8A", "r");
v3 = result;
if ( result )
{
if ( fgets(v4, 4096, result) )
exit(0);
pclose(v3);
return (FILE *)(*env)->NewStringUTF(env, "恭喜你,挑战成功!");
}
return result;
}
从这里可以看到,这个方法里在检查监听端口23946(十六进制5D8A),打开汇编代码,把这里nop掉
(Edit -> Patch program -> Change byte)
.text:C860A174 CODE16
.text:C860A174
.text:C860A174 ; =============== S U B R O U T I N E =======================================
.text:C860A174
.text:C860A174 ; Attributes: bp-based frame fpd=0x1008
.text:C860A174
.text:C860A174 ; FILE *__fastcall sub_C860A174(JNIEnv *env)
.text:C860A174 sub_C860A174 ; DATA XREF: .data.rel.ro.local:C860CE4C↓o
.text:C860A174
.text:C860A174 var_C= -0xC
.text:C860A174
.text:C860A174 ; __unwind { // dword_C8609000
.text:C860A174 F0 B5 PUSH {R4-R7,LR}
.text:C860A176 AD F5 80 5D SUB.W SP, SP, #0x1000
.text:C860A17A 21 4C LDR R4, =(__stack_chk_guard_ptr - 0xC860A188)
.text:C860A17C 83 B0 SUB SP, SP, #0xC
.text:C860A17E 0D F5 80 52 ADD.W R2, SP, #0x100C+var_C
.text:C860A182 20 49 LDR R1, =(aJniLog - 0xC860A190) ; "JNI_LOG"
......
.text:C860A1CA FF F7 16 EF BLX exit ; 把这里nop掉
.text:C860A1CA
.text:C860A1CE ; ---------------------------------------------------------------------------
......
.text:C860A1F6
.text:C860A1F6 loc_C860A1F6 ; CODE XREF: sub_C860A174+7C↑j
.text:C860A1F6 0D F5 80 5D ADD.W SP, SP, #0x1000
.text:C860A1FA 03 B0 ADD SP, SP, #0xC
.text:C860A1FC F0 BD POP {R4-R7,PC}
.text:C860A1FC
.text:C860A1FC ; End of function sub_C860A174
3. JNI_OnLoad里还有一个可以的方法 SearchObjProcess(); 点进去看看
FILE *SearchObjProcess()
{
FILE *result; // r0
FILE *v1; // r5
int v2; // r11
int v3; // r10
int v4; // r9
unsigned __int8 v5; // r0
char *v6; // [sp+4h] [bp-103Ch]
int v7; // [sp+8h] [bp-1038h]
char v8[4140]; // [sp+14h] [bp-102Ch] BYREF
memset(v8, 0, 0x1000u);
result = popen("ps", "r");
v1 = result;
if ( result )
{
while ( fgets(v8, 4096, v1) )
{
_android_log_print(4, "JNI_LOG", byte_C860B544, v8);
v6 = strstr(v8, "android_server");
v7 = (unsigned __int8)strstr(v8, "gdbserver");
v2 = (unsigned __int8)strstr(v8, "gdb");
v3 = (unsigned __int8)strstr(v8, "fuwu");
v4 = (unsigned __int8)strstr(v8, "android_ser");
v5 = (unsigned __int8)strstr(v8, "and_");
if ( v6 || v7 || v2 || v3 || v4 || v5 )
exit(0);
}
return (FILE *)pclose(v1);
}
return result;
}
这里可以看到就是在对进程名关键字进行检查,同上直接把exit给nop掉
4. ctrl+s 看看段列表情况,发现有一个.init_array段,有这个段说明里面有东西,进去看看,有可能有反调试的代码
.init_array:C860CE58 ; ELF Initialization Function Table
.init_array:C860CE58 ; ===========================================================================
.init_array:C860CE58
.init_array:C860CE58 ; Segment type: Pure data
.init_array:C860CE58 AREA .init_array, DATA
.init_array:C860CE58 ; ORG 0xC860CE58
.init_array:C860CE58 ED A0 60 C8 DCD thread_create+1
.init_array:C860CE5C 00 00 00 00 ALIGN 0x10
.init_array:C860CE5C ; .init_array ends
.init_array:C860CE5C
里面有个thread_create函数,看来是有情况,跟进去看看
.text:C860A0EC ; int thread_create()
.text:C860A0EC EXPORT thread_create
.text:C860A0EC thread_create ; DATA XREF: .init_array:C860CE58↓o
.text:C860A0EC ; __unwind { // dword_C8609000
.text:C860A0EC 10 B5 PUSH {R4,LR}
.text:C860A0EE 04 20 MOVS R0, #4 ; prio
.text:C860A0F0 0E 4C LDR R4, =(aJniLog - 0xC860A0F8) ; "JNI_LOG"
.text:C860A0F2 0F 4A LDR R2, =(aEnterThreadCre - 0xC860A0FA) ; "enter thread_create"
.text:C860A0F4 7C 44 ADD R4, PC ; "JNI_LOG"
.text:C860A0F6 7A 44 ADD R2, PC ; "enter thread_create"
.text:C860A0F8 21 46 MOV R1, R4 ; tag
.text:C860A0FA FF F7 66 EF BLX __android_log_print
.text:C860A0FA
.text:C860A0FE 0D 48 LDR R0, =(t0_ptr - 0xC860A108)
.text:C860A100 0D 4A LDR R2, =(thread_function_ptr - 0xC860A10E)
.text:C860A102 00 21 MOVS R1, #0 ; attr
.text:C860A104 78 44 ADD R0, PC ; t0_ptr
.text:C860A106 00 68 LDR R0, [R0] ; t0 ; newthread
.text:C860A108 0B 46 MOV R3, R1 ; arg
.text:C860A10A 7A 44 ADD R2, PC ; thread_function_ptr
.text:C860A10C 12 68 LDR R2, [R2] ; thread_function ; start_routine
.text:C860A10E FF F7 86 EF BLX pthread_create
.text:C860A10E
.text:C860A112 01 30 ADDS R0, #1
.text:C860A114 08 D1 BNE locret_C860A128
.text:C860A114
.text:C860A116 09 4A LDR R2, =(aFailToCreatePt - 0xC860A120) ; "fail to create pthread t0"
.text:C860A118 04 20 MOVS R0, #4 ; prio
.text:C860A11A 21 46 MOV R1, R4 ; tag
.text:C860A11C 7A 44 ADD R2, PC ; "fail to create pthread t0"
.text:C860A11E FF F7 54 EF BLX __android_log_print
.text:C860A11E
.text:C860A122 01 20 MOVS R0, #1 ; int
.text:C860A124 FF F7 68 EF BLX exit
.text:C860A124
.text:C860A128 ; ---------------------------------------------------------------------------
.text:C860A128
.text:C860A128 locret_C860A128 ; CODE XREF: thread_create+28↑j
.text:C860A128 10 BD POP {R4,PC}
.text:C860A128
.text:C860A128 ; End of function thread_create
.text:C860A128
.text:C860A128 ; ---------------------------------------------------------------------------
.text:C860A12A 00 BF ALIGN 4
.text:C860A12C 94 13 00 00 off_C860A12C DCD aJniLog - 0xC860A0F8 ; DATA XREF: thread_create+4↑r
.text:C860A12C ; "JNI_LOG"
.text:C860A130 E8 13 00 00 off_C860A130 DCD aEnterThreadCre - 0xC860A0FA
.text:C860A130 ; DATA XREF: thread_create+6↑r
.text:C860A130 ; "enter thread_create"
.text:C860A134 64 2E 00 00 off_C860A134 DCD t0_ptr - 0xC860A108 ; DATA XREF: thread_create+12↑r
.text:C860A138 62 2E 00 00 off_C860A138 DCD thread_function_ptr - 0xC860A10E
.text:C860A138 ; DATA XREF: thread_create+14↑r
.text:C860A13C D6 13 00 00 off_C860A13C DCD aFailToCreatePt - 0xC860A120
.text:C860A13C ; DATA XREF: thread_create+2A↑r
.text:C860A13C ; } // starts at C860A0EC ; "fail to create pthread t0"
.text:C860A140 CODE32
.text:C860A140
.text:C860A140 ; =============== S U B R O U T I N E =======================================
.text:C860A140
开启了一个线程去调用 thread_function_ptr ,该函数反编译分析
FILE *thread_function()
{
__pid_t v0; // r5
FILE *result; // r0
FILE *v2; // r4
int v3; // r5
char v4[20]; // [sp+0h] [bp-140h] BYREF
char v5[256]; // [sp+14h] [bp-12Ch] BYREF
_android_log_print(4, "JNI_LOG", "enter thread_function");
v0 = getpid();
memset(v4, 0, sizeof(v4));
sprintf(v4, "/proc/%d/status", v0);
while ( 1 )
{
result = fopen(v4, "r");
v2 = result;
if ( !result )
return result;
v3 = 6;
while ( !feof(v2) )
{
fgets(v5, 255, v2);
_android_log_print(4, "JNI_LOG", "linestr:%s", v5);
if ( !--v3 )
{
if ( getnumberfor_str((int)v5) > 0 )
exit(0);
break;
}
}
fclose(v2);
sleep(1u);
}
}
这里就是在检查/proc/目标进程id/status 第六行的值,例子
tiffany:/ # cat /proc/4398/status
Name: m.myapplication
State: S (sleeping)
Tgid: 4398
Pid: 4398
PPid: 640
TracerPid: 0 //就是这里,如果被attach,这里会是其他进程的id,不会是0
Uid: 10151 10151 10151 10151
Gid: 10151 10151 10151 10151
Ngid: 0
FDSize: 128
......
直接把这里的exit也nop掉,,注意是改thread_function 的,而不是外部调用函数的,
6. IDA->Edit -> Patch program -> apply patch into file
ps: 这次我用androidkiller重打包成功,但是运行提示某个资源找不到,选择手动打ok了,运行成功
看来还是得多会几种方式
java -jar ../apktool_2.2.4.jar b anti-debug -o anti-debug-new.apk
java -jar ../signapk.jar ../testkey.x509.pem ../testkey.pk8 anti-debug-new.apk anti-debug-signed.apk