Mac调试工具LLDB和Frida
2024-06-12 本文已影响0人
CoderShmily
LLDB
// 运行app
lldb /Users/mac/Desktop/MacTest.app
lldb /Users/mac/Desktop/confuse_mac.app
// 2个打印的地址相差0x100000000 调试mac应用好像没用,直接用IDA Hopper显示的地址就行
// IDA官网的免费版本不支持arm (下载windows破解wine打包的网上IDA Pro 8.3版本)导致之前看到的函数地址和Hopper不一样
image list 打印的基地址: 0x102a94000
image list -o -f打印的地址: 0x002a94000
// 直接打印第1个镜像
po _dyld_get_image_vmaddr_slide(0)
// 默认是没运行 r运行
r
// 暂停
ctrl + c
// 继续运行
c
// 断点 -a的0x可以省略
b -[ViewController getString:]
b +[ViewController hello]
b [AFZLanguageDecemberSplints lowFileElectrodes:]
br s -a 100002e84
br s -a 0x100000000+0x100017458
// 条件断点
breakpoint set --name lowFileElectrodes:
breakpoint modify -c '(BOOL)objc_msgSend((id)$x2, @selector(isEqualToString:), @"token")'
breakpoint modify -c '(BOOL)objc_msgSend((id)$x2, @selector(containsString:), @"设备%d名称:%@ 到期时间:%@,允许版本:%@")'
// 列出所有断点
br list
// 删除断点 指定br list列出的序号可以删除
br delete 2
// 打印堆栈
bt
// frame select堆栈ID
// 展示当前作用域下的参数和局部变量:frame variable
//反汇编地址: dis -s +地址 当我们调试过程中遇到crash,我们可以通过:dis -s +地址 命令来反汇编地址,来排查导致crash的具体原因。
// 写入内存指令临时覆盖掉逻辑
register read x0
register write x0 0
po (char *)0x000060000374ca80
po $x0
// 每次断点都执行一下po $x0
target stop-hook add -o "po $x0"
hread step-over 、 next 、n 单步运行,把子函数当做整体一步执行
thread step-in 、step、 s 单步运行,遇到子函数会进入子函数
thread step-inst-over、 nexti、 ni 单步运行,把子函数当做整体一步执行
thread step-inst、 stepi 、si 单步运行,遇到子函数会进入子函数
thread step-out 、finish 直接执行完当前函数的所有代码,返回到上一个函数(遇到断点会卡
LLDB自定义打印方法和Block参数 脚本放到最后面
参考 自定义打印方法参数的LLDB命令和 lldb快速打印Objective-C方法中block参数的签名
- 若是自动加载,需要vim ~/.lldbinit,然后里面就输入以下命令
command script import ~/custom.py
- 若是临时加载,只需在LLDB模式下输入command script import ~/custom.py
// Debug是文件名,objargs_func是文件下边的方法名就是具体lldb参数实现,最后的objargs是lldb 环境敲得命令
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f Debug.objargs_func objargs')
LLDB插件Chisel
// Chisel是Facebook发布的一个lldb插件,能够帮助调试
使用说明 https://www.jianshu.com/p/290e81b632e6
brew install chisel
1. 找到fblldb.py:/usr/local/Cellar/chisel/版本号/libexec/fblldb.py
2. 找到~/.lldbinit文件,然后进行编辑,在末尾添加
command script import /usr/local/Cellar/chisel/1.8.1/libexec/fblldb.py
搜索字符串
// 查找文件中的字符串 查找指定目录及其子目录中的所有文件,并检查每个文件是否包含字符串 "用户到期",如果包含,则输出文件名
find /Users/mac/Desktop/confuse_mac.app/Contents -type f -exec sh -c 'strings "$1" | grep "用户到期" && echo "$1"' sh {} \;
// 查找文件 查找当前目录及其子目录中所有名字叫做 CoreRepairCore.framework 的文件或目录 将标准错误输出(文件描述符 2)重定向到 /dev/null,从而忽略并不打印权限不足的错误信息。
find ./ -name "CoreRepairCore.framework" 2>/dev/null
// 从指定目录中查找所有不是 .DS_Store 的文件,并将它们复制到 ./11 目录下
find ./ -d -type f -not -name .DS_Store -exec cp {} ./11 \;
恢复符号表
// 为了堆栈可以看到裁去符号表的方法名
// 原版的restore-symbol有问题 用https://github.com/HeiTanBc/restore-symbol 我自己也fork这个了 教程在readme有写
// 如果是FAT二进制 可以用lipo瘦身只要arm64 我的电脑M2 是arm64
lipo /Users/mac/Desktop/confuse_mac.app/Contents/MacOS/confuse_mac -thin arm64 -output confuse_mac_arm64
// restore-symbol clone的目录restore-symbol
./restore-symbol /Users/mac/Desktop/confuse_mac.app/Contents/MacOS/confuse_mac_arm64 -o /Users/mac/Desktop/confuse_mac.app/Contents/MacOS/confuse_mac_symbol
// 恢复符号表后要改名替换原来的confuse_mac二进制 然后confuse_mac.app无法打开的情况重新签名
sudo codesign --remove-signature "/Users/mac/Desktop/confuse_mac.app" && sudo codesign -f -s - --timestamp=none --all-architectures --deep "/Users/mac/Desktop/confuse_mac.app" && sudo xattr -cr "/Users/mac/Desktop/confuse_mac.app"
常用修改二进制
// return 0
6a 00 58 c3
// nop
90 90
Frida
frida-trace "confuse_mac" -m "*[AFZUserSecretariesHappen *]"
frida-trace "confuse_mac" -m "*[AFZLanguageDecemberSplints lowFileElectrodes:]"
frida-trace "confuse_mac" -m "*[AFZNetworkSuppliesQualifica lowFileElectrodes:]" -o run.txt // 输出日志到
frida-trace "confuse_mac" -m "*[* armsWindlassAnchor:]" -o run.txt
frida-trace "confuse_mac" -m "*[AFZLanguageDecemberSplints lowFileElectrodes:]" -m "*[* objectForKeyedSubscript:]" -m "*[* objectForKeyedSubscript:]" -o run.txt
打印堆栈 在onEnter函数插入打印
log('\tBacktrace:\n\t' + Thread.backtrace(this.context,Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n\t'));
var after = new ObjC.Object(retval); // 打印出来是个指针时,请用该方式转换后再打印
log(`after:${after}`);
方法调用时
onEnter(log, args, state) {
var self = new ObjC.Object(args[0]); // 当前对象
var method = args[1].readUtf8String(); // 当前方法名
log(`[${self.$className} ${method}]`);
var isData = false;
// 字符串
// var str = ObjC.classes.NSString.stringWithString_("hi wit!") // 对应的oc语法:NSString *str = [NSString stringWithString:@"hi with!"];
// args[2] = str // 修改入参
// array
// var
// 数组
// var array = ObjC.classes.NSMutableArray.array(); // 对应的oc语法:NSMutableArray array = [NSMutablearray array];
// array.addObject_("item1"); // 对应的oc语法:[array addObject:@"item1"];
// array.addObject_("item2"); // 对应的oc语法:[array addObject:@"item2"];
// args[2] = array; // 修改入参
// 字典
// var dictionary = ObjC.classes.NSMutableDictionary.dictionary(); // 对应的oc语法:NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
// dictionary.setObject_forKey_("value1", "key1"); // 对应的oc语法:[dictionary setObject:@"value1" forKey:@"key1"]
// dictionary.setObject_forKey_("value2", "key2"); // 对应的oc语法:[dictionary setObject:@"value2" forKey:@"key2"]
// args[2] = dictionary; // 修改入参
// 字节
var data = ObjC.classes.NSMutableData.data(); // 对应的oc语法:NSMutableData *data = [NSMutableData data];
var str = ObjC.classes.NSString.stringWithString_("hi wit!") // 获取一个字符串。 对应的oc语法:NSString *str = [NSString stringWithString:@"hi with!"];
var subData = str.dataUsingEncoding_(4); // 将str转换为data,编码为utf-8。对应的oc语法:NSData *subData = [str dataUsingEncoding:NSUTF8StringEncoding];
data.appendData_(subData); // 将subData添加到data。对应的oc语法:[data appendData:subData];
args[2] = data; // 修改入参
isData = true;
// 更多数据类型:https://developer.apple.com/documentation/foundation
var before = args[2];
// 注意,日志输出请直接使用log函数。不要使用console.log()
if (isData) {
// 打印byte对象
var after = new ObjC.Object(args[2]); // 打印NSData
var outValue = after.bytes().readUtf8String(after.length()) // 将data转换为string
log(`before:=${before}=`);
log(`after:=${outValue}=`);
} else {
// 打印字符串、数组、字段
var after = new ObjC.Object(args[2]); // 打印出来是个指针时,请用该方式转换后再打印
log(`before:=${before}=`);
log(`after:=${after}=`);
}
// 如果是自定义对象时,使用以上方法无法打印时,请使用以下方法:
// var customObj = new ObjC.Object(args[0]); // 自定义对象
// // 打印该对象所有属性
// var ivarList = customObj.$ivars;
// for (key in ivarList) {
// log(`key${key}=${ivarList[key]}=`);
// }
// // 打印该对象所有方法
// var methodList = customObj.$methods;
// for (var i=0; i<methodList.length; i++) {
// log(`method=${methodList[i]}=`);
// }
},
方法返回时
onLeave(log, retval, state) {
// 字符串
var str = ObjC.classes.NSString.stringWithString_("hi wit!") // 对应的oc语法:NSString *str = [NSString stringWithString:@"hi with!"];
retval.replace(str) // 修改返回值
var after = new ObjC.Object(retval); // 打印出来是个指针时,请用该方式转换后再打印
log(`before:=${retval}=`);
log(`after:=${after}=`);
// 其他数据类型,请往上看
}
LLDB自定义脚本
# zlldb_block.py lldb快速打印Objective-C方法中block参数的签名
import lldb
import optparse
import shlex
# https://everettjf.github.io/2020/02/11/print-block-in-lldb/
# github.com:everettjf/zlldb.git
###### Init ######
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f zlldb_block.cmd_ztest ztest')
debugger.HandleCommand('command script add -f zlldb_block.cmd_zdebug zdebug')
debugger.HandleCommand('command script add -f zlldb_block.cmd_zdoc zdoc')
debugger.HandleCommand('command script add -f zlldb_block.cmd_zpvc zpvc')
debugger.HandleCommand('command script add -f zlldb_block.cmd_zpview zpview')
debugger.HandleCommand('command script add -f zlldb_block.cmd_zp1 zp1')
debugger.HandleCommand('command script add -f zlldb_block.cmd_zp2 zp2')
debugger.HandleCommand('command script add -f zlldb_block.cmd_zp3 zp3')
debugger.HandleCommand('command script add -f zlldb_block.cmd_zp4 zp4')
debugger.HandleCommand('command script add -f zlldb_block.cmd_zp5 zp5')
debugger.HandleCommand('command script add -f zlldb_block.cmd_zmemory zmemory')
debugger.HandleCommand('command script add -f zlldb_block.cmd_zdis zdis')
debugger.HandleCommand('command script add -f zlldb_block.cmd_zblock zblock')
print('zlldb loaded')
###### Dev Help ######
def cmd_ztest(debugger, command, result, internal_dict):
print('zlldb test')
def cmd_zdebug(debugger, command, result, internal_dict):
import pdb; pdb.set_trace()
print('zdebug')
def cmd_zdoc(debugger, command, result, internal_dict):
import os
os.system("open https://lldb.llvm.org/python_reference/lldb.{}-class.html".format(command))
###### Util ######
def exec_expression(interpreter, expression, print_when_noresult=None):
res = lldb.SBCommandReturnObject()
interpreter.HandleCommand(expression, res)
if res.HasResult():
print(res.GetOutput())
else:
if print_when_noresult is not None:
print(print_when_noresult)
###### View / ViewController Print ######
def cmd_zpvc(debugger, command, result, internal_dict):
# expression -lobjc -O -- [UIViewController _printHierarchy]
res = lldb.SBCommandReturnObject()
interpreter = debugger.GetCommandInterpreter()
expression = 'expression -lobjc -O -- [UIViewController _printHierarchy]'
interpreter.HandleCommand(expression, res)
if res.HasResult():
print(res.GetOutput())
else:
print('No result')
def cmd_zpview(debugger, command, result, internal_dict):
# expression -lobjc -O -- [(id)[[UIApplication sharedApplication] keyWindow] recursiveDescription]
res = lldb.SBCommandReturnObject()
interpreter = debugger.GetCommandInterpreter()
expression = 'expression -lobjc -O -- [(id)[[UIApplication sharedApplication] keyWindow] recursiveDescription]'
interpreter.HandleCommand(expression, res)
if res.HasResult():
print(res.GetOutput())
else:
print('No result')
###### Parameter Print ######
def cmd_zp1(debugger, command, result, internal_dict):
interpreter = debugger.GetCommandInterpreter()
exec_expression(interpreter, 'expression -lobjc -O -- (id)$arg1', 'no result for arg1')
def cmd_zp2(debugger, command, result, internal_dict):
interpreter = debugger.GetCommandInterpreter()
exec_expression(interpreter, 'expression -lobjc -O -- (id)$arg1', 'no result for arg1')
exec_expression(interpreter, 'expression -O -- (char*)$arg2', 'no result for arg2')
def cmd_zp3(debugger, command, result, internal_dict):
interpreter = debugger.GetCommandInterpreter()
exec_expression(interpreter, 'expression -lobjc -O -- (id)$arg1', 'no result for arg1')
exec_expression(interpreter, 'expression -O -- (char*)$arg2', 'no result for arg2')
exec_expression(interpreter, 'expression -lobjc -O -- (id)$arg3', 'no result for arg3')
def cmd_zp4(debugger, command, result, internal_dict):
interpreter = debugger.GetCommandInterpreter()
exec_expression(interpreter, 'expression -lobjc -O -- (id)$arg1', 'no result for arg1')
exec_expression(interpreter, 'expression -O -- (char*)$arg2', 'no result for arg2')
exec_expression(interpreter, 'expression -lobjc -O -- (id)$arg3', 'no result for arg3')
exec_expression(interpreter, 'expression -lobjc -O -- (id)$arg4', 'no result for arg4')
def cmd_zp5(debugger, command, result, internal_dict):
interpreter = debugger.GetCommandInterpreter()
exec_expression(interpreter, 'expression -lobjc -O -- (id)$arg1', 'no result for arg1')
exec_expression(interpreter, 'expression -O -- (char*)$arg2', 'no result for arg2')
exec_expression(interpreter, 'expression -lobjc -O -- (id)$arg3', 'no result for arg3')
exec_expression(interpreter, 'expression -lobjc -O -- (id)$arg4', 'no result for arg4')
exec_expression(interpreter, 'expression -lobjc -O -- (id)$arg5', 'no result for arg5')
###### Memory Print ######
def cmd_zmemory(debugger, command, result, internal_dict):
cmd_args = shlex.split(command)
usage = "usage: %prog <address> <size=8>"
parser = optparse.OptionParser(prog='zmemory', usage=usage)
try:
(options, args) = parser.parse_args(cmd_args)
except:
print("error parse parameter")
return
if len(args) == 0:
print("You need to specify the name of a variable or an address")
return
address = int(args[0],0)
size = 8
if len(args) == 2:
size = int(args[1],0)
print("address: 0x%x" % (address))
print("size: 0x%x" % (size))
interpreter = debugger.GetCommandInterpreter()
exec_expression(interpreter, 'memory read --size %d --format x 0x%x' % (size, address), 'no result')
###### Disassemble ######
def cmd_zdis(debugger, command, result, internal_dict):
cmd_args = shlex.split(command)
usage = "usage: %prog <address> <size=8>"
parser = optparse.OptionParser(prog='zmemory', usage=usage)
try:
(options, args) = parser.parse_args(cmd_args)
except:
print("error parse parameter")
return
if len(args) == 0:
print("You need to specify the name of a variable or an address")
return
address = int(args[0],0)
instruction_count = 20
if len(args) == 2:
instruction_count = int(args[1],0)
print("address: 0x%x" % (address))
print("instruction_count: %d" % (instruction_count))
interpreter = debugger.GetCommandInterpreter()
disass_cmd = "disassemble --start-address 0x%x -c %d" %(address, instruction_count)
exec_expression(interpreter, disass_cmd, 'no result')
###### Block ######
'''
struct Block_literal_1 {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 {
unsigned long int reserved;
unsigned long int size;
void (*copy_helper)(void *dst, void *src);
void (*dispose_helper)(void *src);
const char *signature;
} *descriptor;
};
'''
def zblock_print_block_signature(debugger, target, process, block_address):
pointer_size = 8 if zblock_arch_for_target_is_64bit(target) else 4
# print("pointer size = {0}".format(pointer_size))
# print("block address = %x"%(block_address))
flags_address = block_address + pointer_size # The `flags` integer is after a pointer in the struct
flags_error = lldb.SBError()
flags = process.ReadUnsignedFromMemory(flags_address, 4, flags_error)
if not flags_error.Success():
print("Could not retrieve the block flags")
return
block_has_signature = ((flags & (1 << 30)) != 0) # BLOCK_HAS_SIGNATURE = (1 << 30)
block_has_copy_dispose_helpers = ((flags & (1 << 25)) != 0) # BLOCK_HAS_COPY_DISPOSE = (1 << 25)
if not block_has_signature:
print("The block does not have a signature")
return
block_descriptor_address = block_address + 2 * 4 + 2 * pointer_size # The block descriptor struct pointer is after 2 pointers and 2 int in the struct
block_descriptor_error = lldb.SBError()
block_descriptor = process.ReadPointerFromMemory(block_descriptor_address, block_descriptor_error)
if not block_descriptor_error.Success():
print("Could not read the block descriptor struct")
return
signature_address = block_descriptor + 2 * pointer_size # The signature is after 2 unsigned int in the descriptor struct
if block_has_copy_dispose_helpers:
signature_address += 2 * pointer_size # If there are a copy and dispose function pointers the signature
signature_pointer_error = lldb.SBError()
signature_pointer = process.ReadPointerFromMemory(signature_address, signature_pointer_error)
signature_error = lldb.SBError()
signature = process.ReadCStringFromMemory(signature_pointer, 255, signature_error)
if not signature_error.Success():
print("Could not retrieve the signature")
return
print("Signature Address: 0x%x" %(signature_address))
print("Signature String: %s" %(signature))
escaped_signature = signature.replace('"', '\\"')
method_signature_cmd = 'po [NSMethodSignature signatureWithObjCTypes:"' + escaped_signature + '"]'
debugger.HandleCommand(method_signature_cmd)
docurl = 'https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html'
print('Type Encodings Ref: %s' % (docurl))
def zblock_disass_block_invoke_function(debugger, target, process, block_address, instruction_count):
pointer_size = 8 if zblock_arch_for_target_is_64bit(target) else 4
invoke_function_address = block_address + pointer_size + 2 * 4 # The `invoke` function is after one pointer and 2 int in the struct
print("Invoke address: 0x%x" % (invoke_function_address))
invoke_function_error = lldb.SBError()
invoke_function_pointer = process.ReadPointerFromMemory(invoke_function_address, invoke_function_error)
if not invoke_function_error.Success():
print("Could not retrieve the block invoke function pointer")
return
disass_cmd = "disassemble --start-address " + str(invoke_function_pointer) + " -c " + str(instruction_count)
debugger.HandleCommand(disass_cmd)
def zblock_arch_for_target_is_64bit(target):
# like: x86_64h, x86_64
arch_64 = ['arm64', 'x86_64']
arch = target.GetTriple().split('-')[0]
for arch64_item in arch_64:
if arch in arch64_item:
return True
return False
def cmd_zblock(debugger, command, result, internal_dict):
cmd_args = shlex.split(command)
usage = "usage: %prog arg1 [--disass -d] [--number-instructions -n]"
parser = optparse.OptionParser(prog='zblock', usage=usage)
parser.add_option('-d', '--disass', action='store_true', dest='disass', default=False)
parser.add_option('-n', '--number-instructions', dest='numberinstructions', default=20)
try:
(options, args) = parser.parse_args(cmd_args)
except:
print("error parse parameter")
return
if len(args) == 0:
print("You need to specify the name of a variable or an address")
return
number_instructions = options.numberinstructions
should_disass = options.disass
target = debugger.GetSelectedTarget()
process = target.GetProcess()
thread = process.GetSelectedThread()
frame = thread.GetSelectedFrame()
variable_arg = args[0]
address = int(variable_arg,0)
if address == 0:
print("invalid address")
return
print("Block address: 0x%x" % (address))
zblock_print_block_signature(debugger, target, process, address)
if should_disass:
zblock_disass_block_invoke_function(debugger, target, process, address, number_instructions)
# lldb_debug_extension.py 自定义打印方法参数的LLDB命令
import lldb
import re
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f lldb_debug_extension.objargs_func args')
# 格式化输出
def __format_output(return_obj):
output = return_obj.GetOutput()
return None if not output else output.replace('\n', '').replace('\r', '')
# 打印各个参数
def __print_arg(interpreter, return_obj, sequence):
"""
打印函数的第几个参数,传入1打印第一个,以此类推
"""
register = '$x%s' % str(sequence + 1)
# 参数值
interpreter.HandleCommand('po %s' % register, return_obj)
arg_value = __format_output(return_obj)
# 参数类型
interpreter.HandleCommand('po [%s class]' % register, return_obj)
arg_type: str = __format_output(return_obj)
# 不是oc对象类型,是基本数据类型
if not arg_type:
interpreter.HandleCommand('p %s' % register, return_obj)
p1 = re.compile(r'[(](.*?)[)]', re.S) # 提取括号里面的参数类型
arr = re.findall(p1, __format_output(return_obj))
arg_type = arr[0]
print("第%s个参数:【类型:%s】【值:%s】" % (sequence, arg_type, arg_value))
return
# 是oc对象类型,判断是不是block类型
interpreter.HandleCommand('po (BOOL)[%s isKindOfClass: [NSBlock class]]' % register, return_obj)
is_block = __format_output(return_obj)
if not is_block == 'YES': # 如果不是block
print("第%s个参数:【类型:%s】【值:%s】" % (sequence, arg_type, arg_value))
return
# block的处理方式
interpreter.HandleCommand('x/4xg %s' % register, return_obj)
descriptor_address = __format_output(return_obj).split(' ')[-1]
offset = 3 if 'Global' in arg_type else 5
interpreter.HandleCommand('x/%sxg %s' % (str(offset),descriptor_address), return_obj)
sign_address = __format_output(return_obj).split(' ')[-1]
interpreter.HandleCommand('x/s %s' % sign_address, return_obj)
sign = __format_output(return_obj).split(' ')[1]
print("第%s个参数:【类型:%s】【签名:%s】" % (sequence, arg_type, sign))
# 定义打印参数的函数
def objargs_func(debugger, command, exe_ctx, result, internal_dict):
"""
人性化的方式打印objc_msgSend的各个参数
"""
interpreter = debugger.GetCommandInterpreter()
# 保存结果
return_obj = lldb.SBCommandReturnObject()
# 处理命令
interpreter.HandleCommand('po $x0', return_obj)
print("方法调用者:%s" % __format_output(return_obj))
interpreter.HandleCommand('x/s $x1', return_obj)
method_name: str = __format_output(return_obj).split(' ')[1][1:-1]
print("方法名:\t %s" % method_name)
args = method_name.split(':')[:-1]
if not len(args): # 如果没有参数
return
for index, arg in enumerate(args):
sequence = index + 1
__print_arg(interpreter, return_obj, sequence)