逆向(六)动态调试

一、什么是动态调试

将运行起来的程序,通过断点、打印等方式,查看参数、返回值、函数调用流程等。

1.1 Xcode调试APP的原理

Xcode中内置LLDB调试器,通过与iPhone连接,在iPhone安装debugserver的程序,debugserver与LLDB互相通信,发送调试指令和接收结果,debugserver与App之间互相通信,发送指令和接受结果。

屏幕快照 2018-10-31 21.48.09

  • GCC、LLVM、GDB、LLDB
    • Xcode的编译器发展历程:GCC—>LLVM
    • Xcode的调试器发展历程:GDB—>LLDB
  • debugserver 一开始存放在Mac的Xcode里面
    • /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/De
      viceSupport/9.1/DeveloperDiskImage.dmg/usr/bin/debugserver
  • 当Xcode识别到手机设备时,Xcode会自动将debugserver安装到iPhone上
    • /Developer/usr/bin/debugserver
  • Xcode调试的局限性
    • 一般情况下,只能调试通过Xcode安装的app

1.2 动态调试其他APP原理

屏幕快照 2018-10-31 21.48.15

二、如何动态调试

2.1 debugserver的权限问题

  • 默认情况下,/Developer/usr/bin/debugserver缺少一定的权限,只能调试通过Xcode安装的APP,无法调试其他的APP(比如通过App Store下载的APP)
  • 如果希望调试其他APP,需要对debugserver重新签名,签上两个调试相关的权限:
    • get-task-allow
    • task_for_pid-allow

2.2 给debugserver签上权限

签名使用到的工具ldid,当然也可以使用codesign。此处利用之前已经讲述的命令ldid

2.2.1 拷贝debugserver到Mac

iPhone上的Developer目录是只读的,无法直接对/Developer/usr/bin/debugserver文件签名,需要先把debugserver复制到Mac

2.2.2 导出原来权限

通过ldid命令导出文件以前的签名权限

1
$ ldid -e debugserver > debugserver.entitlements

2.2.3 添加权限

debugserver.entitlements添加get-task-allow和task_for_pid-allow权限如下:

debugserver_entitlements

2.2.4 重签名

1
$ ldid -Sdebugserver.entitlements debugserver

将已经签好权限的debugserver放到/usr/bin目录,便于直接执行该命令

屏幕快照 2018-10-31 22.29.48

codesign命令

//查看权限信息

$ codesign -d –entitlements - debugserver

//签名

$ codesign -f -s - –entitlements debugserver.entitlements debugserver

// 签名简写为

$ codesign -fs- –entitlements debugserver.entitlements debugserver

2.3 搭建debugserver环境

2.3.1 权限不足

1
2
3
4
5
6
//权限不足
5s:~ root# debugserver
-sh: /usr/bin/debugserver: Permission denied

//添加运行权限
5s:~ root# chmod +x /usr/bin/debugserver

2.3.2 附加到进程

debugserver Usage:
debugserver host:port [program-name program-arg1 program-arg2 …]
debugserver /path/file [program-name program-arg1 program-arg2 …]
debugserver host:port –attach=
debugserver /path/file –attach=
debugserver host:port –attach=<process_name>
debugserver /path/file –attach=<process_name>

1
debugserver *:端口号 -a 进程
  • *:端口号

    使用iPhone的某个端口号(不要使用保留端口号)启动debugserver服务

  • -a 进程

    app的进程id或者进程名称

此处,用端口号10086,附加到微信进程:

注意:此处端口号,之后要通Mac lldb建立连接。

1
2
3
5s:~ root# debugserver *:10086 -a WeChat
Attaching to process WeChat...
Listening to port 10086 for a connection from *...

如上,连接之后,iPhone 10086端口会监听是否有连接到该端口的程序。

2.3.3 lldb连接debugserver

  • 启动LLDB

    1
    $ lldb
Python 2.7错误排除
1
2
3
4
5
6
7
$ lldb                                                                             
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python/lldb/__init__.py", line 98, in <module>
import six
ImportError: No module named six
....

出现以上错误,尝试此方案

1
2
$ brew reinstall python@2
$ pip install six

假如pip遇到下面错误,尝试方案,我的当前系统10.14,Xcode 10:

1
2
3
4
Traceback (most recent call last):
File "/usr/local/bin/pip", line 11, in <module>
.....
ImportError: No module named zlib

通过下面方案解决:

  1. brew update
  2. 重新下载Command Line Tools for Xcode 10,安装;
  3. brew reinstall python@2

之后,再次启动,仍然遇到错误:

