羊城杯_2021 OddCode 天堂之门 + unicorn反混淆

一、天堂之门

输入字符串并进行校验后,有一处远跳,看其硬编码,发现cs段寄存器变成了0x33,天堂之门无疑,
0
并且,我们通过硬编码发现,IDA解析的函数跳转有误,应该是 jmp far ptr 0x405310才对
0x0405315地址的位置,显然是天堂之门切换为了32位
0
我们修改exe的PE结构中的魔数,使其能在IDA64位下分析,再次打开后,发现代码还是混淆严重,无法阅读,各种垃圾指令干扰阅读...
那我们就考虑到使用unicorn来模拟64位代码的运行

二、unicorn模拟代码运行

为什么使用unicorn呢? unicorn中有一类Hook函数,它可以记录程序读写了那些指令或地址,跳转到了哪些地址,我们可以观察程序对关键地址的读写或程序的转移位置来忽视混淆
我们首先用Hook函数查看程序的转移位置,查看程序对input和key的地址做了什么
from unicorn import *
from unicorn.x86_const import *
from struct import  *

with open(r"C:\Users\TLSN\Desktop\OddCode.exe",'rb')  as fp:
    fp.seek(0x400)
    X64code = fp.read(0x5000)

class UniVm:
    def __init__(self,strr):
        # 创建虚拟机
        mu = Uc(UC_ARCH_X86, UC_MODE_64)

        # 分配内存 分配10 * 0x1000的大小
        ADDRESS = 0x7d1000  # 初始地址  20mb
        mu.mem_map(ADDRESS, 20 * 0x100000)
        # 填充内存
        mu.mem_write(ADDRESS, X64code)
        # 填充寄存器  注意入口点的选取和寄存器有关系
        mu.reg_write(UC_X86_REG_RAX, 1)
        mu.reg_write(UC_X86_REG_RBX, 0xc8402d)
        mu.reg_write(UC_X86_REG_RCX, 0x9098de57)
        mu.reg_write(UC_X86_REG_RDX, 0x768c9cca)
        mu.reg_write(UC_X86_REG_RSI, 0x7d701d)
        mu.reg_write(UC_X86_REG_RDI, 0x7d705c)
        mu.reg_write(UC_X86_REG_RBP, 0xf5faf8)
        mu.reg_write(UC_X86_REG_RIP, 0x7d1010)
        mu.reg_write(UC_X86_REG_RSP, 0xf5fae0)
        # mu.reg_write(UC_X86_REG_FLAGS, 0x202)

        # 填充堆栈
        # mu.mem_write(0x0f5fae0, b'\x15\x53\x7d\x00\x00\x00\x00\x00')
        # mu.mem_write(0x0f5fae8, b'\x6c\x52\x7d\x00\x39\x67\xa1\x76')
        # mu.mem_write(0x0f5faf0, b'\x00\x40\xc8\x00\x20\x67\xa1\x76')
        # mu.mem_write(0x0f5faf8, b'\x50\xfb\xf5\x00\xef\x8f\x29\x77')
        # 填充input 和 key
        mu.mem_write(0x007d701D, strr)  #
        mu.mem_write(0x007d705C, b'\x90\xF0\x70\x7C\x52\x05\x91\x90\xAA\xDA\x8F\xFA\x7B\xBC\x79\x4D')
        # 关键指令,通过Hook函数代码保存代码轨迹
        mu.hook_add(UC_HOOK_CODE, self.hook_code, begin=ADDRESS, end=ADDRESS + len(X64code))
        mu.hook_add(UC_HOOK_MEM_READ, self.hook_read_mem)
        ADDRESS = 0x7d1000
        LocIns_Addr = 0
        Pre_Ins_Size = 0
        Nums = 0
        Save_Rip = []
        self.mu = mu
        self.LocIns_Addr = LocIns_Addr
        self.Nums = Nums
        self.Save_Rip = Save_Rip
        self.ADDRESS = ADDRESS
        self.Pre_Ins_Size = Pre_Ins_Size
    # Hook指令
    def hook_code(self, mu, address, size, data):
        if abs(address - self.LocIns_Addr) > self.Pre_Ins_Size:
            self.Save_Rip.append(address)
            self.Nums += 1
        self.LocIns_Addr = address
        self.Pre_Ins_Size = size
    #Hook读地址
    def hook_read_mem(self,uc, access, address, size, value, data):
        if address >= 0x007d701D and address <= 0x007d701D + 41:
            print(f'Read input[{address - 0x007d701D}] : {hex(uc.reg_read(UC_X86_REG_RIP))}')
        if address >= 0x007d705C and address <= 0x007d705C + 15:
            print(f'Read key[{address - 0x007d705C}] : {hex(uc.reg_read(UC_X86_REG_RIP))}')
        if uc.reg_read(UC_X86_REG_RIP) == 0x07D531A:
            print("okkkk")
    def start(self):
        try:
            self.mu.emu_start(self.ADDRESS + 0x10, self.ADDRESS + len(X64code) - 0x10)
        except UcError as e:
            print("ERROR: %s " % e)
            self.p_Rip()
            self.mu.emu_stop()
    def p_Rip(self):
        ans = 1
        for i in self.Save_Rip:
            print(hex(i), end=",")
            if ans % 16 == 0:
                print()
            ans += 1


