This commit is contained in:
firmianay 2017-08-07 23:01:04 +08:00
parent e1fa80d37c
commit 40a141ec9b
5 changed files with 275 additions and 2 deletions

View File

@ -43,12 +43,15 @@
- [五、高级篇](doc/5_advanced.md)
- [5.1 Fuzz 测试](doc/5.1_fuzz.md)
- [5.2 Pin 动态二进制插桩](doc/5.2_pin.md)
- [5.3 angr 二进制自动化分析](doc/5.3_angr.md)
- [六、附录](doc/6_appendix.md)
- [6.1 更多 Linux 工具](doc/6.1_Linuxtools.md)
- [6.2 更多 Windows 工具](doc/6.2_wintools.md)
- [6.3 博客、文章和书籍](doc/6.3_books&blogs.md)
- [6.4 习题 write-up](doc/6.4_writeup.md)
- [6.5 Linux x86-64 系统调用表](http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/)
合作和贡献
---

View File

@ -10,7 +10,7 @@
## gdb 基本工作原理
gdb 通过系统调用 `ptrace` 来接管一个进程的执行。ptrace 系统调用提供了一种方法使得父进程可以观察和控制其它进程的执行检查和改变其核心映像以及寄存器。它主要用来实现断点调试和系统调用跟踪。ptrace 系统调用的原型如下:
```c
```
#include <sys/ptrace.h>
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
```
@ -18,7 +18,7 @@ long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
- **void *addr**:指示要监控的内存地址。
- **void *data**:存放读取出的或者要写入的数据。
- **enum __ptrace_request request**:决定了系统调用的功能,几个主要的选项:
- *PTRACE_TRACEME*:表示此进程将被父进程跟踪,任何信号(除了 SIGKILL都会暂停子进程接着阻塞于 `wait()` 等待的父进程被唤醒。子进程内部对 `exec()` 的调用将发出 `SIGTRAP` 信号,这可以让父进程在子进程新程序开始运行之前就完全控制它。
- *PTRACE_TRACEME*:表示此进程将被父进程跟踪,任何信号(除了 `SIGKILL`)都会暂停子进程,接着阻塞于 `wait()` 等待的父进程被唤醒。子进程内部对 `exec()` 的调用将发出 `SIGTRAP` 信号,这可以让父进程在子进程新程序开始运行之前就完全控制它。
- *PTRACE_ATTACH*attach 到一个指定的进程,使其成为当前进程跟踪的子进程,而子进程的行为等同于它进行了一次 PTRACE_TRACEME 操作。但需要注意的是,虽然当前进程成为被跟踪进程的父进程,但是子进程使用 `getppid()` 的到的仍将是其原始父进程的 pid。
- *PTRACE_CONT*:继续运行之前停止的子进程。可同时向子进程交付指定的信号。
@ -40,4 +40,5 @@ long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
#### 断点的实现
断点的功能是通过内核信号实现的,在 x86 架构上,内核向某个地址打入断点,实际上就是往该地址写入断点指令 `INT 3`,即 `0xCC`。目标程序运行到这条指令之后会触发 `SIGTRAP` 信号gdb 捕获这个信号,并根据目标程序当前停止的位置查询 gdb 维护的断点链表,若发现在该地址确实存在断点,则可判定为断点命中。
## gdb 基本操作

View File

@ -1 +1,269 @@
# Pin 动态二进制插桩
- [插桩技术](#插桩技术)
- [Pin 简介](#Pin-简介)
- [Pin 的基本用法](#Pin-的基本用法)
- [Pintool 示例分析](#Pintool-示例分析)
- [Pintool 编写](#Pintool-编写)
- [Pin 在 CTF 中的应用](#Pin-在-CTF-中的应用)
## 插桩技术
插桩技术是将额外的代码注入程序中以收集运行时的信息,可分为两种:
源代码插桩Source Code Instrumentation(SCI)):额外代码注入到程序源代码中。
示例:
```c
// 原始程序
void sci() {
int num = 0;
for (int i=0; i<100; ++i) {
num += 1;
if (i == 50) {
break;
}
}
printf("%d", num);
}
```
```c
// 插桩后的程序
char inst[5];
void sci() {
int num = 0;
inst[0] = 1;
for (int i=0; i<100; ++i) {
num += 1;
inst[1] = 1;
if (i == 50) {
inst[2] = 1;
break;
}
inst[3] = 1;
}
printf("%d", num);
inst[4] = 1;
}
```
二进制插桩Binary Instrumentation(BI)):额外代码注入到二进制可执行文件中。
- 静态二进制插桩:在程序执行前插入额外的代码和数据,生成一个永久改变的可执行文件。
- 动态二进制插桩:在程序运行时实时地插入额外代码和数据,对可执行文件没有任何永久改变。
以上面的函数 `sci` 生成的汇编为例:
原始汇编代码
```
sci:
pushl %ebp
movl %esp, %ebp
pushl %ebx
subl $20, %esp
call __x86.get_pc_thunk.ax
addl $_GLOBAL_OFFSET_TABLE_, %eax
movl $0, -16(%ebp)
movl $0, -12(%ebp)
jmp .L2
```
- 插入指令计数代码
```
sci:
counter++;
pushl %ebp
counter++;
movl %esp, %ebp
counter++;
pushl %ebx
counter++;
subl $20, %esp
counter++;
call __x86.get_pc_thunk.ax
counter++;
addl $_GLOBAL_OFFSET_TABLE_, %eax
counter++;
movl $0, -16(%ebp)
counter++;
movl $0, -12(%ebp)
counter++;
jmp .L2
```
- 插入指令跟踪代码
```
sci:
Print(ip)
pushl %ebp
Print(ip)
movl %esp, %ebp
Print(ip)
pushl %ebx
Print(ip)
subl $20, %esp
Print(ip)
call __x86.get_pc_thunk.ax
Print(ip)
addl $_GLOBAL_OFFSET_TABLE_, %eax
Print(ip)
movl $0, -16(%ebp)
Print(ip)
movl $0, -12(%ebp)
Print(ip)
jmp .L
```
## Pin 简介
Pin 是 Intel 公司研发的一个动态二进制插桩框架,可以在二进制程序运行过程中插入各种函数,以监控程序每一步的执行。
Pin 具有一下优点:
- 易用
- 使用动态插桩,不需要源代码、不需要重新编译和链接。
- 可扩展
- 提供了丰富的 API可以使用 C/C++ 编写插桩工具(被叫做 Pintools
- 多平台
- 支持 x86、x86-64、Itanium、Xscale
- Windows、Linux、OSX、Android
- 鲁棒性
- 支持插桩现实世界中的应用:数据库、浏览器等
- 支持插桩多线程应用
- 支持信号量
- 高效
- 在指令代码层面实现编译优化
## Pin 的基本结构和原理
Pin 是一个闭源的框架,由 Pin 和 Pintool 组成。Pin 内部提供 API用户使用 API 编写可以由 Pin 调用的动态链接库形式的插件,称为 Pintool。
![](../pic/5.2_pin_arch.png)
由图可以看出Pin 由进程级的虚拟机、代码缓存和提供给用户的插桩检测 API 组成。Pin 虚拟机包括 JIT(Just-In-Time) 编译器、模拟执行单元和代码调度三部分,其中核心部分为 JIT 编译器。当 Pin 将待插桩程序加载并获得控制权之后在调度器的协调下JIT 编译器负责对二进制文件中的指令进行插桩,动态编译后的代码即包含用户定义的插桩代码。编译后的代码保存在代码缓存中,经调度后交付运行。
程序运行时Pin 会拦截可执行代码的第一条指令并为后续指令序列生成新的代码新代码的生成即按照用户定义的插桩规则在原始指令的前后加入用户代码通过这些代码可以抛出运行时的各种信息。然后将控制权交给新生成的指令序列并在虚拟机中运行。当程序进入到新的分支时Pin 重新获得控制权并为新分支的指令序列生成新的代码。
通常 Pintool 中包含了两部分:
- 插桩代码Instrumentation code
- 在什么位置插入插桩代码
- 分析代码Analysis code
- 在选定的位置要执行的代码
Pintool 采用向 Pin 注册插桩回调函数的方式对每一个被插桩的代码段Pin 调用相应的插桩回调函数,观察需要产生的代码,检查它的静态属性,并决定是否需要以及插入分析函数的位置。分析函数会得到插桩函数传入的寄存器状态、内存读写地址、指令对象、指令类型等参数。
## Pin 的基本用法
Pin 的三种插桩模式:
- Instruction level(Ins)
- 在每一条原始程序指令前(或后)插入自己的代码,即调用这个函数。
- Function level(RTN)
- Pin 通过符号表信息来找到这些需要插入的位置,要使用这种模式的代码插入,首先得调用 pin 内置的初始化符号表函数,即 `PIN_InitSymbols()`
- Basic block level(BBL)
- 基本调用块级别的插入模式,只在 trace 的时候可用。
在 Pin 解压后的目录下,编译一个 Pintool首先在 `source/tools/` 目录中创建文件夹 `MyPintools`,将 `mypintool.cpp` 复制到 `source/tools/MyPintools` 目录下,然后 `make`
```
$ cp mypintools.cpp source/tools/MyPintools
$ cd source/tools/MyPintools
```
对于 32 位架构,使用 `TARGET=ia32`
```
$ make obj-ia32/mypintool.so TARGET=ia32
```
对于 64 位架构,使用 `TARGET=intel64`
```
$ make obj-intel64/mypintool.so TARGET=intel64
```
启动并插桩一个应用程序:
```
$ ../../../pin -t obj-intel64/mypintools.so -- application
```
其中 `pin` 是插桩引擎,由 Pin 的开发者提供;`pintool.so` 是插桩工具,由用户自己编写并编译。
绑定并插桩一个正在运行的程序:
```
$ ../../../pin -t obj-intel64/mypintools.so -pid 1234
```
## Pintool 示例分析
Pin 提供了一些 Pintool 的示例,下面我们分析一下用户手册中介绍的指令计数工具 inscount。
```c
#include <iostream>
#include <fstream>
#include "pin.H"
ofstream OutFile;
// The running count of instructions is kept here
// make it static to help the compiler optimize docount
static UINT64 icount = 0;
// This function is called before every instruction is executed
VOID docount() { icount++; }
// Pin calls this function every time a new instruction is encountered
VOID Instruction(INS ins, VOID *v)
{
// Insert a call to docount before every instruction, no arguments are passed
INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)docount, IARG_END);
}
KNOB<string> KnobOutputFile(KNOB_MODE_WRITEONCE, "pintool",
"o", "inscount.out", "specify output file name");
// This function is called when the application exits
VOID Fini(INT32 code, VOID *v)
{
// Write to a file since cout and cerr maybe closed by the application
OutFile.setf(ios::showbase);
OutFile << "Count " << icount << endl;
OutFile.close();
}
/* ===================================================================== */
/* Print Help Message */
/* ===================================================================== */
INT32 Usage()
{
cerr << "This tool counts the number of dynamic instructions executed" << endl;
cerr << endl << KNOB_BASE::StringKnobSummary() << endl;
return -1;
}
/* ===================================================================== */
/* Main */
/* ===================================================================== */
/* argc, argv are the entire command line: pin -t <toolname> -- ... */
/* ===================================================================== */
int main(int argc, char * argv[])
{
// Initialize pin
if (PIN_Init(argc, argv)) return Usage();
OutFile.open(KnobOutputFile.Value().c_str());
// Register Instruction to be called to instrument instructions
INS_AddInstrumentFunction(Instruction, 0);
// Register Fini to be called when the application exits
PIN_AddFiniFunction(Fini, 0);
// Start the program, never returns
PIN_StartProgram();
return 0;
}
```
执行流程如下:
- 在主函数 `main` 中:
- 初始化 `PIN_Init()`,注册指令粒度的回调函数 `INS_AddInstrumentFunction(Instruction, 0)`,被注册插桩函数名为 `Instruction`
- 注册完成函数(常用于最后输出结果)
- 启动 Pin 执行
- 在每条指令之前(`IPOINT_BEFORE`)执行分析函数 `docount()`,功能是对全局变量递增计数。
- 执行完成函数 `Fini()`,输出计数结果到文件。
## Pintool 编写
## Pin 在 CTF 中的应用

1
doc/5.3_angr.md Normal file
View File

@ -0,0 +1 @@
# angr 二进制自动化分析

BIN
pic/5.2_pin_arch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB