【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
7. 重打包+签名+安装,发现APP可以正常动态调试了,反调试成功绕过
【Android逆向】反调试绕过的更多相关文章
- 编译Android内核 For nexus 5 以及绕过Android的反调试
本文博客链接:http://blog.csdn.net/qq1084283172/article/details/54880488 前面的博客中已经记录了Nexus 5手机的Android 4.4.4 ...
- 《Android逆向反编译代码注入》 - 逆向安全入门必看视频教程
适合人群: Android开发人员.逆向反编译开发人员.以及对Android逆向安全感兴趣的朋友. 视频地址: 51CTO学院:https://edu.51cto.com/course/24485 ...
- 解决Android加固多进程ptrace反调试的思路整理
本文博客链接:http://blog.csdn.net/qq1084283172/article/details/53613481 一.Android多进程反调试的原理代码 当ptrace附加目标进程 ...
- 修改Android手机内核,绕过反调试
本文博客链接:http://blog.csdn.net/qq1084283172/article/details/57086486 0x1.手机设备环境 Model number: Nexus 5 O ...
- 手动绕过百度加固Debug.isDebuggerConnected反调试的方法
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78237571 1.调用Debug.isDebuggerConnected函数这种反 ...
- [转]Android逆向之动态调试总结
一.在SO中关键函数上下断点 刚学逆向调试时.大多都满足于在SO中某关键函数上下断点.然后通过操作应用程序,去触发这个断点,然后进行调试 详细的步骤可以参见非虫大大的<Android软件安全与逆 ...
- 一种绕过PTRACE反调试的办法
Linux 系统gdb等调试器,都是通过ptrace系统调用实现.Android加固中,ptrace自身防止调试器附加是一种常用的反调试手段. 调试时一般需要手工在ptrace处下断点,通过修改ptr ...
- Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)
Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码) 来源 https://blog.csdn.net/jiangwei0910410003/article/details/51 ...
- 浅谈android反调试之 转发端口
反调试方案: 我们最通常使用的动态工具是IDA, IDA的动态调试端口默认为23946,我们可以通过/pro/net/tcp 查看android 系统所有TCP Socket 启动android_se ...
- 浅谈android反调试之轮询TracePid(解决方案是特色)
参考文章: 1. http://bbs.pediy.com/thread-207538.htm 2. http://www.wjdiankong.cn/android 需求: 常见的Android ...
随机推荐
- [转帖]在yum安装本地rpm文件时遇到public key不正确问题
yum错误:public.gpg.key: import read failed(2). 在yum安装本地rpm文件时遇到public key不正确问题 Downloading Packages: ...
- [转帖]一份快速实用的 tcpdump 命令参考手册
http://team.jiunile.com/blog/2019/06/tcpdump.html tcpdump 简介 对于 tcpdump 的使用,大部分管理员会分成两类.有一类管理员,他们熟知 ...
- [转帖]SQL Server数据库存储总结
SQL Server数据库存储文件类型:数据文件和日志文件.数据文件以页面作为存储单元存储数据. 页面:即数据页面,数据页(Page).是系统在磁盘间中分配的一段大小为8k的连续空间. 文件头(Fil ...
- vCenter6.7 无法启动
Get service 567f6edd-d4f7-4bfb-905b-1834c758a99d_com.vmware.vsphere.clientDon't update service 567f6 ...
- [转帖]python读取配置文件获取所有键值对_python总结——处理配置文件(ConfigParser)
python处理ConfigParser 使用ConfigParser模块读写ini文件 (转载) ConfigParserPython 的ConfigParser Module中定义了3个类对INI ...
- [转帖]SYSTEMD 配置文件
https://www.cnblogs.com/xiexun/p/13643952.html [Unit]区块通常是配置文件的第一个区块,用来定义 Unit 的元数据,以及配置与其他 Unit 的关系 ...
- [转帖]陈巍谈芯:NLP里比BERT更优秀的XLNet长什么样?
https://zhuanlan.zhihu.com/p/447836322 目录 收起 一.XLNet的优势 1)独得AR与AE两大绝学 2)集成了Tansformer-XL 二.XLNet的结 ...
- linux线程调度策略
linux线程调度策略 这是一篇非常好的关于线程调度的资料,翻译自shed 目录 linux线程调度策略 Scheduling policies SCHED_FIFO: First in-first ...
- Stream流处理快速上手最佳实践
一 引言 JAVA1.8得益于Lambda所带来的函数式编程,引入了一个全新的Stream流概念Stream流式思想类似于工厂车间的"生产流水线",Stream流不是一种数据结构, ...
- vue3封装搜索表单组件
seacrch 表单完成的功能 1.根据配置json配置项自动生成表单 ok 2.是响应式的排版 ok 3.点击搜索按钮会向上抛出值 ok 4.点击重置按钮会自动清空数据,不需要父组件额外的处理 ok ...