strr = b"SangFor{01234567890123456789012345678901}"
UniVm(strr).start()
 
0
 
根据程序对input和key的读取,我们可以看出,程序应该是两字符两字符的进行处理,并且限制了字符的范围:
0
 
0
 
我们尝试先爆破前两个字符,得到前两个字符是A7,我们用A7替换输入并再次查看Hook的结果:
0
 
可以发现,读取input的次数变多了并且程序开始读取下两个字符,而且读取下两个字符的地址和第一次读取的地址一模一样,猜测程序是在循环读取并且两个字节两个字节的进行比较,只不过这个大循环被混淆了,那爆破思路就有了,只需要两个字符两个字符的爆破就行了

三、爆破脚本(前30个字符)

from unicorn import *
from unicorn.x86_const import *
from struct import  *

with open(r"./OddCode.exe",'rb')  as fp:
    fp.seek(0x400)
    X64code = fp.read(0x5000)

times = 0
class UniVm:
    index = []
    def __init__(self,strr,bs):
        # 创建虚拟机
        mu = Uc(UC_ARCH_X86, UC_MODE_64)

        # 分配内存 分配10 * 0x1000的大小
        ADDRESS = 0x7d1000  # 初始地址  20mb
        mu.mem_map(ADDRESS, 20 * 0x100000)
        # 填充内存
        mu.mem_write(ADDRESS, X64code)
        # 填充寄存器  注意入口点的选取和寄存器有关系
        mu.reg_write(UC_X86_REG_RAX, 1)
        mu.reg_write(UC_X86_REG_RBX, 0xc8402d)
        mu.reg_write(UC_X86_REG_RCX, 0x9098de57)
        mu.reg_write(UC_X86_REG_RDX, 0x768c9cca)
        mu.reg_write(UC_X86_REG_RSI, 0x7d701d)
        mu.reg_write(UC_X86_REG_RDI, 0x7d705c)
        mu.reg_write(UC_X86_REG_RBP, 0xf5faf8)
        mu.reg_write(UC_X86_REG_RIP, 0x7d1010)
        mu.reg_write(UC_X86_REG_RSP, 0xf5fae0)

        # 填充input 和 key
        mu.mem_write(0x007d701D, strr)  #
        mu.mem_write(0x007d705C, b'\x90\xF0\x70\x7C\x52\x05\x91\x90\xAA\xDA\x8F\xFA\x7B\xBC\x79\x4D')
        # 关键指令,通过Hook函数代码保存代码轨迹
        mu.hook_add(UC_HOOK_CODE, self.hook_code, begin=ADDRESS, end=ADDRESS + len(X64code))
        mu.hook_add(UC_HOOK_MEM_READ, self.hook_read_mem)
        ADDRESS = 0x7d1000
        LocIns_Addr = 0
        Pre_Ins_Size = 0
        Nums = 0
        Save_Rip = []
        self.mu = mu
        self.LocIns_Addr = LocIns_Addr
        self.Nums = Nums
        self.Save_Rip = Save_Rip
        self.ADDRESS = ADDRESS
        self.Pre_Ins_Size = Pre_Ins_Size
        self.strrr = strr
        self.bns = bs
    # Hook指令
    def hook_code(self, mu, address, size, data):
        if abs(address - self.LocIns_Addr) > self.Pre_Ins_Size:
            self.Save_Rip.append(address)
            self.Nums += 1
        self.LocIns_Addr = address
        self.Pre_Ins_Size = size
    #Hook读地址
    def hook_read_mem(self,uc, access, address, size, value, data):
        if address >= 0x007d701D and address <= 0x007d701D + 41:
            self.index.append(address - 0x007d701D)

    def start(self):
        global times
        try:
            self.mu.emu_start(self.ADDRESS + 0x10, self.ADDRESS + len(X64code) - 0x10)
        except UcError as e:
            self.mu.emu_stop()
        num = len(list(set(self.index))) // 2
        #print(num,self.bns)
        if num == self.bns:
            print(self.strrr)
            return 1
        else:
            return 0


