# 3.2.1 patch 二进制文件

* [什么是 patch](#什么是-patch)
* [手工 patch](#手工-patch)
* [使用工具 patch](#使用工具-patch)

## 什么是 patch

许多时候，我们不能获得程序源码，只能直接对二进制文件进行修改，这就是所谓的 patch，你可以使用十六进制编辑器直接修改文件的字节，也可以利用一些半自动化的工具。

patch 有很多种形式：

* patch 二进制文件（程序或库）
* 在内存里 patch（利用调试器）
* 预加载库替换原库文件中的函数
* triggers（hook 然后在运行时 patch）

## 手工 patch

手工 patch 自然会比较麻烦，但能让我们更好地理解一个二进制文件的构成，以及程序的链接和加载。有许多工具可以做到这一点，比如 xxd、dd、gdb、radare2 等等。

### xxd

```
$ echo 01: 01 02 03 04 05 06 07 08 | xxd -r - output
$ xxd -g1 output
00000000: 00 01 02 03 04 05 06 07 08                       .........
$ echo 04: 41 42 43 44 | xxd -r - output
$ xxd -g1 output
00000000: 00 01 02 03 41 42 43 44 08                       ....ABCD.
```

参数 `-r` 用于将 hexdump 转换成 binary。这里我们先创建一个 binary，然后将将其中几个字节改掉。

### radare2

一个简单的例子：

```c
#include<stdio.h>
void main() {
    printf("hello");
    puts("world");
}
```

```
$ gcc -no-pie patch.c
$ ./a.out
helloworld
```

下面通过计算函数偏移，我们将 `printf` 换成 `puts`：

```
[0x004004e0]> pdf @ main
            ;-- main:
/ (fcn) sym.main 36
|   sym.main ();
|              ; DATA XREF from 0x004004fd (entry0)
|           0x004005ca      55             push rbp
|           0x004005cb      4889e5         mov rbp, rsp
|           0x004005ce      488d3d9f0000.  lea rdi, str.hello          ; 0x400674 ; "hello"
|           0x004005d5      b800000000     mov eax, 0
|           0x004005da      e8f1feffff     call sym.imp.printf         ; int printf(const char *format)
|           0x004005df      488d3d940000.  lea rdi, str.world          ; 0x40067a ; "world"
|           0x004005e6      e8d5feffff     call sym.imp.puts           ; sym.imp.printf-0x10 ; int printf(const char *format)
|           0x004005eb      90             nop
|           0x004005ec      5d             pop rbp
\           0x004005ed      c3             ret
```

地址 `0x004005da` 处的语句是 `call sym.imp.printf`，其中机器码 `e8` 代表 `call`，所以 `sym.imp.printf` 的偏移是 `0xfffffef1`。地址 `0x004005e6` 处的语句是 `call sym.imp.puts`，`sym.imp.puts` 的偏移是 `0xfffffed5`。

接下来找到两个函数的 plt 地址：

```
[0x004004e0]> is~printf
vaddr=0x004004d0 paddr=0x000004d0 ord=003 fwd=NONE sz=16 bind=GLOBAL type=FUNC name=imp.printf
[0x004004e0]> is~puts
vaddr=0x004004c0 paddr=0x000004c0 ord=002 fwd=NONE sz=16 bind=GLOBAL type=FUNC name=imp.puts
```

计算相对位置：

```
[0x004004e0]> ?v 0x004004d0-0x004004c0
0x10
```

所以要想将 `printf` 替换为 `puts`，只要替换成 `0xfffffef1 -0x10 = 0xfffffee1` 就可以了。

```
[0x004004e0]> s 0x004005da
[0x004005da]> wx e8e1feffff
[0x004005da]> pd 1
|           0x004005da      e8e1feffff     call sym.imp.puts           ; sym.imp.printf-0x10 ; int printf(const char *format)
```

搞定。

```
$ ./a.out
hello
world
```

当然还可以将这一过程更加简化，直接输入汇编，其他的事情 r2 会帮你搞定：

```
[0x004005da]> wa call 0x004004c0
Written 5 bytes (call 0x004004c0) = wx e8e1feffff
[0x004005da]> wa call sym.imp.puts
Written 5 bytes (call sym.imp.puts) = wx e8e1feffff
```

## 使用工具 patch

### patchkit

[patchkit](https://github.com/lunixbochs/patchkit) 可以让我们通过 Python 脚本来 patch ELF 二进制文件。
