其实是 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) 那个大数组其实就相当于栈,内存这种类似的存在了 就是一个空间