from alive_progress import alive_bar
map = b"0123456789ABCDEFabcdef"  #爆破的时候发现,输入中存在小写字符
fflag =  b"SangFor{"
bns = 2
with alive_bar(16*len(map)*len(map)) as bar:
    for z in range(8,40,2):
        print(f"{z},{z+1}:")
        for i in map:
            ans = 0
            for j in map:
                strr = (fflag[:z] + pack("B",i)+ pack("B",j)).ljust(40,b"0") + b"}"
                if UniVm(strr,bns).start() == 1:
                    ans = 1
                    bns += 1
                    fflag += pack("B",i) + pack("B",j)
                    break
                bar()
            if ans == 1:
                break
爆破结果如下:
不知道为什么,程序在windows下跑不了,但在linux上可以跑
0
 
 
0
 
这个脚本只能爆破得到前30个字符,猜测最后两个字符的校验不在循环之内,那我们只好再另写脚本爆破最后两个字符了

四、爆破最后两个字符

#下面代码在windows下运行
import os
map = "0123456789ABCDEFabcdef"
flag = "SangFor{A7A4A0C0B10Bafa776F55FF4F8C6E8"
for i in map:
    for j in map:
        strr = flag + str(i) + str(j) + "}"
        tmp = os.popen(fr'echo {strr} | C:\Users\TLSN\Desktop\各种ctf题目\RE刷\混淆\OddCode.exe').readlines()[2:3]
        # print(tmp)#出错原因,一开始用bytes进行爆破,发现echo的时候多出了 b'' 等多余字符
        if "Success." in tmp:
            print(strr)
            exit(0)
 
0

五、总脚本和flag

最后的总脚本如下
from unicorn import *
from unicorn.x86_const import *
from struct import  *

with open(r"./OddCode.exe",'rb')  as fp:
    fp.seek(0x400)
    X64code = fp.read(0x5000)

