<> 三

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语言脚本

变量

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提供一组方便分析搜集到的数据的特殊函数,其他聚合函数一般用作性能分析.

内置函数与变量

image.png

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
addressaddress+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;
}
//打印结果
完成异常端口的设置,执行处罚异常的代码

你可能感兴趣的:(<> 三)