EzMachine和DbgIsFun--GKCTF

DbgIsFun

TLS反调试,线程局部存储,先于程序EP代码执行1
TLS函数里包含循环,再结合IDA反编译代码爆红,可知代码存在自修改
4015C0:(不知这里为何是入口_笑哭.jpg)
main函数开头安装了seh函数(红框)

SEH是window操作系统默认的异常处理机制,逆向分析中,SEH除了基本的异常处理功能外,还大量用于反调试程序

EzMachine和DbgIsFun--GKCTF_第1张图片
安装后进行输入,然后对输入的长度减了一个0x1C,并随后触发int3断点来到seh函数。EzMachine和DbgIsFun--GKCTF_第2张图片
context结构体:EzMachine和DbgIsFun--GKCTF_第3张图片
SEH中的判断如图所示。在main函数中对flag长度做的sub操作会影响EFlags的值,当输入长度等于0x1C时,ZFlag置0,SEH函数由此判断输入的长度是否正确,如果错误会将程序的流程引向一段假的flag解密函数。长度正确则对标志位置1。
EzMachine和DbgIsFun--GKCTF_第4张图片
dword_41A8E0这个标志位将在子线程的函数中被循环检查,静态下该函数还未解密,所以需要进行动态调试等待代码段解密。解密后来到子线程函数。
有反调试,调都调不起来。
后面的就哈哈哈了,一只动调菜狗路过。

EzMachine

vm逆向,两种思路
输入下硬件断点,f9动调
IDA找opcode,分析各个opcode的作用写脚本生成伪汇编代码

IDA打开,没有符号表,所以找导出表start,找到函数入口004019C0,X32dbg跟进想找到输入,但到41594跑飞了,41595没有,分析知这是一个混淆,回到IDA nop掉
EzMachine和DbgIsFun--GKCTF_第5张图片
IDA nop掉
EzMachine和DbgIsFun--GKCTF_第6张图片
创建函数并查看伪代码,发现是VM结构,开始分析找opcode
off_4448F4处保存各个opcode对应函数的地址,4449A0处为虚拟机代码起始地址,dword_445BD8为ip,off_4427FC处保存有四个寄存器的地址,dword_445BAC为栈的起始地址,dword_445BC8为esp,opcode中有对栈的操作、跳转操作、取输入的操作以及加减乘除异或五种运算,分析各个opcode的作用后,可以写脚本生成伪汇编代码。EzMachine和DbgIsFun--GKCTF_第7张图片

opcode = {0x00:'nop',0x01:'mov',0x02:'pushi',0x03:'pushr',0x04:'pop',0x05:
'print',0x06:'add',0x07:'sub',0x08:'mul',0x09:'div',0x0A:'xor',0x0B:'jmp',
0x0C:'cmp',0x0D:'je',0x0E:'jn',0x0F:'jg',0x10:'jl',0x11:'input',0x12:'clrstr',0x13:'LoadStack',0x14:'LoadString',0xFF:'quit'}
operand = {'nop':0,'mov':2,'pushi':1,'pushr':1,'pop':1,'print':0,'add':2,'sub':2,'mul':2,'div':2,'xor':2,'jmp':1,'cmp':2,'je':1,'jn':1,'jg':1,'jl':1
,'input':0,'clrstr':0,'LoadStack':2,'LoadString':2,'quit':0}
code = [ 
    0x01, 3,3, 
    0x05, 0x00, 0x00, 
    0x11, 0x00, 0x00, 
    0x01, 1,17, 
    0x0C, 0,1, 
    0x0D, 10, 0x00, 
    0x01, 3,1, 
    0x05, 0x00, 0x00, 
    0xFF, 0x00, 0x00, 
    0x01, 2, 0, 
    0x01, 0, 17,
    0x0C, 0, 2,
    0x0D, 43, 0x00,
    0x14, 0, 2,
    0x01, 1, 97,
    0x0C, 0, 1,
    0x10, 26, 0x00,
    0x01, 1, 122,
    0x0C, 0, 1,
    0x0F, 26, 0x00,
    0x01, 1, 71,
    0x0A, 0, 1,
    0x01, 1, 1,
    0x06, 0, 1, 
    0x0B, 36, 0x00,
    0x01, 1, 65, 
    0x0C, 0, 1, 
    0x10, 36, 0x00, 
    0x01, 1, 90, 
    0x0C, 0, 1, 
    0x0F, 36, 0x00, 
    0x01, 1, 75, 
    0x0A, 0, 1, 
    0x01, 1, 1, 
    0x07, 0, 1, 
    0x01, 1, 16, 
    0x09, 0, 1, 
    0x03, 1, 0x00, 
    0x03, 0, 0x00, 
    0x01, 1, 1, 
    0x06, 2, 1, 
    0x0B, 11, 0x00, 
    0x02, 0x7, 0x00, 
    0x02, 0xD, 0x00, 
    0x02, 0x0, 0x00, 
    0x02, 0x5, 0x00, 
    0x02, 0x1, 0x00, 
    0x02, 0xC, 0x00, 
    0x02, 0x1, 0x00, 
    0x02, 0x0, 0x00, 
    0x02, 0x0, 0x00, 
    0x02, 0xD, 0x00, 
    0x02, 0x5, 0x00, 
    0x02, 0xF, 0x00, 
    0x02, 0x0, 0x00, 
    0x02, 0x9, 0x00, 
    0x02, 0x5, 0x00, 
    0x02, 0xF, 0x00, 
    0x02, 0x3, 0x00, 
    0x02, 0x0, 0x00, 
    0x02, 0x2, 0x00, 
    0x02, 0x5, 0x00, 
    0x02, 0x3, 0x00, 
    0x02, 0x3, 0x00, 
    0x02, 0x1, 0x00, 
    0x02, 0x7, 0x00, 
    0x02, 0x7, 0x00, 
    0x02, 0xB, 0x00, 
    0x02, 0x2, 0x00, 
    0x02, 0x1, 0x00, 
    0x02, 0x2, 0x00, 
    0x02, 0x7, 0x00, 
    0x02, 0x2, 0x00, 
    0x02, 0xC, 0x00, 
    0x02, 0x2, 0x00, 
    0x02, 0x2, 0x00, 
    0x01, 2, 1, 
    0x13, 1, 2, 
    0x04, 0, 0x00, 
    0x0C, 0, 1, 
    0x0E, 91, 0x00, 
    0x01, 1, 34, 
    0x0C, 2, 1, 
    0x0D, 89, 0x00, 
    0x01, 1, 1, 
    0x06, 2, 1, 
    0x0B, 78, 0x00, 
    0x01, 3, 0,
    0x05, 0x00, 0x00, 
    0xFF, 0x00, 0x00, 
    0x01, 3, 1, 
    0x05, 0x00, 0x00, 
    0xFF, 0x00, 0x00, 
] 
for i in range(0,len(code),3):
    if not code[i] in opcode.keys(): 
        print('unknow') 
        continue 
    op = opcode[code[i]] 
    print(op, end = ' ') 
    if operand[op] != 0: 
        print(code[i+1:i+1+operand[op]])
    else: 
        print() 