times = 0
class UniVm:
    index = []
    def __init__(self,strr,bs):
        # 创建虚拟机
        mu = Uc(UC_ARCH_X86, UC_MODE_64)

        # 分配内存 分配10 * 0x1000的大小
        ADDRESS = 0x7d1000  # 初始地址  20mb
        mu.mem_map(ADDRESS, 20 * 0x100000)
        # 填充内存
        mu.mem_write(ADDRESS, X64code)
        # 填充寄存器  注意入口点的选取和寄存器有关系
        mu.reg_write(UC_X86_REG_RAX, 1)
        mu.reg_write(UC_X86_REG_RBX, 0xc8402d)
        mu.reg_write(UC_X86_REG_RCX, 0x9098de57)
        mu.reg_write(UC_X86_REG_RDX, 0x768c9cca)
        mu.reg_write(UC_X86_REG_RSI, 0x7d701d)
        mu.reg_write(UC_X86_REG_RDI, 0x7d705c)
        mu.reg_write(UC_X86_REG_RBP, 0xf5faf8)
        mu.reg_write(UC_X86_REG_RIP, 0x7d1010)
        mu.reg_write(UC_X86_REG_RSP, 0xf5fae0)

        # 填充input 和 key
        mu.mem_write(0x007d701D, strr)  #
        mu.mem_write(0x007d705C, b'\x90\xF0\x70\x7C\x52\x05\x91\x90\xAA\xDA\x8F\xFA\x7B\xBC\x79\x4D')
        # 关键指令,通过Hook函数代码保存代码轨迹
        mu.hook_add(UC_HOOK_CODE, self.hook_code, begin=ADDRESS, end=ADDRESS + len(X64code))
        mu.hook_add(UC_HOOK_MEM_READ, self.hook_read_mem)
        ADDRESS = 0x7d1000
        LocIns_Addr = 0
        Pre_Ins_Size = 0
        Nums = 0
        Save_Rip = []
        self.mu = mu
        self.LocIns_Addr = LocIns_Addr
        self.Nums = Nums
        self.Save_Rip = Save_Rip
        self.ADDRESS = ADDRESS
        self.Pre_Ins_Size = Pre_Ins_Size
        self.strrr = strr
        self.bns = bs
    # Hook指令
    def hook_code(self, mu, address, size, data):
        if abs(address - self.LocIns_Addr) > self.Pre_Ins_Size:
            self.Save_Rip.append(address)
            self.Nums += 1
        self.LocIns_Addr = address
        self.Pre_Ins_Size = size
    #Hook读地址
    def hook_read_mem(self,uc, access, address, size, value, data):
        if address >= 0x007d701D and address <= 0x007d701D + 41:
            self.index.append(address - 0x007d701D)

    def start(self):
        global times
        try:
            self.mu.emu_start(self.ADDRESS + 0x10, self.ADDRESS + len(X64code) - 0x10)
        except UcError as e:
            self.mu.emu_stop()
        num = len(list(set(self.index))) // 2
        #print(num,self.bns)
        if num == self.bns:
            print(self.strrr)
            return 1
        else:
            return 0


from alive_progress import alive_bar
map = b"0123456789ABCDEFabcdef"
fflag =  b"SangFor{"
bns = 2
with alive_bar(16*len(map)*len(map)) as bar:
    for z in range(8,40,2):
        print(f"{z},{z+1}:")
        for i in map:
            ans = 0
            for j in map:
                strr = (fflag[:z] + pack("B",i)+ pack("B",j)).ljust(40,b"0") + b"}"
                if UniVm(strr,bns).start() == 1:
                    ans = 1
                    bns += 1
                    fflag += pack("B",i) + pack("B",j)
                    break
                bar()
            if ans == 1:
                break


# #下面代码在windows下运行
# import os
# map = "0123456789ABCDEFabcdef"
# flag = "SangFor{A7A4A0C0B10Bafa776F55FF4F8C6E8"
# for i in map:
#     for j in map:
#         strr = flag + str(i) + str(j) + "}"
#         tmp = os.popen(fr'echo {strr} | C:\Users\TLSN\Desktop\各种ctf题目\RE刷\混淆\OddCode.exe').readlines()[2:3]
#         # print(tmp)#出错原因,一开始用bytes进行爆破,发现echo的时候多出了 b'' 等多余字符
#         if "Success." in tmp:
#             print(strr)
#             exit(0)
flag即为
SangFor{A7A4A0C0B10Bafa776F55FF4F8C6E849}
 
 
 
第一次接触unicorn ,感觉是真滴好用,尤其是Hook函数...
这道题也是参考无名侠和地球人大佬的博客才做了出来,也是有很大收获
posted @ 2022-07-23 12:05  TLSN  阅读(225)  评论(0编辑  收藏  举报