其实是 hgame week4 的啦
# shellcode

DIE 中查看发现为 GO 语言

IDA 中查看主函数发现有 base64
程序逻辑:对 shellcode 进行 base64 解码,然后分配内存,遍历文件夹读文件,调用函数加密,写文件。

复制找到的字符串去解密为新文件 (base64 肯定是可见字符呀)
VUiD7FBIjWwkIEiJTUBIi0VAiwCJRQC4BAAAAEgDRUCLAIlFBMdFCAAAAADHRQwj782rx0UQFgAAAMdFFCEAAADHRRgsAAAAx0UcNwAAAMdFIAAAAACLRSCD+CBzWotFDANFCIlFCItFBMHgBANFEItVCANVBDPCi1UEweoFA1UUM8IDRQCJRQCLRQDB4AQDRRiLVQgDVQAzwotVAMHqBQNVHDPCA0UEiUUEuAEAAAADRSCJRSDrnkiLRUCLVQCJELgEAAAASANFQItVBIkQSI1lMF3D

010 编辑器选择粘贴自 base64

新文件用 IDA 选择 64 位打开全选后 create function 再按 F5 发现一个 TEA

附件的 enc 文件就是加密过的数据 解 TEA 之后就能得到 flag
| a=[0x6d616768,0x68747b65,0x315f7331,0x68745f73,0x75745f33,0x73277574,0x6d30685f,0x72307765,0x7d6b,0x0,0x4adb98d,0xacb9e545] | |
| flag=b'' | |
| for i in a: | |
| flag +=long_to_bytes(i)[::-1] | |
| print(flag) | |
| #b"hgame{th1s_1s_th3_tutu's_h0mew0rk}\x00\x8d\xb9\xad\x04E\xe5\xb9\xac" | 
官方总结:
根据题目名称和提示来看,此题为一个 shellcode 加载器,程序运行过程中会解密一段机器码来运
行。go 编译的程序主函数为 main_main。观察 ida 反编译出来的内容,可很明显的观察到 base64 解
码的内容,所以我们将题目中的 base64 解码并保存成文件,使用 ida 打开,即可看出是 tea 加密算
法。之后再分析题目的文件读取和加密逻辑,发现就是很普通的 8 字节一组调用一次 base64 的机器
码,所以写解密脚本即可。
# VM
附件为一个 64 位程序 放进 IDA 中

在 main 函数中,可以看到第 8 行初始化了 vm 的结构体

点开 vm 的主体,可以看到一个 while 循环,byte_140005360 是一个全局数组,该循环在全局数组中按 (a1+24) 下标取的数据只要不是 255 就继续执行,因此该全局数组为 code,就是本虚拟机的字节码数组。而 (a1+24) 应该是 rip 寄存器。

进入 sub_140001940 函数 switch-case 结构,这些不同的 case 就对应不同指令了
点开 case0

首先 a1 [6] 就是 (a1+24) (int 为 4 字节) 也就是 rip 分析猜测为 mov 指令 *a1 是寄存器
后面分析的时候按照官方 WP 给出的方法在 ida 中新建一个 struct,将 rip 的偏移确定这样好看一点


再按 Y 改 a1 类型 (是指针的哇)

case1 有明显的栈操作的特征,所以此处是 push,而 unknown2 [0] 我们也可以确定是 rsp
根据分析过程完善定义的 vm 结构体

(zf 标志寄存器,用 0 和 1 记录计算结果)

再看后面的就很清晰了 case2 为 pop 出栈

case3 为进行运算的操作 (wp 直接用 alu 概括了)

case4 是 cmp 比较 zf 寄存器记录结果

case5 是跳转 jmp

case6 是 jne 不相等跳转 (左操作数≠右操作数)

