19.软件静态分析
常见分析套路
.1 行为分析 :先运行起来,到处点一点,找突破口
.2 资源分析 : 查看.nib文件,NibUnlocker ,BBEdit,这两款软件,都能打开nib,可以修改nib的尺寸,但是看不了响应事件.
BBEdit下载链接:http://www.pc6.com/mac/111181.html
.3 数据分析 : 未加入沙盒功能的软件来说,保存文件的方式通常有NSUserDefaults,KeyChain,CoreData
,自定义路径的数据文件.
备注: KeychainAccess 是使用KeyChain的第三方框架;CoreData数据保存在~/Library/Application Support/
包名;自定义数据可以尝试采用动态调试等方式来分析.
流量分析
tcpDump : 终端工具
WireShark /Charles/CocoaPacketAnalyzer
API分析
通过查看调用系统弹窗之类的,来搜索关键字,
IDA Pro /Hopper : 界面工具
还可以运用UltraEdit直接修改代码地址
segment_command_64定义
struct segment_command_64 { /* for 64-bit architectures */
uint32_t cmd; /* LC_SEGMENT_64 */
uint32_t cmdsize; /* includes sizeof section_64 structs */
char segname[16]; /* segment name */
uint64_t vmaddr; /* memory address of this segment */
uint64_t vmsize; /* memory size of this segment */
uint64_t fileoff; /* file offset of this segment */
uint64_t filesize; /* amount to map from the file */
vm_prot_t maxprot; /* maximum VM protection */
vm_prot_t initprot; /* initial VM protection */
uint32_t nsects; /* number of sections in segment */
uint32_t flags; /* flags */
};
vmaddr 与 vmsize 指定的就是段加载到内存中的地址与内容的大小,
fileoff字段是段数据在文件中的起始地址,虚拟地址到文件地址的换算方法如下:
fileaddr(代码的真实地址) = codevmaddr(代码虚拟地址) - vmaddr(段所在虚拟地址) + fileoff(文件偏移)
20.软件动态调试
DTrace: 属于命令行工具,功能很强大;
同样,它能够执行脚本,是它自己开发设计的 D语言,macOS自带的命令行工具,目录位于:/usr/sbin/dtrace
.
相比C语言,它有如下特性:
1.不支持循环,while/for
2.不支持if条件判断
3.不支持C语言函数定义,只能使用内置函数
4.不支持宏定义
5.变量定义与C不同,
6.无法进行危险的指针操作,
一个探测器子句由探测器说明 + 谓词 + 操作语句列表
三部分组成;
D语言介绍
sudo dtrace -l,可以打印所有语句,有40多万条,可以grep 查找自己想要的语句
sudo strace -l | grep exec-success
我摘抄了一部分,说明一下,第一列是命令ID,其他四列对应探测器说明语句的4个部分,没有的部分可直接空白,表示这部分可以匹配任意项.
444594 fbt mach_kernel mac_vnode_check_setextattr return
444595 fbt mach_kernel mac_vnode_check_setflags entry
444596 fbt mach_kernel mac_vnode_check_setflags return
444597 fbt mach_kernel mac_vnode_check_setmode entry
444598 fbt mach_kernel mac_vnode_check_setmode return
变量
D语言支持全局变量,子句局部变量,线程局部变量.
1.全局变量定义很简单,
2.局部变量:
3.线程局部变量和局部变量类似,只是把this
换成self
即可
套路说明:
proc
是固定写法,
两个探测器说明语句之前用:
分隔,
如果想中间匹配所有内容就写成::
,
谓词的格式: /execname == "Calculator"/
,谓词就是判定条件,相当于C中的if
{ }
是条件成功时执行的代码,
说的直白点就是四列探测器说明语句组合 ,再加个条件判断 和事件响应.
//按步骤操作,总失败
yangpei1@localhost Desktop % sudo dtrace -n 'proc::posix_spawn:exec-success{printf( "execname: %s",execname);}'
Password:
dtrace: invalid probe specifier proc::posix_spawn:exec-success{printf( "execname: %s",execname);}: probe description proc::posix_spawn:exec-success does not match any probes
yangpei1@localhost Desktop %
后来我查找了对应的命令行
yangpei1@localhost Desktop % sudo dtrace -l |grep exec-success
1487 proc mach_kernel dtrace_thread_bootstrap exec-success
posix_spawn:exec-success这样搭配命令是错误的,
所以我把命令替换成如下:
yangpei1@localhost Desktop % sudo dtrace -n 'proc::dtrace_thread_bootstrap:exec-success{printf( "execname: %s",execname);}'
dtrace: description 'proc::dtrace_thread_bootstrap:exec-success' matched 1 probe
CPU ID FUNCTION:NAME
0 1487 dtrace_thread_bootstrap:exec-success execname: xpcproxy
4 1487 dtrace_thread_bootstrap:exec-success execname: xpcproxy
5 1487 dtrace_thread_bootstrap:exec-success execname: xpcproxy
4 1487 dtrace_thread_bootstrap:exec-success execname: Calculator
当我打开电脑的计算器的时候,终端打印了 Calculator,
所以上面那条命令实际是监听电脑打开的程序,然后打印在终端.
还有就是探测器指令不能随便组合.
参数传递
在D脚本中,参数通过$1
, $2
的方式来获取,跟lldb
里面的一样,
将如下脚本写入 trace_newApp文件
#!usr/sbin/dtrace -s
proc::posix_span:exec-success
/execname == $1/
{
printf("execname: %s",execname);
}
例如:执行trace_newApp脚本,在终端
sudo ./trace-newApp ' "Calculator" '//这个Calculator 就是传入参数,
单引号包含一层双引号,是shell会把双引号解释掉,如果确定某个参数是字符串,
可以用$$来使用它,将$1改为$$1,就不需要加两层引号了.
聚合
DTrace提供一组方便分析搜集到的数据的特殊函数,其他聚合函数一般用作性能分析.
内置函数与变量
21.调试器(GDB/LLDB)
GDB是老派调试器了,历史久远,支持的平台更多,
LLDB属于新秀:mac平台使用
官网贴出来:http://lldb.llvm.org/use/map.html
使用:
将某个方法转成汇编
di -n '-[ViewController btnClick]'
,默认是AT&T,
转成Intel命令: settings set target.x86-disassembly-flavor intel
lldb还有提示,汇编后面还有注释,简直爽到不行~
FaceBook开发的Chisel
软件,是针对lldb的脚本命令集
是重要 Xcode 调试工具 Chisel
安装: brew install chisel
Chisel的使用说明: https://github.com/facebook/chisel/wiki
都说它很牛逼,那它能干什么???
举个例子
当你在Xcode,到断点位置,
(lldb) hide self.nextBtn 直接在断点状态下,能隐藏/显示你的控件
(lldb) show self.nextBtn
(lldb) pvc 打印控制器的层级
(lldb) visualize imageView 预览一个ImageV
(lldb) pinternals 0x7f9779e0cc60 打印内部参数
(UIButton) $16 = {
UIControl = {
UIView = {
UIResponder = {
NSObject = {
isa = UIButton
}
}
}
}
}
调试工具 IDA
首先安装一款补丁插件keypatch
,用于在IDA中修改目标文件,它基于keystone实现,可以将单行或多行的汇编代码直接汇编成机器码.
在安装keypatch
之前先安装keystone
python -m install keystone-engine
下载keypatch.py : github 找到keystone-engine ,在目录里能找到.
22.ptrace(process trace)
ptrace 是Linux,BSD,UNIX系统上的调试API
PT_TRACE_ME
:此功能值应该在被调试进程中调用,用于通知系统本进程将被父进程跟踪调试.
PT_SIGEXC
:将UNIX信号转换成为Mach异常.
PT_READ_I, PT_READ_D, PT_READ_U
用于调试器进程读取被调试进程的内存,在macOS不可用
PT_WRITE_I, PT_WRITE_D, PT_WRITE_U
用于向北调试进程内存空间写入数据,在macOS不可用
PT_CONTINUE
通知系统让挂起的被调试进程继续执行.
PT_KILL
终止被调试进程
PT_STEP
使被调试进程单步执行一条指令,在macOS不可用
PT_ATTACH,PT_ATTACHEXC
附加指令到指定进程并开始调试
PT_DETACH
与被调试进程分离,不再调试指定进程
23.调试器中用到的Mach接口
1.task_for _pid
对进程进行操作必须要有任务端口号
,
2.mach_error _string
将错误码转成文字描述信息
3.mach_vm_read/mach_vm_read_overwrite
用于读取其他进程的内存,这个就可以替代之前ptrace所说的,PT_READ_I, PT_READ_D, PT_READ_U
4.mach_vm_write
写入到内存
5.mach_vm_protect
将address
到address+size
范围内的内存区域的保护级别设置为最高级别.
6.mach_vm_region_recurse
用于获取指定内存地址位置的虚拟内存区域的信息
7.thread_get_state
获取指定线程对应的寄存器状态,可用于代替ptrace读取寄存器状态的功能
8.thread_set_state
获取设置线程对应的寄存器状态,
9.thread_suspend/thread_resume
挂起指定线程和恢复指定线程的执行,每一个线程都有一个挂起计数器,每当对一个线程调用thread_suspend()
一次,便会对这个计数器值+1,而对线程调用thread_resume()
则会将计数器-1,只有在挂起计数器为0的情况下,线程才会被正常的调度和执行.
10.thread_terminate
强制结束线程
11.task_threads
枚举指定任务中的所有线程,并将枚举结果放到act_list链表中, act_list返回现成的数量
12.task_suspend/task_resume
挂起/恢复指定任务的执行
24.异常与Mach RPC/IPC
Mach IPC是一套进程间通信机制,与UNIX管道十分相似.
Mach RPC是基于Mach IPC实现的一套远程过程调用接口.
Mach作为一个内核,几乎所有对象之间的通信都是通过消息来实现的
macOS使用mach_msg函数来进行IPC消息的收发,一般有三个步骤
(1)申请一个用于收发消息的端口号,它有唯一的名称,可以与其他进程共享
(2)将端口传递给其他任务
(3)使用mach_msg函数进行消息收发
mach_port_allocate
:用于常见一个新的端口
mach_port_insert_right
:为新的异常端口添加接收消息的权限.
设置异常端口参数说明(代码在xnu源码的task.defs文件)
/*
* Set an exception handler for a task on one or more exception types.
* These handlers are invoked for all threads in the task if there are
* no thread-specific exception handlers or those handlers returned an
* error.
*/
routine task_set_exception_ports(
task : task_t; 设置异常端口的任务
exception_mask : exception_mask_t; 报错原因,枚举类型
new_port : mach_port_t; 新创建的端口
behavior : exception_behavior_t;异常需要生成的信息
new_flavor : thread_state_flavor_t); 传递给异常处理程序的线程状态所包含的信息
下列代码,既不会因为访问空指针而崩溃,也没有运行到结束,
因为设置了一个新的异常处理端口,替换了默认的异常处理端口,所以程序会一直处于挂起的状态,等待处理结果的返回.
/
// main.m
// Mach_test
//
// Created by 易家杨 on 2020/9/27.
// Copyright © 2020 易家杨. All rights reserved.
//
#import
#import
#import
#import
mach_port_t exc_port;
void setup_exc_port()
{
kern_return_t kr;
//创建一个端口用于接收异常消息
kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &exc_port);
if (kr != KERN_SUCCESS) {
fprintf(stderr, "mach_port_allocate 失败,错误信息: %s",mach_error_string(kr));
exit(1);
}
//为端口添加MACH_MSG_TYPE_MAKE_SEND权限
kr = mach_port_insert_right(mach_task_self(), exc_port, exc_port, MACH_MSG_TYPE_MAKE_SEND);
if (kr != KERN_SUCCESS) {
fprintf(stderr, "mach_port_insert_right 失败,错误信息: %s",mach_error_string(kr));
exit(1);
}
//为当前任务设置异常端口
kr = task_set_exception_ports(mach_task_self(),
EXC_MASK_ALL,
exc_port,
EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES,
THREAD_STATE_NONE);
if (kr != KERN_SUCCESS) {
fprintf(stderr, "task_set_exception_ports 失败,错误信息: %s",mach_error_string(kr));
exit(1);
}
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
setup_exc_port();
printf("完成异常端口的设置,执行处罚异常的代码\n");
int *a = NULL;
*a = 1;
printf("这句话不会被输出");
NSLog(@"Hello, World!");
}
return 0;
}
//打印结果
完成异常端口的设置,执行处罚异常的代码