lldb python 脚本扩展之超级断点增强版
引言
不知道各位在逆向ios用到lldb的时候是啥感觉,反正我是服了。没界面到算了,各种跳转和断点用起来不知道有多麻烦。所以我尽量用静态分析。不过越往后,越发现,光静态实在是不行。
- ios不知道有没有动态解密一说,就是把mach-o加密的部分在加载的时候再解密。android和windows上有一种方式叫做dump内存,可以把加载后解密的部分保存下来,但我在ios上用lldb memory read dump的时候发现限制了1024字节,后来又想把一堆1024字节拼起来,有发现不是所有的地址都能读。于是乎,我怀疑,ios内存读写存在限制。不知是否?有明白的希望留言告知。
- 网上有个python lldb的超级断点的脚本,本工具经该工具改编而来,原作者不详,大家可以查下。
- ios和window汇编上有个说法上的不同,模块的地址ios上叫ASLR偏移,而windows上叫基地址。我们反编译工具里的地址,ios叫做基地址,windows上叫偏移offset.好像正好相反,文中按照ios本来的叫法说明。
lldb 断点之困
lldb下断点之前都有一个偏移查找的过程,“image list -o -f”。其实其他平台也类是,超级断点这个工具的核心就是把模块偏移寻找的过程自动化,让我们只需要输入基地址就可以在目标地址下断点的脚本。当然这一切的前提是,lldb预留了python的接口,否则就悲催了。
简单介绍下代码:
def get_ASLR():
# 获取‘image list -o’命令的返回结果
interpreter = lldb.debugger.GetCommandInterpreter()
returnObject = lldb.SBCommandReturnObject()
interpreter.HandleCommand('image list -o', returnObject)
output = returnObject.GetOutput();
# 正则匹配出第一个0x开头的16进制地址
match = re.search(r'(1\]\s)(0x[0-9a-fA-F]+)', output)
if match:
return match.group(2)
else:
return None
代码很简单,通过接口执行lldb命令得到返回的数据,正则匹配出用户模块偏移量。
def sbr(debugger, command, result, internal_dict):
#用户是否输入了地址参数
if not command:
print >>result, 'Please input the address!'
return
ASLR = get_ASLR()
if ASLR:
#如果找到了ASLR偏移,就设置断点
debugger.HandleCommand('br set -a "%s+%s"' % (ASLR, command))
else:
print >>result, 'ASLR not found!'
# And the initialization code to add your commands
同样利用接口把我们输入的基地址和偏移传入lldb执行命令。
用户态代码的锁定
这里到了我胡编乱造的时候,我在调试ios的发现,object-c反汇编后的代码有一个很大的特点,就是喜欢在汇编代码块里乱跳。可能是更object-c和arm的运行机制有关,导致我们的代码在用户模块里的过程很难捕捉到,一句话,单步走功能就是个坑。
为了解决这个问题,我在以上脚本的基础上,利用断点功能实现了两大扩展。
1.单步执行在本模块,坚决不跳到其他模块的单步走
2.多条指令一键式执行,只要输入执行指令数,一条命令立刻执行到位。同样执行过程锁定在同一个模块。
(你要想去其他模块,本身的命令就够了)
正真意义上的单步执行
首先,我们要获得当前指令的地址,很简单,“register read/x pc”
同样我们正则匹配出地址字符串返回。
def get_pc():
# 获取‘image list -o’命令的返回结果
interpreter = lldb.debugger.GetCommandInterpreter()
returnObject = lldb.SBCommandReturnObject()
interpreter.HandleCommand('register read/x pc', returnObject)
output = returnObject.GetOutput();
# 正则匹配出第一个0x开头的16进制地址
match = re.match(r'.+(0x[0-9a-fA-F]+)', output)
if match:
return match.group(1)
else:
return None
然后在下一条指令的本模块下一条地址的地方下断点,并执行到断点
ADDRESS = get_pc()
#增加断点
if not command:
if ADDRESS:
debugger.HandleCommand('br set -a "%s+4"' % (ADDRESS))
print 'br set success:"%s+4"'%ADDRESS
#执行到断点
debugger.HandleCommand('c')
NUM = getbrlistn()
执行完毕删除断点
NUM = getbrlistn()
#删除断点
if NUM:
debugger.HandleCommand('br dele %s' % (NUM))
print "br dele success:%s"%NUM
else:
print >>result, 'br list number not found!'
else:
这里还有个获取上一步断点并删除的操作。同样正则。
#得到最后断点的标号
def getbrlistn():
interpreter = lldb.debugger.GetCommandInterpreter()
returnObject = lldb.SBCommandReturnObject()
interpreter.HandleCommand('br list', returnObject)
output = returnObject.GetOutput();
# 正则匹配出所有的断点代号
match = re.findall(r'(^\d{1,4})(:\saddress)', output,re.M)
if match:
num=len(match)
strlast=match[num-1]
brnum=strlast[0]
#print brnum
return brnum
else:
return None
# Super breakpoint
单步基础上的多步执行
基本和单步执行的代码一样,不同之处在于我们需要传入执行步数,command.并转换到响应的地址下断点。
offby=int(command,16)*4
if ADDRESS:
debugger.HandleCommand('br set -a "%s+%x"' % (ADDRESS,offby))
print 'br set success:"%s+%x"'%(ADDRESS,offby)
#执行到断点
debugger.HandleCommand('c')
然后就是执行到断点和删除断点。
获得静态编译的地址
同样的,我们执行到某个位置后想在ida里静态查看下相应的代码,也给他自动化一下。
def idaposi(debugger, command, result, internal_dict):
ASLR=get_ASLR()
PCADD=get_pc()
print "ASLR:",ASLR
print "trueADDR:",PCADD
if (ASLR!=None)&(PCADD!=None):
#print ASLR
#print PCADD
addr=int(PCADD,16)-int(ASLR,16)
print "idaposition_address:ox%x"%addr
else:
print "get info error"
这下齐全了。各位可以自己敲一下,把代码改成这样就能用了。
完整代码在下面。
#!/usr/bin/python
#coding:utf-8
'''
执行以下脚本导入超级断点工具
(lldb) command script import ~/workspace/3-lldb/subr.py
The "sbr" python command has been installed and is ready for use.
(lldb)
输出安装成功,只需就收一个地址就可工作
subr 地址
(lldb) br delete
About to delete all breakpoints, do you want to do that?: [Y/n] y
All breakpoints removed. (1 breakpoint)
(lldb) subr 0x00000001000093dd
Breakpoint 2: where = Calculator`___lldb_unnamed_function161$$Calculator, address = 0x000000010cb033dd
(lldb)
对于经常使用的脚本,可以在lldb的初始化文件里添加命令加载脚本,启动自定义的命令
修改~/.lldbinit文件,在文件里加入一行
command script import ~/sbr.py
'''
import lldb
import commands
import optparse
import shlex
import re
# 获取ASLR的偏移地址
def get_ASLR():
# 获取‘image list -o’命令的返回结果
interpreter = lldb.debugger.GetCommandInterpreter()
returnObject = lldb.SBCommandReturnObject()
interpreter.HandleCommand('image list -o', returnObject)
output = returnObject.GetOutput();
# 正则匹配出第一个0x开头的16进制地址
match = re.search(r'(1\]\s)(0x[0-9a-fA-F]+)', output)
if match:
return match.group(2)
else:
return None
def get_pc():
# 获取‘image list -o’命令的返回结果
interpreter = lldb.debugger.GetCommandInterpreter()
returnObject = lldb.SBCommandReturnObject()
interpreter.HandleCommand('register read/x pc', returnObject)
output = returnObject.GetOutput();
# 正则匹配出第一个0x开头的16进制地址
match = re.match(r'.+(0x[0-9a-fA-F]+)', output)
if match:
return match.group(1)
else:
return None
#得到最后断点的标号
def getbrlistn():
interpreter = lldb.debugger.GetCommandInterpreter()
returnObject = lldb.SBCommandReturnObject()
interpreter.HandleCommand('br list', returnObject)
output = returnObject.GetOutput();
# 正则匹配出所有的断点代号
match = re.findall(r'(^\d{1,4})(:\saddress)', output,re.M)
if match:
num=len(match)
strlast=match[num-1]
brnum=strlast[0]
#print brnum
return brnum
else:
return None
# Super breakpoint
def sni(debugger, command, result, internal_dict):
ADDRESS = get_pc()
#增加断点
if not command:
if ADDRESS:
debugger.HandleCommand('br set -a "%s+4"' % (ADDRESS))
print 'br set success:"%s+4"'%ADDRESS
#执行到断点
debugger.HandleCommand('c')
NUM = getbrlistn()
#删除断点
if NUM:
debugger.HandleCommand('br dele %s' % (NUM))
print "br dele success:%s"%NUM
else:
print >>result, 'br list number not found!'
else:
print >>result, 'ADDRESS not found!'
else:
offby=int(command,16)*4
if ADDRESS:
debugger.HandleCommand('br set -a "%s+%x"' % (ADDRESS,offby))
print 'br set success:"%s+%x"'%(ADDRESS,offby)
#执行到断点
debugger.HandleCommand('c')
NUM = getbrlistn()
#删除断点
if NUM:
debugger.HandleCommand('br dele %s' % (NUM))
print "br dele success:%s"%NUM
else:
print >>result, 'br list number not found!'
else:
print >>result, 'ADDRESS not found!'
def idaposi(debugger, command, result, internal_dict):
ASLR=get_ASLR()
PCADD=get_pc()
print "ASLR:",ASLR
print "trueADDR:",PCADD
if (ASLR!=None)&(PCADD!=None):
#print ASLR
#print PCADD
addr=int(PCADD,16)-int(ASLR,16)
print "idaposition_address:ox%x"%addr
else:
print "get info error"
def sni2(debugger, command, result, internal_dict):
NUM = getbrlistn()
#删除断点
if NUM:
#print "%d"%NUM
debugger.HandleCommand('br dele %s' % (NUM))
print NUM
else:
print >>result, 'br list number not found!'
# And the initialization code to add your commands
# Super breakpoint
def sbr(debugger, command, result, internal_dict):
#用户是否输入了地址参数
if not command:
print >>result, 'Please input the address!'
return
ASLR = get_ASLR()
if ASLR:
#如果找到了ASLR偏移,就设置断点
debugger.HandleCommand('br set -a "%s+%s"' % (ASLR, command))
else:
print >>result, 'ASLR not found!'
# And the initialization code to add your commands
def __lldb_init_module(debugger, internal_dict):
# 'command script add sbr' : 给lldb增加一个'sbr'命令
# '-f sbr.sbr' : ¸该命令调用了sbr文件的sbr
debugger.HandleCommand('command script add subr -f subr.sbr')
debugger.HandleCommand('command script add sni -f subr.sni')
debugger.HandleCommand('command script add idap -f subr.idaposi')
print 'The "sbr" python command has been installed and is ready for use.'