[GKCTF2020]EzMachine
参考资料:
https://blog.csdn.net/weixin_43876357/article/details/108488762
https://www.cnblogs.com/EveningBreeze/p/13773930.html
一直想动笔写ezmachine这个,去网上看wp的时候发现是vm,还从来没写过,然后尝试着分析,便很快放弃,代码量还好,就是老跳转,把自己跳傻了,今天在来挑战的试试看。
由于在写这道题之前就看过wp,知道这是一个模仿机器指令的程序,所以先想办法推出程序流程。
在浏览汇编的时候突然看到一些标红的代码段,并且在里面发现了混淆指令,把401594这里nop掉就行了
看到函数,由于之前我用x32dbg动调的时候,看到了这一部分的汇编代码,我知道这是主要的跳转函数
先看第一个dd offset sub_4011B0,之前动调经常看到这个全局变量,并且我推测这个全局变量是eip,所以这个指令可能就是eip+1
看第二个dd offset sub_401000,这里把byte_4449a1的值给了eax与ecx,eip+3,a2的值存在off_4427fc与eax的一个偏移里面
看第三个dd offset sub_401070,把byte_4449a1的值给cl,在把这个值给到dword__445bac+esi,esi看起来像个index,并且每次调用都会加1
看第四个dd offset sub_401030,从off_4427fc中选一个数组里的元素出来,在放入另一个数组中,并且esi加1
看第五个dd offset sub_4010A0,把dword_445BAC数组里的最后一个放入off_4427FC数组里的元素,并且dword_445BC8-1
第六个dd offset sub_4010E0,一个switch语句,估计是打印结果的
.text:004010E0 mov eax, dword_445BCC .text:004010E5 mov ecx, dword_445BA8 .text:004010EB cmp eax, 4 ; switch 5 cases .text:004010EE ja loc_401187 ; jumptable 004010F4 default case .text:004010F4 jmp ds:off_401198[eax*4] ; switch jump .text:004010FB ; --------------------------------------------------------------------------- .text:004010FB .text:004010FB loc_4010FB: ; CODE XREF: sub_4010E0+14↑j .text:004010FB ; DATA XREF: .text:off_401198↓o .text:004010FB mov eax, ds:dword_4427D8 ; jumptable 004010F4 case 0 .text:00401100 mov [ecx], eax .text:00401102 mov ax, ds:word_4427DC .text:00401108 push ecx .text:00401109 mov [ecx+4], ax .text:0040110D call sub_407C00 .text:00401112 add esp, 4 .text:00401115 add dword_445BD8, 3 .text:0040111C retn .text:0040111D ; --------------------------------------------------------------------------- .text:0040111D .text:0040111D loc_40111D: ; CODE XREF: sub_4010E0+14↑j .text:0040111D ; DATA XREF: .text:off_401198↓o .text:0040111D mov eax, ds:dword_4427E0 ; jumptable 004010F4 case 1 .text:00401122 mov [ecx], eax .text:00401124 mov ax, ds:word_4427E4 .text:0040112A push ecx .text:0040112B mov [ecx+4], ax .text:0040112F call sub_407C00 .text:00401134 add esp, 4 .text:00401137 add dword_445BD8, 3 .text:0040113E retn .text:0040113F ; --------------------------------------------------------------------------- .text:0040113F .text:0040113F loc_40113F: ; CODE XREF: sub_4010E0+14↑j .text:0040113F ; DATA XREF: .text:off_401198↓o .text:0040113F movq xmm0, ds:qword_4427E8 ; jumptable 004010F4 case 3 .text:00401147 movq qword ptr [ecx], xmm0 .text:0040114B mov ax, ds:word_4427F0 .text:00401151 mov [ecx+8], ax .text:00401155 mov al, ds:byte_4427F2 .text:0040115A push ecx .text:0040115B mov [ecx+0Ah], al .text:0040115E call sub_407C00 .text:00401163 add esp, 4 .text:00401166 add dword_445BD8, 3 .text:0040116D retn .text:0040116E ; --------------------------------------------------------------------------- .text:0040116E .text:0040116E loc_40116E: ; CODE XREF: sub_4010E0+14↑j .text:0040116E ; DATA XREF: .text:off_401198↓o .text:0040116E mov eax, ds:dword_4427F4 ; jumptable 004010F4 case 4 .text:00401173 mov [ecx], eax .text:00401175 mov ax, ds:word_4427F8 .text:0040117B mov [ecx+4], ax .text:0040117F mov al, ds:byte_4427FA .text:00401184 mov [ecx+6], al .text:00401187 .text:00401187 loc_401187: ; CODE XREF: sub_4010E0+E↑j .text:00401187 ; sub_4010E0+14↑j .text:00401187 ; DATA XREF: ... .text:00401187 push ecx ; jumptable 004010F4 default case .text:00401188 call sub_407C00 .text:0040118D add dword_445BD8, 3 .text:00401194 add esp, 4 .text:00401197 retn .text:00401197 sub_4010E0 endp .text:00401197 .text:00401197 ; --------------------------------------------------------------------------- .text:00401198 off_401198 dd offset loc_4010FB ; DATA XREF: sub_4010E0+14↑r .text:00401198 dd offset loc_40111D ; jump table for switch statement .text:00401198 dd offset loc_401187 .text:00401198 dd offset loc_40113F .text:00401198 dd offset loc_40116E .text:004011AC align 10h
第七个dd offset sub_4011D0 ,两个值相加
第8个dd offset sub_401200,两个数相减
第9个dd offset sub_401230,两个数相乘
第10个dd offset sub_401270,两个数相除,dword_445bdc存放商,dword_445bc4存放余数
第11个dd offset sub_4012B0,两个数做异或
第12个dd offset sub_4012E0,不太清楚
第13个 dd offset sub_401300,两个数相减,并存放到dword_445bcc
第14个dd offset sub_401340,这个也没看懂干嘛
第15个dd offset sub_401370,感觉都跟上面一个样
第16个dd offset sub_4013A0-17几乎都是跟上面一样
第18个dd offset sub_401400,计算字符串的长度
第19个dd offset sub_401430,没看懂
第20个dd offset sub_401470,一个值加上dword_445bd0在加上dword_445bac偏移给另一个值
第21个dd offset sub_4014B0,跟上面的差不都
第22个dd offset sub_4011C0,不知道干啥
把opcode和数据全部导出来,并写下脚本,把程序流程打印出来
#字节码
code =[
0x01, 0x03, 0x03, 0x05, 0x00, 0x00, 0x11, 0x00, 0x00, 0x01,
0x01, 0x11, 0x0C, 0x00, 0x01, 0x0D, 0x0A, 0x00, 0x01, 0x03,
0x01, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x01, 0x02, 0x00,
0x01, 0x00, 0x11, 0x0C, 0x00, 0x02, 0x0D, 0x2B, 0x00, 0x14,
0x00, 0x02, 0x01, 0x01, 0x61, 0x0C, 0x00, 0x01, 0x10, 0x1A,
0x00, 0x01, 0x01, 0x7A, 0x0C, 0x00, 0x01, 0x0F, 0x1A, 0x00,
0x01, 0x01, 0x47, 0x0A, 0x00, 0x01, 0x01, 0x01, 0x01, 0x06,
0x00, 0x01, 0x0B, 0x24, 0x00, 0x01, 0x01, 0x41, 0x0C, 0x00,
0x01, 0x10, 0x24, 0x00, 0x01, 0x01, 0x5A, 0x0C, 0x00, 0x01,
0x0F, 0x24, 0x00, 0x01, 0x01, 0x4B, 0x0A, 0x00, 0x01, 0x01,
0x01, 0x01, 0x07, 0x00, 0x01, 0x01, 0x01, 0x10, 0x09, 0x00,
0x01, 0x03, 0x01, 0x00, 0x03, 0x00, 0x00, 0x01, 0x01, 0x01,
0x06, 0x02, 0x01, 0x0B, 0x0B, 0x00, 0x02, 0x07, 0x00, 0x02,
0x0D, 0x00, 0x02, 0x00, 0x00, 0x02, 0x05, 0x00, 0x02, 0x01,
0x00, 0x02, 0x0C, 0x00, 0x02, 0x01, 0x00, 0x02, 0x00, 0x00,
0x02, 0x00, 0x00, 0x02, 0x0D, 0x00, 0x02, 0x05, 0x00, 0x02,
0x0F, 0x00, 0x02, 0x00, 0x00, 0x02, 0x09, 0x00, 0x02, 0x05,
0x00, 0x02, 0x0F, 0x00, 0x02, 0x03, 0x00, 0x02, 0x00, 0x00,
0x02, 0x02, 0x00, 0x02, 0x05, 0x00, 0x02, 0x03, 0x00, 0x02,
0x03, 0x00, 0x02, 0x01, 0x00, 0x02, 0x07, 0x00, 0x02, 0x07,
0x00, 0x02, 0x0B, 0x00, 0x02, 0x02, 0x00, 0x02, 0x01, 0x00,
0x02, 0x02, 0x00, 0x02, 0x07, 0x00, 0x02, 0x02, 0x00, 0x02,
0x0C, 0x00, 0x02, 0x02, 0x00, 0x02, 0x02, 0x00, 0x01, 0x02,
0x01, 0x13, 0x01, 0x02, 0x04, 0x00, 0x00, 0x0C, 0x00, 0x01,
0x0E, 0x5B, 0x00, 0x01, 0x01, 0x22, 0x0C, 0x02, 0x01, 0x0D,
0x59, 0x00, 0x01, 0x01, 0x01, 0x06, 0x02, 0x01, 0x0B, 0x4E,
0x00, 0x01, 0x03, 0x00, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x00,
0x01, 0x03, 0x01, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00
]
#指令集
opcodekey = {0:'pcadd',1:'mov',2:'push',3:'?',4:'pop',5:'caseprint',6:'add',7:'sub1',8:'mul',9:'div',10:'xor',11:'jmp',12:'subcmp',13:'jedx4',14:'jnedx4',15:'jedx4orlow',16:'jedx4orhight',17:'input',18:'?2',19:'LoadStack',20:'LoadString',0xFF:'end'}
#B为字节码的下标
B=0
#C为每一行的序号
C=1
print(str(C)+": ",end="")
#字节码三个一组,每三个的第一个是指令,后两个为操作数
#i为字节码的遍历
for i in code:
#print(hex(i)+" ",end="")
if B==0:
#print(i)
op=opcodekey[i]
#opcodekey【每三个的第一个字节码】所对应的指令
print(op,end=' ')
else:
print(i,end=" ")
B=B+1
#每三个转下一行,也就是下一个汇编
if B==3:
B=0
C=C+1
print()
print(str(C)+": ",end="")
得到如下程序流程
1: mov 3 3 2: caseprint 0 0 3: input 0 0 4: mov 1 17 5: subcmp 0 1 6: jedx4 10 0 7: mov 3 1 8: caseprint 0 0 9: end 0 0 10: mov 2 0 11: mov 0 17 12: subcmp 0 2 13: jedx4 43 0 14: LoadString 0 2 15: mov 1 97 16: subcmp 0 1 17: jedx4orhight 26 0 18: mov 1 122 19: subcmp 0 1 20: jedx4orlow 26 0 21: mov 1 71 22: xor 0 1 23: mov 1 1 24: add 0 1 25: jmp 36 0 26: mov 1 65 27: subcmp 0 1 28: jedx4orhight 36 0 29: mov 1 90 30: subcmp 0 1 31: jedx4orlow 36 0 32: mov 1 75 33: xor 0 1 34: mov 1 1 35: sub1 0 1 36: mov 1 16 37: div 0 1 38: ? 1 0 39: ? 0 0 40: mov 1 1 41: add 2 1 42: jmp 11 0 43: push 7 0 44: push 13 0 45: push 0 0 46: push 5 0 47: push 1 0 48: push 12 0 49: push 1 0 50: push 0 0 51: push 0 0 52: push 13 0 53: push 5 0 54: push 15 0 55: push 0 0 56: push 9 0 57: push 5 0 58: push 15 0 59: push 3 0 60: push 0 0 61: push 2 0 62: push 5 0 63: push 3 0 64: push 3 0 65: push 1 0 66: push 7 0 67: push 7 0 68: push 11 0 69: push 2 0 70: push 1 0 71: push 2 0 72: push 7 0 73: push 2 0 74: push 12 0 75: push 2 0 76: push 2 0 77: mov 2 1 78: LoadStack 1 2 79: pop 0 0 80: subcmp 0 1 81: jnedx4 91 0 82: mov 1 34 83: subcmp 2 1 84: jedx4 89 0 85: mov 1 1 86: add 2 1 87: jmp 78 0 88: mov 3 0 89: caseprint 0 0 90: end 0 0 91: mov 3 1 92: caseprint 0 0 93: end 0 0 94: pcadd
在分析源程序
整个程序的逻辑大概是这样的:(开了两个栈)
首先判断你输入的是不是17位,不是直接跳转报错,是的话进行判断。
如果是小写字母,那就xor71+1,最后除以16,将商和余数压栈。(即十位和个位)
如果是大写字母,那就xor75-1,最后除以16,将商和余数压栈。(即十位和个位)
其余的直接除16压栈。
然后压入对照数据,最后弹栈比较。
最终脚本
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,] #注意是压栈,先进后出,所以要[::-1] 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 = "")
学到的知识
vm就是虚拟的机器指令
cdq指令,把eax的符号位给edx的每一位