1
2
3
4
5
6
7
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/copy.py", line 52, in <module>
import weakref
File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/weakref.py", line 14, in <module>
from _weakref import (
ImportError: cannot import name _remove_dead_weakref

方案在这儿。

再次重启,完美!

  • 连接debugserver服务

    1
    (lldb) process connect connect://手机IP地址:debugserver服务端口号

    此处,由于上面开放的端口为10086,连接,需要稍等,输出如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    (lldb) process connect connect://192.168.1.6:10086
    Process 1844 stopped
    * thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
    frame #0: 0x0000000198420a40 libsystem_kernel.dylib`mach_msg_trap + 8
    libsystem_kernel.dylib`mach_msg_trap:
    -> 0x198420a40 <+8>: ret

    libsystem_kernel.dylib`mach_msg_overwrite_trap:
    0x198420a44 <+0>: mov x16, #-0x20
    0x198420a48 <+4>: svc #0x80
    0x198420a4c <+8>: ret
    Target 0: (WeChat) stopped.
  • 优化连接debugserver

    我们现在是通过WiFi连接,同样也可以通过USB连接。

    在我们之前的bfios-usb.sh脚本,脚本地址

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # This is a comment too
    echo '---------------------------------------------'
    echo 'SSH login'
    echo 'mac port 10010 connect to iPhone port 22'
    echo '---------------------------------------------'
    echo 'lldb connect debugserver by mac port 9999 to iPhone port 10086'
    echo '1. In iPhone: debugserver *:10086 -a App name'
    echo '2. In Mac: enter lldb'
    echo '3. In Mac: process connect connect://localhost:9999'
    echo '---------------------------------------------'
    iproxy 10010 22 & \
    iproxy 9999 10086

    现在,我们连接debugserver的命令修改为:

    1
    (lldb) process connect connect://localhost:9999
  • 继续运行程序

    1
    2
    (lldb) c
    Process 1844 resumin

现在,可以通过Mac上的lldb直接动态调试App了。

2.3.4 总结

  1. Mac调用脚本bfios-usb.sh通过USB连接手机;

  2. Mac调用脚本bfios-login.sh登录到手机;

  3. 在手机中,debugserver连接到运行中的app;

    1
    5s:~ root# debugserver *:10086 -a WeChat
  4. 在Mac上,lldb连接到debugserver:

    1
    2
    3
    $ lldb
    (lldb) process connect connect://localhost:9999
    (lldb) c
  5. 调试中…..

三、ASLR

​ Address Space Layout Randomization,地址空间布局随机化。

​ ASLR是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的的一种技术。

​ iOS4.3开始引入了ASLR技术

一个Mach-O文件整个地址布局分为三种:

  • Mach-O 文件地址布局:Mach-O不运行,不会映射到内存,在文件内的地址空间;
  • Mach-O 未使用ASLR的内存布局:Mach-O会运行,但映射到内存中的地址空间;
  • Mach-O 使用ASLR的内存布局:Mach-O运行,使用ASLR映射到内存中的地址空间;

关于Mach-O更多的信息,请参考Mach-O(一)结构Mach-O(二)内存分布

下面实例以微信的破壳头文件WeChat为例,并且以Segment来说明:

3.1 文件地址布局

文件地址布局,就是Mach-O文件中各个段的布局,其中一个Segment:

  • File Offset:段偏移地址
  • File Size:段大小

计算:下一个段地址File Offset = 上一个段Offset + File Size

我们通过MachOExplore来整理得到下表:

Segment File Offset File Size
__PAGEZERO 0x0 0x0
__TEXT 0x0 0x3910000
__DATA 0x3910000 0xCA8000
__LINKEDIT 0X45B8000 0x3AB210

计算__LINKEDIT Offset = __DATA Offset + __DATA Size

0X45B8000 = 0x3910000 + 0xCA8000

另外,Section之间的地址也是这种规律。

3.2 未使用ASLR的内存布局

​ 文件地址,是读取Segment的 File Offset以及File Size。

​ 那么内存布局,也就是我们说的虚拟内存布局,我们在开发中说的内存,都是经过系统调度分配的虚拟内存,系统将虚拟内存映射到真实的物理内存,这一步我们不用去管。

​ 未使用ASLR的内存布局,即是通过Mach-O文件中的,VM Address以及VM Size来确定其在内存中的位置。

  • VM Address:虚拟内存起始地址,
  • VM Size:虚拟内存中占据的大小

我们再次列表查看WeChat的内存布局:

Segment VM Address VM Size File Offset File Size
__PAGEZERO 0X0 0x100000000 0x0 0x0
__TEXT 0x100000000 0x3910000 0x0 0x3910000
__DATA 0x103910000 0xD48000 0x3910000 0xCA8000
__LINKEDIT 0x104658000 0x3AC000 0X45B8000 0x3AB210

据此表格,我们绘制出如下图:

unaslr_mach_o_address

针对上图,我们还有一些需要说明的点:

  • __PAGEZERO大小为0x100000000,是固定值;
    • arm64:0x100000000(8个0)
    • 非arm64:0x4000(3个0)
  • __TEXT段在加载内存之后,大小不变,函数代码存放在__TEXT段中;
    • 代码段的内存地址,就是LC_SEGMENT(__TEXT)中的VM Address
  • __DATA段在加载到内存中占用空间变大,是由于系统虚拟内存会边界对齐,以提高调度速率;
    • 全局变量等存放在__DATA段中
  • 可使用size -l -m -x WeChat来查看Mach-O的内存分布
  • 可使用vm_stat查看进程在运行时的内存分布

3.3 使用ASLR的内存布局

​ ASLR随机产生的Offset(偏移),此处为:0x3500

aslr_mach_o_address

  • 函数的内存地址

    函数的内存地址(VM Address) = File Offset + ASLR Offset + __PAGEZERO Size

  • Hopper、IDA中的地址都是未使用ASLR的VM Address

  • 通过image查看ASLR随机分配的地址空间:

    1
    (lldb) image list -f -o