0x00 前言
有价之宝:戳这里
无价之宝:戳这里
说明
在上一篇中进行了一个demo的练习,本来是静态分析的,但是动态分析也是可以的,为了进行一个练习,所以就拿来用了,这里感谢鬼哥。
内容
由于我们正在动态调试,所以要面对的一个问题就是反调试,所以不只是要进行动态调试,还要对反调试进行越过。
所以为了越过反调试,就要先了解一下反调试有哪些。
1.利用ptrace防止附加(1)
2.针对IDA。
3.总结
4.demo
0x01 利用ptrace 反调试
原理
ida等都是用ptrace来进行附加的。ptrace 有一个重要的地方就是,一个进程只能被一个进程调试。这是一个非常重要的地方。
知识补充
稍安勿躁,如果着急的话,直接看实现就好。
知识点1:
在执行系统调用之前,内核会先检查当前进程是否处于被“跟踪”(traced)的状态。如果是的话,内核暂停当前进程并将控制权交给跟踪进程,使跟踪进程得以察看或者修改被跟踪进程的寄存器。
知识点2:
ptrace 函数有四个参数。
第一个参数决定了ptrace的行为与其他参数的使用方法。
我们这里使用的是
PTRACE_TRACEME
其他的之后遇到再说。
我们使用这个调用是为了告诉内核,当前进程已经正在被 traced。
知识点3:
ida等调试工具使用的是ptrace。
以上
实现
NDK编程
说明
这个实现,使用了自身进行进程的ptrace。
首先是函数实现。
void anti_debug(){
ptrace(PTRACE_TRACEME,0,0,0);
}
JIN_OnLoad 调用
jint JNI_OnLoad(JavaVM* vm,void * reserved)
{
anti_debug();
JNIEnv *env;
if(vm->GetEnv(reinterpret_cast<void**>(&env),JNI_VERSION_1_6)!=JNI_OK){
return -1;
}
return JNI_VERSION_1_6;
}
本来这一句
if(vm->GetEnv(reinterpret_cast<void**>(&env),JNI_VERSION_1_6)!=JNI_OK){
return -1;
}
困扰我很久,最后知道了,这个就是注册Native。
测试
反调试测试
ida调试
首先adb连接root好的手机
我懒的用线,直接用wifi连接。
手机shell
su
setprop service.adb.tcp.port 5555
stop adbd
start adbd
输入指令即可。随便下载一个终端模拟器就可以了。
为了熟悉流程,我们来进行android_server的转发
最好还是自己进行敲一遍不要进行复制粘贴
转发完成。
ida进行调试
首先要知道包名,使用apkHelper。
com.example.hanlei.demo
这里有一个非常重要的事情
就是调试的手机你要先安装app。
然后进行attach
发现出现了这样的问题
The debugger could not attach to the selected process.
This can perhaps indicate the process was just terminated,or thiat you don't have the necessary privileges.
调试器无法连接到选定的进程。
这或许可以说明过程就终止,或者的时候你没有必要的特权。
看到了这里,就很有可能是出现了反调试。
所以我们现在进行反调试的去除。
反调试去除
明确一点,就是一般反调试,都是在JNI_OnLoad中进行的。
我们首先来看函数表。
我们要解决的是反动态调试。
来看JNI_OnLoad
这里调用了一个函数。
这个函数就很有可能是反调试的地方。
j__Z10anti_debugv ; CODE XREF: JNI_OnLoad+24↓p
.plt:0000065C ADR R12, 0x664
.plt:00000660 ADD R12, R12, #0x2000
.plt:00000664 LDR PC, [R12,#(_Z10anti_debugv_ptr - 0x2664)]! ; anti_debug(void)
看到这里只是进行一个返回。我们接着往下看
看到了ptrace,那么也就差不多找到了重点了。
我们去除反调试的方法这时候就知道最基础的就是两种,一种就是不调用,一种就是清空函数。
我们来进行修改。
进行修改。
然后替换,然后重新签名。
然后重新安装。
我android studio有问题,这里就不动态调试了。
优点(自己总结)
1.拦截新手。
2.消耗一点时间。(我瞎掰掰的)
3.可以当做练习(这个也是我瞎掰掰的)
缺点
1.直接可以静态去除。
0x02 针对IDA的android_server
pid_t GetPidByName(const charchar *as_name) {
DIR *pdir = NULL;
struct dirent *pde = NULL;
FILEFILE *pf = NULL;
char buff[128];
pid_t pid;
char szName[128];
// 遍历/proc目录下所有pid目录
pdir = opendir("/proc");
if (!pdir) {
perror("open /proc fail.\n");
return -1;
}
while ((pde = readdir(pdir))) {
if ((pde->d_name[0] < '0') || (pde->d_name[0] > '9')) {
continue;
}
sprintf(buff, "/proc/%s/status", pde->d_name);
pf = fopen(buff, "r");
if (pf) {
fgets(buff, sizeof(buff), pf);
fclose(pf);
sscanf(buff, "%*s %s", szName);
pid = atoi(pde->d_name);
if (strcmp(szName, as_name) == 0) {
closedir(pdir);
return pid;
}
}
}
closedir(pdir);
return 0;
}
我的电脑没有办法调试,自己可以尝试。
对应的破解方法就是改变android_server名称就可以了
其他的就先不进行测试了。
0x03 总结
1.针对逆向工具
防:比如检测android_server
攻:改名字就可以了
思路
1.检测android_server
2.检测gdbserver
3.软件使用自己已经不能用的server
2.ptrace
防:
(1)自身调用
(2)子进程调用
(3)孙子进程调用子进程,子进程调用父进程,父进程进行调用
攻:
(1)线程
(2)其他方法
3./proc/$pid/status
简单实现。
void be_attached_check()
{
try
{
const int bufsize = 1024;
char filename[bufsize];
char line[bufsize];
int pid = getpid();
sprintf(filename, "/proc/%d/status", pid);
FILE* fd = fopen(filename, "r");
if (fd != nullptr)
{
while (fgets(line, bufsize, fd))
{
if (strncmp(line, "TracerPid", 9) == 0)
{
int statue = atoi(&line[10]);
LOGD("%s", line);
if (statue != 0)
{
LOGD("be attached !! kill %d", pid);
fclose(fd);
int ret = kill(pid, SIGKILL);
}
break;
}
}
fclose(fd);
} else
{
LOGD("open %s fail...", filename);
}
} catch (...)
{
}
}
防:检测字段是否为0
攻:删除检测即可
4.针对调试要求
我们知道调试要求有两个,满足其中一个就是。
1.androidmainfest.xml的android:debuggable=true;
2.build.prop中ro.debuggable=true。
可以对这个地方进行检测。
5.IDA反附加和GDB反附加
搜索IDA或者GDB进行检测。在附加的时候停止工作。
说明
其他的如果在之后会遇到的话,那么就进行分析好了。
关于反调试暂时以上。
0x04 demo
网上好多的资料都是这个
我也来一刀吧
按照主要流程来。
预热
验证
重新签名,安装测试。
失败。证明可能有签名验证。
反编译。
尴尬,反编译发现没有检测签名的。
本来想偷懒的,用模拟器,但是好像模拟器运行失败了。到真机测试一下。发现可以运行。
这里没有签名验证。
好像可以直接开始
start java层分析
反编译
这个就是那个按钮的监听事件了。
我们去$smali里找找。
.method public onClick(Landroid/view/View;)V
.locals 5
.param p1, "v" # Landroid/view/View;
.prologue
.line 33
iget-object v2, p0, Lcom/yaotong/crackme/MainActivity$1;->this$0:Lcom/yaotong/crackme/MainActivity;
iget-object v2, v2, Lcom/yaotong/crackme/MainActivity;->inputCode:Landroid/widget/EditText;
invoke-virtual {v2}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
move-result-object v2
invoke-interface {v2}, Landroid/text/Editable;->toString()Ljava/lang/String;
move-result-object v1
.line 34
.local v1, "result":Ljava/lang/String;
iget-object v2, p0, Lcom/yaotong/crackme/MainActivity$1;->this$0:Lcom/yaotong/crackme/MainActivity;
invoke-virtual {v2, v1}, Lcom/yaotong/crackme/MainActivity;->securityCheck(Ljava/lang/String;)Z
move-result v2
if-eqz v2, :cond_0
.line 35
new-instance v0, Landroid/content/Intent;
iget-object v2, p0, Lcom/yaotong/crackme/MainActivity$1;->this$0:Lcom/yaotong/crackme/MainActivity;
const-class v3, Lcom/yaotong/crackme/ResultActivity;
invoke-direct {v0, v2, v3}, Landroid/content/Intent;-><init>(Landroid/content/Context;Ljava/lang/Class;)V
.line 36
.local v0, "i":Landroid/content/Intent;
iget-object v2, p0, Lcom/yaotong/crackme/MainActivity$1;->this$0:Lcom/yaotong/crackme/MainActivity;
invoke-virtual {v2, v0}, Lcom/yaotong/crackme/MainActivity;->startActivity(Landroid/content/Intent;)V
.line 42
.end local v0 # "i":Landroid/content/Intent;
:goto_0
return-void
.line 39
:cond_0
iget-object v2, p0, Lcom/yaotong/crackme/MainActivity$1;->this$0:Lcom/yaotong/crackme/MainActivity;
invoke-virtual {v2}, Lcom/yaotong/crackme/MainActivity;->getApplicationContext()Landroid/content/Context;
move-result-object v2
const-string v3, "\u9a8c\u8bc1\u7801\u6821\u9a8c\u5931\u8d25"
.line 40
const/4 v4, 0x0
.line 39
invoke-static {v2, v3, v4}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v2
.line 40
invoke-virtual {v2}, Landroid/widget/Toast;->show()V
goto :goto_0
.end method
这里我们看到。
:cond_0
iget-object v2, p0, Lcom/yaotong/crackme/MainActivity$1;->this$0:Lcom/yaotong/crackme/MainActivity;
invoke-virtual {v2}, Lcom/yaotong/crackme/MainActivity;->getApplicationContext()Landroid/content/Context;
move-result-object v2
const-string v3, "\u9a8c\u8bc1\u7801\u6821\u9a8c\u5931\u8d25"
cond_0才是失败才要跳转的
if-eqz v2, :cond_0
这里进行跳转
invoke-virtual {v2, v1}, Lcom/yaotong/crackme/MainActivity;->securityCheck(Ljava/lang/String;)Z
move-result v2
上面一句是这样的
也就是说securityCheck是重点函数。
.method public native securityCheck(Ljava/lang/String;)Z
这里看到了是一个Native层的函数
那么密码没有在smali那么就一定是在so层了。不然它又不能上天。
start Native层分析
找到函数
看到了这样一句话,我就是答案
那么这就是我们要弄的重点了。
先要知道包名 Android killer里有。
动态调试的结果并不理想,很有可能是因为有反调试。
反调试一般出现的地方是JNI_OnLoad
所以我们要进行越过。
反调试,动态调试
第一步,动态调试环境配置。
android:debuggable="true"
回编译
转发android_server
adb shell su /data/local/tmp/android_server
转发端口
adb forward tcp:23946 tcp:23946
使用调试
如果我们按照之前的调试方式的话,JNI_OnLoad就已经运行过了,所以我们要在运行之前进行调试。
adb shell am start -D -n com.yaotong.crackme/com.yaotong.crackme.MainActivity
转发端口
adb forward tcp:8700 jdwp:15127
IDA挂起 attach
正常进入调试
libdvm.so断点
jdb
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
总结
不知道是什么原因,我的调试总是出问题。
尝试下直接去掉验证好了。
但是流程是对的。
之后看来还要对jdb调试进行一下学习呢。
动态调试
转发android_server
进入so文件,进入函数
下段,运行
F8运行到这个位置,我们发现寄存器里的数值,R0存的是我们输入的数字。
R3里就是密码了。
然后就是在这个位置
拿到我们的答案。
以上