得到伪汇编

mov [3, 3]
print 
input 
mov [1, 17]
cmp [0, 1]
je [10]
mov [3, 1]
print 
quit 
mov [2, 0]
mov [0, 17]
cmp [0, 2]
je [43]
LoadString [0, 2]
mov [1, 97]
cmp [0, 1]
jl [26]
mov [1, 122]
cmp [0, 1]
jg [26]
mov [1, 71]
xor [0, 1]
mov [1, 1]
add [0, 1]
jmp [36]
mov [1, 65]
cmp [0, 1]
jl [36]
mov [1, 90]
cmp [0, 1]
jg [36]
mov [1, 75]
xor [0, 1]
mov [1, 1]
sub [0, 1]
mov [1, 16]
div [0, 1]
pushr [1]
pushr [0]
mov [1, 1]
add [2, 1]
jmp [11]
pushi [7]
pushi [13]
pushi [0]
pushi [5]
pushi [1]
pushi [12]
pushi [1]
pushi [0]
pushi [0]
pushi [13]
pushi [5]
pushi [15]
pushi [0]
pushi [9]
pushi [5]
pushi [15]
pushi [3]
pushi [0]
pushi [2]
pushi [5]
pushi [3]
pushi [3]
pushi [1]
pushi [7]
pushi [7]
pushi [11]
pushi [2]
pushi [1]
pushi [2]
pushi [7]
pushi [2]
pushi [12]
pushi [2]
pushi [2]
mov [2, 1]
LoadStack [1, 2]
pop [0]
cmp [0, 1]
jn [91]
mov [1, 34]
cmp [2, 1]
je [89]
mov [1, 1]
add [2, 1]
jmp [78]
mov [3, 0]
print 
quit 
mov [3, 1]
print 
quit

逻辑还挺好懂的,伪代码中,类似于mov、add、sub等指令后面的操作数为寄存器编号,例如 mov [1,2]代表将2号寄存器的内容放入1号寄存器。接下来分析伪汇编代码,发现是对输入的值进行处理,小写字母异或71后+1,大写字母异或75后-1,非字母不处理,处理得到的结果除以16,得到的商和余数分别进栈,并在最后部分比较,因此可以写出exp计算flag

array = [0x7,0xd,0x0,0x5,0x1,0xc,0x1,0x0,0x0,0xd,0x5,0xf,0x0,0x9,0x5,0xf,0x3,0x0,0x2,0x5,0x3,0x3,0x1,0x7,0x7,0xb,0x2,0x1,0x2,0x7,0x2,0xc,0x2,0x2,]
array = array[::-1] 
for i in range(0, len(array), 2): 
    c = array[i] + array[i+1]*16 
    tmp = (c-1) ^ 71 
    if tmp >= ord('a') and tmp <= ord('z'): 
        print(chr(tmp), end = "") 
        continue 
    tmp = (c+1) ^ 75 
    if tmp >= ord('A') and tmp <= ord('Z'): 
        print(chr(tmp), end = "") 
        continue 
    print(chr(c), end = "") 

flag{Such_A_EZVM}

你可能感兴趣的:(我的CTF之路)