首页
论坛
课程
招聘
[原创]记一次完整的Android native层动态调试--使用avd虚拟机
2022-7-19 15:46 7831

[原创]记一次完整的Android native层动态调试--使用avd虚拟机

2022-7-19 15:46
7831

最近打dsctf,第一道Android题用到了动态调试(其实也可以不用)arm架构的so库,经过不懈努力,终于搭好了一个像样的环境,在此记录一下完整的过程。

1.反编译并分析java层代码

把apk包丢进jadx(或jeb)即可,找到MainActivity分析
图片描述
发现关键函数是check,显然存在于so库。使用apktool解包apk,得到so库并使用ida分析。
apktool解包命令:

1
apktool d catchme.apk -o catchme

(正常情况下应该是java -jar apktool,我将此命令打包成了批处理文件执行)

2.分析so库文件代码

在ida里找到接口函数JNIOnload
图片描述
直接查找check没有结果,说明函数是动态注册产生且函数名经过了混淆处理,在ida中载入jni.h,以便找到registerclass函数
使用ctrl+f9或file->load file->Parse C header file即可载入,jni.h我会放在附件里。载入后对变量重新设置类型(如JNIEnv *)即可将指针偏移直接转化为结构体成员。
图片描述
实践发现,此函数为动态注册函数,传入的第二个值(a2)就是函数地址,我们定位到对应的函数中
图片描述
使用findcrypt插件会发现存在aes算法,分析函数可得具体加密过程,但发现解密根本得不到明文,后面经过动调会发现,这是一个假的check,程序根本不会经过这里。

3.搭建avd环境动态调试

因为so文件是基于arm架构,故不能在一般的模拟器上调试,只能在Android Studio中下载arm架构虚拟机(推荐各位有root真机最好还是用真机,不仅流畅程度高不少,某些要检测环境的app也可以避免去绕),安装好虚拟机后,使用adb push将ida中的android_server、android_server64放入虚拟机(我放的位置是/data/local/tmp),接着给予执行权限并执行,然后进行调试的四个步骤

1.以调试模式启动app

1
adb shell am start -D -n com.ctf.catchme/.MainActivity

2.端口转发

1
adb forward tcp:23946 tcp:23946

3.ida附加,并更改调试选项

打开IDA,选择菜单Debugger -> Attach -> Remote ARM Linux/Android debugger,访问本地的23946端口。连接后更改调试选项,至少将载入库断点加上,也就是Suspend on library load/unload。

4.jdb连接

一定要打开ddms,并且是在以调试模式启动那一个步骤前(也就是第一步之前)打开,往往调试端口就是8700

1
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700

然后就可以查看当前载入库,不断运行(F9)当发现我们要的so库被载入时,即可点击查看函数了
对JNIOnload和我们怀疑的check函数断点,发现在JNIOnload开头成功断下,但再次执行后程序直接脱离调试器,这让我一度认为我的调试步骤有问题。后面经过学长点拨,推测是有反调试机制,于是进入JNIOnload不断步过,成功定位到使程序中止的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int sub_A394()
{
  FILE *stream; // [sp+30h] [bp-820h]
  __pid_t v2; // [sp+3Ch] [bp-814h]
  char s1[10]; // [sp+44h] [bp-80Ch] BYREF
  char v4[1014]; // [sp+4Eh] [bp-802h] BYREF
  char s[1036]; // [sp+444h] [bp-40Ch] BYREF
 
  v2 = getpid();
  sprintf(s, byte_1F168, v2);
  stream = fopen(s, byte_1F178);
  if ( stream )
  {
    while ( fgets(s1, 1024, stream) )
    {
      if ( !strncmp(s1, aZikmzxal, 9u) )
      {
        if ( atoi(v4) )
        {
          fclose(stream);
          sub_89C8(v2, 9);
          LOBYTE(dword_0) = 99;
        }
        break;
      }
    }
    fclose(stream);
  }
  return _stack_chk_guard;
}

其中函数sub_89C8就是关闭程序的罪魁祸首,而这整个函数就是使用了一种反调试方法
简单的patch掉if执行条件后,再次运行依然不正常退出,再次定位出现问题的点,发现还有一个反调试函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int __fastcall sub_A208(int a1)
{
  int v1; // r0
  __pid_t v2; // r0
  int v4; // [sp+18h] [bp-18h]
 
  sub_9F20(a1);
  if ( a1 )
  {
    v4 = sub_A020(a1, &unk_1F130);
    v1 = sub_A04A(a1, v4, &unk_1F150, &unk_1F164);
    if ( (unsigned __int8)sub_A2A0(a1, v4, v1) )
    {
      v2 = getpid();
      sub_89C8(v2, 9);
      LOBYTE(dword_0) = 99;
      return 1;
    }
    else
    {
      return 0;
    }
  }
  else
  {
    return 0;
  }
}

使用isDebuggerConnected函数判断是否被调试,同样patch即可,不过多赘述。
再次运行发现还是无法进入check函数,只好仔细分析JNIOnload,发现在动态注册了之后会有另一个类似覆盖地址的操作
图片描述
推测原地址被覆盖为了新的函数,点进可疑函数,发现同样是一个aes加密,并且使用了魔改base64,简单分析解密,成功得到明文。


[2022冬季班]《安卓高级研修班(网课)》月薪两万班招生中~

最后于 2022-7-20 20:36 被Ysiel编辑 ,原因: 内容错误
上传的附件:
收藏
点赞2
打赏
分享
最新回复 (6)
雪    币: 239
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
石晓 活跃值 2022-8-10 18:28
2
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
逆向-新手 活跃值 2022-9-13 23:07
3
0
大佬  请教个问题。麻烦了
我调试到registernative函数这个位置:而且我也知道了R2是check函数
libcatchme.so:D66CB9B8 LDR             R1, [SP,#0x68]
libcatchme.so:D66CB9BA ADD             R2, SP, #0xDC
libcatchme.so:D66CB9BC MOVS            R3, #1
libcatchme.so:D66CB9BE BL              unk_D66CBAD4(registernative函数)
但是R2实际对应的是一个DCB数据
libcatchme.so:D66DF32D unk_D66DF32D DCB 0x63

问题点:我该怎么获取到这个R2的地址信息啊
雪    币: 227
活跃值: 活跃值 (657)
能力值: ( LV4,RANK:44 )
在线值:
发帖
回帖
粉丝
Ysiel 活跃值 2022-9-20 16:42
4
0
逆向-新手 大佬 请教个问题。麻烦了 我调试到registernative函数这个位置:而且我也知道了R2是check函数 libcatchme.so:D66CB9B8 LDR R1 ...
没太懂你的意思,你把它转化成四字节应该就是地址了吧
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
逆向-新手 活跃值 2022-9-23 09:12
5
0
大佬 就是你文章中描述的这段“实践发现,此函数为动态注册函数,传入的第二个值(a2)就是函数地址,我们定位到对应的函数中” 这个a2的地址是怎么获取到的
雪    币: 227
活跃值: 活跃值 (657)
能力值: ( LV4,RANK:44 )
在线值:
发帖
回帖
粉丝
Ysiel 活跃值 2022-9-23 19:41
6
0
逆向-新手 大佬 就是你文章中描述的这段“实践发现,此函数为动态注册函数,传入的第二个值(a2)就是函数地址,我们定位到对应的函数中” 这个a2的地址是怎么获取到的
IDA可以把D66DF32D那个地址的DB修改成四字节的数据DD,对应的应该就是注册的函数地址
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
逆向-新手 活跃值 2022-9-24 08:52
7
0
好的 谢谢大佬
游客
登录 | 注册 方可回帖
返回