[天堂之门+模拟执行]2021羊城杯oddcode
天堂之门
学习文章 https://taardisaa.github.io/2021/09/25/Heaven'sGate/
前言
Windows判别位的方式,是根据cs段寄存器的。
在32位程序中,cs的值是0x23;
在64位程序中,cs的值是0x33。
所以只要修改cs的值,就能实现切换。而实现这个切换,使用的是ljmp长跳转指令(在Intel语法中也可以叫做jmp far ptr),或者retf长返回指令.
32位转64位
以jmp far ptr xxxx对应jmp xx:xxxx
读硬编码:
EA 10 53 C7 00 33 00
jmp 0033:00C75310(cs:ip)
以这个长跳转便改变了cs的值位0x33.之后便会以64位执行下面call的函数
64位转32位
用retf实现
call到下一条指令,并将这条指令的地址压栈.
call $+5
将栈顶esp的上一个数赋值为0x23
mov dword ptr [esp+4], 23h
将栈顶esp进行修正.将返回地址设置到retf后一共0x874331-0x87431A=0x17(从mov到retf)
sub dword ptr [esp], 87431Ah
add dword ptr [esp], 874331h
最好retf依次从栈顶弹出ip的值与cs的值,cs:ip共同作用实现返回
模拟执行(Unicorn)
先贴一下exp
import capstone
from unicorn import *
from unicorn.x86_const import *
ADDRESS = 0xC71000 # 初始地址
INPUT_ADDR = 0xc7701d
KEY_ADDR = 0xc7705c
with open(r"D:\ctf\ctf_game\OddCode.exe", 'rb') as fp:
fp.seek(0x400)
x64_code = fp.read(0x5000)
class UniVM:
def __init__(self, flag, except_hit):
# 创建虚拟机(x86架构,64位)
mu = Uc(UC_ARCH_X86, UC_MODE_64)
# 分配内存
mu.mem_map(ADDRESS, 0x1000000)
# 填充内存
mu.mem_write(ADDRESS, x64_code)
# 填充寄存器
mu.mem_write(INPUT_ADDR, flag)
mu.mem_write(KEY_ADDR, b'\x90\xF0\x70\x7C\x52\x05\x91\x90\xAA\xDA\x8F\xFA\x7B\xBC\x79\x4D')
mu.reg_write(UC_X86_REG_RAX, 1)
mu.reg_write(UC_X86_REG_RBX, 0xf1b02d)
mu.reg_write(UC_X86_REG_RCX, 0x5779887a)
mu.reg_write(UC_X86_REG_RDX, 1)
mu.reg_write(UC_X86_REG_RSI, INPUT_ADDR)
mu.reg_write(UC_X86_REG_RDI, KEY_ADDR)
mu.reg_write(UC_X86_REG_RBP, 0x118fe44)
mu.reg_write(UC_X86_REG_RSP, 0x118fe34)
mu.reg_write(UC_X86_REG_RIP, 0xC75309)
mu.reg_write(UC_X86_REG_RFLAGS, 0x202)
# mu.hook_add(UC_HOOK_MEM_READ, self.hook_mem_read)
mu.hook_add(UC_HOOK_CODE, self.trace)
self.md = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64)
self.mu = mu
self.except_addr = 0
self.except_hit = except_hit
self.traces = []
self.hit = 0
self.success = False
'''
#capstone查看cmp与test位置
def trace(self, mu, address,size, data):
disasm = self.md.disasm(mu.mem_read(address, size), address)
for i in disasm:
mnemonic = i.mnemonic
if mnemonic == 'cmp' or mnemonic == 'test':
print(f'Instruction{mnemonic} at {hex(address)}')
if address != self.except_addr:
self.traces.append(address)
self.except_addr = address+size
'''
def trace(self, mu, address, size, data):
if address != self.except_addr:
self.traces.append(address)
self.except_addr = address + size # 更新 self.except_addr 为当前指令结束后的地址(address + size)
if address == 0xc738EF:
self.hit += 1
if self.hit == self.except_hit:
self.success = True
mu.emu_stop()
def hook_mem_read(self, mu, access, address, size, value, data):
if address >= INPUT_ADDR and address <= INPUT_ADDR + 41:
print(f'Read input[{address - INPUT_ADDR}] at {hex(mu.reg_read(UC_X86_REG_RIP))}')
if address >= KEY_ADDR and address <= KEY_ADDR + 16:
print(f'Read key[{address - KEY_ADDR}] at {hex(mu.reg_read(UC_X86_REG_RIP))}')
def solve(self):
try:
self.mu.emu_start(ADDRESS + 0x10, -1)
except:
pass
return self.success
def get_flag(flag, except_hit):
for i in b'1234567890abcdefABCDEF':
for j in b'1234567890abcdefABCDEF':
flag[8 + (except_hit - 1) * 2] = i
flag[8 + (except_hit - 1) * 2 + 1] = j
if UniVM(bytes(flag), except_hit).solve():
return
flag = bytearray(b'SangFor{00000000000000000000000000000000}')
for i in range(1, 17):
get_flag(flag, i)
print(flag.decode())
基本使用方法: https://www.cnblogs.com/Only-xiaoxiao/p/17316343.html