case7 是 je 相等跳转 (左操作数 = 右操作数)
lazy IDA 提取出 data (input) 和字节码数组 写反汇编器把程序转换为汇编语言
| data=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 155, 168, 2, 188, 172, 156, 206, 250, 2, 185, 255, 58, 116, 72, 25, 105, 232, | |
| 3, 203, 201, 255, 252, 128, 214, 141, 215, 114, 0, 167, 29, 61, 153, 136, 153, | |
| 191, 232, 150, 46, 93, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 201, 169, 189, 139, | |
| 23, 194, 110, 248, 245, 110, 99, 99, 213, 70, 93, 22, 152, 56, 48, 115, 56, | |
| 193, 94, 237, 176, 41, 90, 24, 64, 167, 253, 10, 30, 120, 139, 98, 219, 15, | |
| 143, 156, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18432, 61696, 16384, 8448, 13569, | |
| 25600, 30721, 63744, 6145, 20992, 9472, 23809, 18176, 64768, 26881, 23552, | |
| 44801, 45568, 60417, 20993, 20225, 6657, 20480, 34049, 52480, 8960, 63488, | |
| 3072, 52992, 15617, 17665, 33280, 53761, 10497, 54529, 1537, 41473, 56832, | |
| 42497, 51713, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] | |
| code=[0x00, 0x03, 0x02, 0x00, 0x03, 0x00, 0x02, 0x03, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x03, 0x02, 0x32, | |
| 0x03, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, | |
| 0x01, 0x00, 0x00, 0x03, 0x02, 0x64, 0x03, 0x00, 0x02, 0x03, | |
| 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x01, 0x00, 0x00, 0x03, | |
| 0x00, 0x08, 0x00, 0x02, 0x02, 0x01, 0x03, 0x04, 0x01, 0x00, | |
| 0x03, 0x05, 0x02, 0x00, 0x03, 0x00, 0x01, 0x02, 0x00, 0x02, | |
| 0x00, 0x01, 0x01, 0x00, 0x00, 0x03, 0x00, 0x01, 0x03, 0x00, | |
| 0x03, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x03, 0x01, 0x28, | |
| 0x04, 0x06, 0x5F, 0x05, 0x00, 0x00, 0x03, 0x03, 0x00, 0x02, | |
| 0x01, 0x00, 0x03, 0x02, 0x96, 0x03, 0x00, 0x02, 0x03, 0x00, | |
| 0x00, 0x00, 0x00, 0x04, 0x07, 0x88, 0x00, 0x03, 0x00, 0x01, | |
| 0x03, 0x00, 0x03, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x03, | |
| 0x01, 0x28, 0x04, 0x07, 0x63, 0xFF, 0xFF, 0x00] | |
| ip=0 | |
| def mov(): | |
| global code,ip | |
| match code[ip+1]: | |
|  case 0: | |
|  print("mov reg[0],data[reg[2]]") | |
|  case 1: | |
|  print("mov data[reg[2]],reg[0]") | |
|  case 2: | |
|  print(f"mov reg[{code[ip+2]}],reg[{code[ip+3]}]") | |
|  case 3: | |
|  print(f"mov reg[{code[ip+2]}],{code[ip+3]}") | |
| ip+=4 | |
| def push(): | |
| global code,ip | |
| match code[ip+1]: | |
|  case 0: | |
|  print("push reg[0]") | |
|  case 1: | |
|  print("push reg[0]") | |
|  case 2: | |
|  print("push reg[2]") | |
|  case 3: | |
|  print("push reg[3]") | |
| ip+=2 | |
| def pop(): | |
| global code,ip | |
| match code[ip+1]: | |
|  case 0: | |
|  print("pop reg[0]") | |
|  case 1: | |
|  print("pop reg[0]") | |
|  case 2: | |
|  print("pop reg[2]") | |
|  case 3: | |
|  print("pop reg[3]") | |
| ip+=2 | |
| def alu(): | |
| global code,ip | |
| match code[ip+1]: | |
|  case 0: | |
|  print(f"add reg[{code[ip+2]}],reg[{code[ip+3]}]") | |
|  case 1: | |
|  print(f"sub reg[{code[ip+2]}],reg[{code[ip+3]}]") | |
|  case 2: | |
|  print(f"mul reg[{code[ip+2]}],reg[{code[ip+3]}]") | |
|  case 3: | |
|  print(f"xor reg[{code[ip+2]}],reg[{code[ip+3]}]") | |
|  case 4: | |
|  print(f"shl reg[{code[ip+2]}],reg[{code[ip+3]}]") | |
|  case 5: | |
|  print(f"shr reg[{code[ip+2]}],reg[{code[ip+3]}]") | |
| ip+=4 | |
| def cmp(): | |
| global code,ip | |
| print("cmp reg[0],reg[1]") | |
| ip+=1 | |
| def jmp(): | |
| global code,ip | |
| print(f"jmp {code[ip+1]}") | |
| ip+=2 | |
| def jne(): | |
| global code,ip | |
| print(f"jne {code[ip+1]}") | |
| ip+=2 | |
| def je(): | |
| global code,ip | |
| print(f"je {code[ip+1]}") | |
| ip+=2 | |
| while code[ip]!=255: | |
| match code[ip]: | |
|  case 0: | |
|  mov() | |
|  case 1: | |
|  push() | |
|  case 2: | |
|  pop() | |
|  case 3: | |
|  alu() | |
|  case 4: | |
|  cmp() | |
|  case 5: | |
|  jmp() | |
|  case 6: | |
|  jne() | |
|  case 7: | |
|  je() | 
运行结果

首先 data [0] 到 data [39] 共 40 个数据为输入的 flag 分析这 39 行汇编知操作为:
可看出逻辑为加 data [50+i],异或 data [100+i],左移 8 位 + 右移 8 位,并与 data [150+i] 逆序(因为加密后结果存放在栈中)进行比较,写解密代码即可:

总结:
这里方法是手写反汇编器还原虚拟机的指令 算是比较严谨的做法了顺便学习 VM 的基础吧 貌似可以动态调试来做?
虚拟机题型的关键在于分析出来虚拟机的数据结构,如寄存器,栈,内存等。在分析过程中能把寄存器在 IDA 中表示出来是最好的 (方便看,如题中分析出是结构体), 要把关键寄存器如 rip 先分析出来
题中 input (也就是脚本中的 data) 那个大数组其实就相当于栈,内存这种类似的存在了 就是一个空间
