其实是 hgame week4 的啦

# shellcode

image.png

DIE 中查看发现为 GO 语言

image.png

IDA 中查看主函数发现有 base64

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

image.png

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

VUiD7FBIjWwkIEiJTUBIi0VAiwCJRQC4BAAAAEgDRUCLAIlFBMdFCAAAAADHRQwj782rx0UQFgAAAMdFFCEAAADHRRgsAAAAx0UcNwAAAMdFIAAAAACLRSCD+CBzWotFDANFCIlFCItFBMHgBANFEItVCANVBDPCi1UEweoFA1UUM8IDRQCJRQCLRQDB4AQDRRiLVQgDVQAzwotVAMHqBQNVHDPCA0UEiUUEuAEAAAADRSCJRSDrnkiLRUCLVQCJELgEAAAASANFQItVBIkQSI1lMF3D

image.png

010 编辑器选择粘贴自 base64

image.png

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

image.png

附件的 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 中

image.png

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

image.png

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

image.png

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

点开 case0

image.png

首先 a1 [6] 就是 (a1+24) (int 为 4 字节) 也就是 rip 分析猜测为 mov 指令 *a1 是寄存器

后面分析的时候按照官方 WP 给出的方法在 ida 中新建一个 struct,将 rip 的偏移确定这样好看一点

image.png

image.png

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

image.png

case1 有明显的栈操作的特征,所以此处是 push,而 unknown2 [0] 我们也可以确定是 rsp

根据分析过程完善定义的 vm 结构体

image.png

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

image.png

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

image.png

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

image.png

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

image.png

case5 是跳转 jmp

image.png

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

image.png

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()

运行结果

image.png

首先 data [0] 到 data [39] 共 40 个数据为输入的 flag 分析这 39 行汇编知操作为:

可看出逻辑为加 data [50+i],异或 data [100+i],左移 8 位 + 右移 8 位,并与 data [150+i] 逆序(因为加密后结果存放在栈中)进行比较,写解密代码即可:

image.png

总结:

这里方法是手写反汇编器还原虚拟机的指令 算是比较严谨的做法了顺便学习 VM 的基础吧 貌似可以动态调试来做?

虚拟机题型的关键在于分析出来虚拟机的数据结构,如寄存器,栈,内存等。在分析过程中能把寄存器在 IDA 中表示出来是最好的 (方便看,如题中分析出是结构体), 要把关键寄存器如 rip 先分析出来

题中 input (也就是脚本中的 data) 那个大数组其实就相当于栈,内存这种类似的存在了 就是一个空间

更新于 阅读次数