新博客
@[toc]
前言
- Hopper
是一种适用于 OS X 和 Linux 的逆向工程工具,可以用于反汇编、反编译和调试 32位/64位英特尔处理器的 Mac、Linux、Windows 和 iOS 可执行程序!
hopper常用的操作:直接修改汇编代码 (在菜单Modify - Assemble Instruction 进行汇编代码的修改)
- LLDB是Low Level Debugger的简称,
在iOS开发的调试中LLDB是经常使用的,LLDB是Xcode内置的动态调试工具。使用LLDB可以动态的调试你的应用程序,如果你不做其他的额外处理,因为debugserver缺少task_for_pid权限,所以你只能使用LLDB来调试你自己的App。
那么本篇博客中就要使用LLDB来调试从AppStore下载安装的App,并且结合着Hopper来分析第三方App内部的结构。
LLDB与Hopper的结合,会让你看到不一样的东西.
在上篇博客《iOS逆向工程之脱壳》 我们已经给微信进行可脱壳处理,所以使用Hopper进行处理是没有问题的。
此部分我们就要将Hopper与LLDB结合在一起发挥其双剑合璧的作用。该部分也算是本篇博客中实战的一部分。
I、查看WeChat线程的模块偏移后基地址
1.1 基础概念
- 模块基地址
`模块在内存中的起始地址----模块基地址
- ASLR偏移
ASLR偏移 ---- 虚拟内存起始地址与模块基地址的偏移量
- lldb命令的格式如下 :
<noun> <verb> [-options [option-value]] [argument [argument..]]
1.2 查看WeChat线程的模块偏移后基地址
LLDB连接上debugserver后,我们首先使用下方的命令来查看当前进程中的所有模块。从这些输出信息中我们能找到“WeChat”这个进程在虚拟内存相对于模块基地址的偏移量。
(lldb) image list -o -f
[ 0] 0x00082000 /private/var/mobile/Containers/Bundle/Application/01ECB9D1-858D-4BC6-90CE-922942460859/WeChat.app/WeChat(0x0000000000086000)
第一个就是“WeChat”程序的相关信息
左边0x00082000就是ASLR偏移量(随机偏移量);ASLR偏移量其实就是虚拟内存的启始地址,相对于模块基地址的偏移量。
右边0x0000000000086000的地址就是偏移后的地址。
从输出结果我们可以知道:ASLR偏移量 = 0x00082000, 模块偏移后基地址 = 0x0000000000086000
下方是使用Hopper打开的解密后的微信的安装包,其起始地址从下图中我们可以看出是0x4000, 这个地址就是模块偏移前的地址,也就是模块在虚拟内存中的起始地址。从Hopper中我们可以知道:模块偏移前的基地址=0x4000
从上面两组数据我们可以得出:
模块偏移后的基地址(0x0000000000086000)= ASLR偏移量(0x00082000)+ 模块偏移前基地址(0x4000)
上面这个公式是尤为重要的,因为Hopper中显示的都是“ 模块偏移前基地址”,而LLDB要操作的都是“模块偏移后的基地址”。
所以从Hopper到LLDB,我们要做一个地址偏移量的转换。这个会多次用到。
当然,有一点需要注意的是Hopper与LLDB所选择的AMR架构的位数得一致,要么是32位,要么都是64位,如果位数不匹配的话,那么计算出来的内存地址肯定是不对的。
II、使用LLDB给微信登录添加断点的例子
将断点添加在登录页面的初始化方法中
分析:
首先得知道手机号“登录”视图控制器的内存地址,然后才可以使用LLDB添加断点。
使用dump出来的头文件,或者使用hopper
WCAccountPhoneLoginControlLogic
- (id)initWithData:(id)arg1;
下方截图中的地址就是“initWithData:”方法偏移前的基地址。根据上面的公式我们很容易就可以计算出该方法“偏移后的基地址”也就是真正的内存地址。算法如下所示:
initWithData内存地址 = 0x01ba1c7e + 0x00082000(ALSR偏移) = 0x01c23c7e
III、常用命令
//0x000000015bd0ff80等是某个虚拟地址
po 0x000000015bd0ff80
mem read 0x000000015bd0ff80 ;用来取内存查看内存
watchpoint set expression -w read_write -- 0x00000002812cdb80 ;用来设置watchpoint,来观察某个内存的读写情况;
watchpoint set expression -w write -- 0x0000000281281e00
dis -a 0x000000015bd0ff80; //反汇编某个地址,如果该地址是个函数符号,则可以看到对应汇编代码;
x 0x000000015bd0ff80 ; //这个命令x/n比较有意思,查看对应地址往后n字节的内存
breakpoint set -a 0x00000001ca9abc0c ;命令行的方式设置断点地址,特别有利于设置非符号断点,可直接根据crash地址计算得到当前的运行地址然后直接设置断点.
im li ;这个命令把当前所有加载的binary images的地址给出来,可用来结合进行动态分析;
thread return 0;直接当前函数调用return或return返回值0
image lookup -a 0x202952620; 符号查找
3.1、添加断点
使用下述命令,给上述地址添加断点。断点添加后,点击登录按钮就会跳转到“手机号登录”页面就会执行该断点,下方截图的红框中就是“断点”执行后的效果。从下方截图中我们可以看出该断点的编号是1,Breakpoint后方就是断点编号,该编号会在操作断点是会用到,下方会给出实例。
br s -a 0x01C23C7E
如果发现错误,立即重现查看下
(lldb) image list -o -f|grep 'WeChat'
[ 0] 0x00001000 /private/var/mobile/Containers/Bundle/Application/5CC4B194-DDC5-442F-A117-2D135C3FCEA9/WeChat.app/WeChat(0x0000000000005000)
此时的ALSR偏移 = 0x00001000;
因此
initWithData内存地址 = 0x01ba1c7e + 0x00001000(ALSR偏移) = 0x01ba2c7e
br s -a 0x01ba2c7e
(lldb) br s -a 0x01ba2c7e
Breakpoint 1: where = WeChat`ClearDataItem::compareTime(std::__1::shared_ptr<ClearDataItem> const&, std::__1::shared_ptr<ClearDataItem> const&) + 4897562, address = 0x01ba2c7e
重新生成WeChat.decrypted 6.5.18
‘
(lldb) image list -o -f|grep 'WeChat'
[ 0] 0x00017000 /private/var/mobile/Containers/Bundle/Application/5CC4B194-DDC5-442F-A117-2D135C3FCEA9/WeChat.app/WeChat(0x000000000001b000)
initWithData内存地址 = 0x016dbf3a + 0x00017000(ALSR偏移) = 0x016f2f3a
br s -a 0x016f2f3a
(lldb) br s -a 0x000c3000+0x016dbf3a
Breakpoint 17: where = WeChat`__cxa_throw + 16426614, address = 0x0179ef3a
还是没有成功,待续
3.2、断点的单步执行(ni, si)
你可以通过nexti (简写:ni)和stepi (简写:si)来进行单步的调试。ni遇到跳转不会进入到跳转中去,而si则会跳转到相应的分支中去。
(lldb) ni
Process 609 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step over
frame #0: 0x327e6478 libsystem_kernel.dylib`mach_msg_trap + 24
libsystem_kernel.dylib`mach_msg_trap:
-> 0x327e6478 <+24>: bx lr
libsystem_kernel.dylib`mach_msg_overwrite_trap:
0x327e647c <+0>: mov r12, sp
0x327e6480 <+4>: push {r4, r5, r6, r8}
0x327e6484 <+8>: ldm r12, {r4, r5, r6, r8}
(lldb) si
Process 609 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into
frame #0: 0x327e626c libsystem_kernel.dylib`mach_msg + 40
libsystem_kernel.dylib`mach_msg:
-> 0x327e626c <+40>: cmp r0, #0x0
0x327e626e <+42>: beq 0x327e62be ; <+122>
0x327e6270 <+44>: str.w r10, [sp, #0x10]
0x327e6274 <+48>: tst.w r10, #0x40
3.3 放开执行该断点(c)
命令c可以执行该断点, 上面这种情况如果执行c命令,因为只有一个断点,该断点执行后,就会跳转到“手机号登陆页面”。
(lldb) c
Process 609 resuming
3.4断点的禁用和开启
上面也有提到,上述创建的断点的编号是1,我们要对该断点进行禁用和开启操作,具体命令如下所示:
br dis 1 -- 禁用(disable)编号为1的断点
br en 1 -- 启用(enable)编号为1的断点
br dis -- 禁用所有断点
br en -- 启用所有断点
(lldb) c
Process 609 resuming
(lldb) br dis 1
1 breakpoints disabled.
(lldb) br en 1
1 breakpoints enabled.
3.5、删除breakpoints
(lldb) br dis
All breakpoints disabled. (5 breakpoints)
(lldb) br del
About to delete all breakpoints, do you want to do that?: [Y/n] y
All breakpoints removed. (5 breakpoints)
br del 1 -- 删除(delete)编号为1的断点
3.6、 退出lldb
(lldb) br del
About to delete all breakpoints, do you want to do that?: [Y/n] y
All breakpoints removed. (1 breakpoint)
(lldb) exit
Quitting LLDB will kill one or more processes. Do you really want to proceed: [Y/n] y
IV、lldb 操作寄存器的值
4.1.输出寄存器的值(p, po)
我们使用$来访问某个寄存器中的值,并且使用p命令进行打印。下方就是通过p命令将r1寄存器中所存的内容进行打印,在打印之前将$r1进行类型转换,po命令则输出了Objective-C的对象,而p输出的是C语言类型的数据。如下所示:
(lldb) po $r0
(lldb) p (char *)$r1
(char *) $1 = 0x07000806 "\xffffffec\xffffff99"
我们还可以将一个地址所存放的值进行打印,下方这个命令就是输出了$sp指针所指的地址处所存放的值:
(lldb) p/x $sp
(unsigned int) $2 = 0x039a3c74
4.2.修改寄存器中的值
我们不仅可以查看某些寄存器中的值,而且可修改寄存器中的中,通过下述命令我们就可以修改指的寄存器中的值。
register write 寄存器 值
接过在Hopper中对登录模块的分析,发现“WCAccountManualAuthControlLogic”这个类中的“handleAuthResponse:”方法就是用来处理“登录认证响应”的方法。
也就是说“handleAuthResponse:”负责处理登录业务逻辑的网络响应,并且在这个函数的前边有一个比较(cmp r0, r1), 根据r0和r1的比较结果来进行跳转。
接下来我们要做的事情就是,在比较寄存器r0和r1中的值时我们要改变r1寄存器中的值,然后观察App的运行效果。下方这个截图是随便输入手机号和密码时所提示的内容。也就是正常的流程会弹出下方的框。
(lldb) image list -o -f|grep 'WeChat'
[ 0] 0x00012000 /private/var/mobile/Containers/Bundle/Application/5CC4B194-DDC5-442F-A117-2D135C3FCEA9/WeChat.app/WeChat(0x0000000000016000)
接下来我们要做的就是给
`(lldb) br s -a 0x00012000+0x13f087a
`(cmp)这个内存地址添加断点,然后去修改寄存器r1的值。
接下来我们先将r0和r1中的值进行打印$r0 ,$r1中的值改成$r0
V、总结
5.1 lldb 常用命令
lldb命令的格式如下 :
<noun> <verb> [-options [option-value]] [argument [argument..]]
- 连接server
process connect connect://127.0.0.1:12345
- 查看进程在虚拟内存相对于模块基地址的偏移量。
image list -o -f
- 只查看微信的进程
(lldb) image list -o -f|grep 'WeChat'
5.1.0 断点管理
- 简单地通过文件和行号进行断点 :
breakpoint set --file foo.c --line 12
breakpoint set -f foo.c -l 12
- 通过函数的名称来设置断点 :
breakpoint set --name foo
breakpoint set -n foo
- 可以同时使用 -n来设置多个函数的断点:
`breakpoint set -n foo -n bar
- `要为C++函数设置断点,使用method :
breakpoint set --method foo
breakpoint set -M foo
- 设置 OC里面selector的断点 :
breakpoint set --seletor alignLeftEdges:
breakpoint set -S alignLeftEdges:
- 也可以针对可执行的image设置断点 :
breakpoint set --shlib foo.dylib --name foo
breakpoint set -s foo.dylib -n foo
可以重复使用 --shlib来标记多个公共库。
- breakpoint简写为br :
breakpoint set -n "-[SKTGraphicView alignLeftEdges:]"
br s -n "-[SKTGraphicView alignLeftEdges:]"
(lldb) br list
No breakpoints currently set.
(lldb) br li
No breakpoints currently set.
使用 breakpoint delete id 删除断点,简写 br del, 使用breakpoint enable id 和 breakpoint disable id 来启用或禁用断点,
5.1.1 自定义alias
我们可以设置alias :
command alias bfl breakpoint set -f %1 -l %2
bfl foo.c 12
用户可以修改~/.lldbinit文件,以存储自己的快捷键。
5.1.2 流程控制
- process continue 取消暂停,继续执行函数 ,别名是 continue,简写是 c.
- thread step-over : 执行当前函数中的一行代码,但是不跳进函数。简写是 next ,n 。
- thread step-in : 跳进函数中, 简写 step,s 。
- thread step-out : 跳出函数,简写是 finish 。
- thread list : 列出线程列表
- thread backtrace : 列出线程堆栈。可以通过thread select 2切换线程。 thread backtrace all 输出所有线程堆栈
- thread return <RETURN EXPRESSION> : 直接返回当前函数, 可以设置返回值。
5.1.2.1 thread return
5.1.3 查看当前的断点的函数信息。
(lldb) frame info
frame #0: 0x0000000114ab4e76 libsystem_kernel.dylib`mach_msg_trap + 10
- 打印出view的所有层次信息
po [[UIApp keyWindow] recursiveDescription]
5.1.4 添加Action以在断点时,执行自定义事件
(lldb) breakpoint set -n isEven
Breakpoint 1: where = DebuggerDance`isEven + 16 at main.m:4, address = 0x00000001083b5d00
(lldb) breakpoint modify -c 'i == 99' 1
(lldb) breakpoint command add 1
Enter your debugger command(s). Type 'DONE' to end.
> p i
> DONE
- 添加action的触发条件
(lldb) breakpoint modify -c 'i == 99' 1
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。