mirror of
https://github.com/nganhkhoa/CTF-All-In-One.git
synced 2024-12-24 03:01:15 +07:00
add pin
This commit is contained in:
parent
e1fa80d37c
commit
40a141ec9b
@ -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/)
|
||||
|
||||
|
||||
合作和贡献
|
||||
---
|
||||
|
@ -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 基本操作
|
||||
|
268
doc/5.2_pin.md
268
doc/5.2_pin.md
@ -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
1
doc/5.3_angr.md
Normal file
@ -0,0 +1 @@
|
||||
# angr 二进制自动化分析
|
BIN
pic/5.2_pin_arch.png
Normal file
BIN
pic/5.2_pin_arch.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
Loading…
Reference in New Issue
Block a user