1.前言
论坛里已经有很多人发过关于"2023腾讯游戏安全大赛-安卓赛道决赛"的文章了我就不多介绍,本人有幸参加过这次比赛,很可惜的是我PC和安卓两个赛道都参加了时间不够,最近有时间了花了两天重新看了一下题目,发现这个"tvm"还是比较简单的给大家分析还原一下。
在阅读本文之前,建议先对虚拟机(VM)的执行流程有一定的了解。这样能更好地理解本文所讨论的内容。
2.TVM
虚拟机流程
进入虚拟机
a64.dat不懂的可以看一下juice4fun(a64.dat),里面主要包含了arm64虚拟化指令("tvm"shellcode)。
uint32_t sub_99ef4(uint32_t arg1) {
char var_80[0x10] = "builtin";
char var_90[0x10] = "vm_main.img";
char PK[0x10] = "PK";
char var_d8[0x10] = "";
char var_e0[0x10] = "";
uintptr_t ctx = sub_95970(var_d8, var_80, var_90, PK, 0x409, var_e0);
// vm_Start(ctx, step)
sub_98e50(ctx + 0x10, 0x249f0);
return 0;
}
上面是虚拟机初始化代码,通过sub_9570函数初始化虚拟机环境得到vContext(包含虚拟寄存器、虚拟化指令、handler表、跳转表、调用表...),调用sub_98e50开始执行虚拟机。
vContext结构如下
struct vContext {
uint64_t x[29]; // 0x00 ~ 0xE0
uint64_t fp; // + 0xE8
uint64_t lr; // + 0xF0
uint64_t xzr; // +0xF8
uint64_t sp; // + 0x100
uint64_t pc; // + 0x108
...
}
handler分发
对于确认sub_98e50是vm_start的问题,可以根据上面的流程图进行分析。在反汇编中,可以观察到这里进行了表查找调用,也就是Handler的分发过程。
sub_98e50(vm_start)还原大致内容
uint64_t sub_98e50(uint64_t ctx, uint64_t step) {
// 0x00098e90 获取 opcode_tab
int64_t opcode_tab =
// 000996ec 获取 handler_tab
int64_t handler_tab =
// ctx + 0x00 X0
// ctx + 0x08 X1
// ctx + 0x10 X2 ... 以此类推 上面有vContext结构
// 0x00098ea4 获取 pc 寄存器
uint64_t* pc = (uint64_t*)(ctx + 0x108);
while(1) {
// 0x00099354 虚拟机状态置 1
*(uint32_t*)((char*)ctx + 0x120) = 1;
uint64_t i = (*pc >> 2);
// 取出 handler = *(handler_tab+i*8);
uint64_t handler = *(uint64_t*)(handler_tab + (i << 3));
// 取出 opcode = *(opcode_tab+i*4);
uint32_t opcode = *(uint32_t*)(opcode_tab + (i << 2));
// 0x00099368 Handler 分发
uintptr_t result = (funCall(handler))(opcode, ctx);
if (!result)
break;
*pc += 4;
}
return 0;
}
导出所有handler和对应虚拟化指令
+ 0x00
[vm] fun: 0xE2DB4 , opcode: 0xF01E0FF3
[vm] fun: 0xE227C , opcode: 0x95417BFD
[vm] fun: 0xCF004 , opcode: 0xD30043FD
[vm] fun: 0xE1148 , opcode: 0x092003F3
[vm] fun: 0xD0A88 , opcode: 0x05000000
[vm] fun: 0xCF004 , opcode: 0xB200E000
[vm] fun: 0xD2AD0 , opcode: 0x64000000
[vm] fun: 0xE1148 , opcode: 0x761303E1
[vm] fun: 0xD2AD0 , opcode: 0xF8000000
[vm] fun: 0xD6158 , opcode: 0xBD417BFD
[vm] fun: 0xE1148 , opcode: 0x673303E0
[vm] fun: 0xD6E00 , opcode: 0xC04207F3
[vm] fun: 0xD1CC0 , opcode: 0x14123456
+ 0x48
[vm] fun: 0xE6BF4 , opcode: 0xC38043FF
[vm] fun: 0xD0A88 , opcode: 0x91000009
[vm] fun: 0xE1148 , opcode: 0x541F03E8
[vm] fun: 0xCF004 , opcode: 0xCB856129
[vm] fun: 0xCF004 , opcode: 0xFD0033EA
[vm] fun: 0xE3E18 , opcode: 0x75C03BFF
[vm] fun: 0xE4C50 , opcode: 0xFD401BFF
[vm] fun: 0xE9564 , opcode: 0x18137C0C
[vm] fun: 0xE0BE8 , opcode: 0x1E1C03EB
[vm] fun: 0xE1148 , opcode: 0x6F00158C
[vm] fun: 0xE0BE8 , opcode: 0x7A9F03ED
[vm] fun: 0xDF75C , opcode: 0x31CB7D8E
[vm] fun: 0xD8B64 , opcode: 0x424D6D2F
[vm] fun: 0xD5A78 , opcode: 0x750B01CE
[vm] fun: 0xD0B2C , opcode: 0x3F001DD0
[vm] fun: 0xE9564 , opcode: 0x6F9E7610
[vm] fun: 0xD2420 , opcode: 0x63861DD0
[vm] fun: 0xCFAC4 , opcode: 0x601001EE
[vm] fun: 0xE8054 , opcode: 0x8F0001BF
[vm] fun: 0xE4980 , opcode: 0xFDAD694E
[vm] fun: 0xE6BF4 , opcode: 0xE88005AD
[vm] fun: 0xE6BF4 , opcode: 0x1D00216B
[vm] fun: 0xD1AE8 , opcode: 0xAEFFFEBC
[vm] fun: 0xE1148 , opcode: 0xD53F03EB
[vm] fun: 0xD8B64 , opcode: 0x77EB6D4C
[vm] fun: 0xD8B64 , opcode: 0x400B6D2D
[vm] fun: 0xE9564 , opcode: 0x75857D8E
[vm] fun: 0xD2420 , opcode: 0x3D1D1D8E
[vm] fun: 0xD5A78 , opcode: 0x2E2D01CC
[vm] fun: 0xE4980 , opcode: 0xC8AB694C
[vm] fun: 0xCF004 , opcode: 0xFA80056B
[vm] fun: 0xE8054 , opcode: 0xBF000D7F
[vm] fun: 0xD1AE8 , opcode: 0x1FFFFF11
[vm] fun: 0xD7F4C , opcode: 0xDD003BEB
[vm] fun: 0xCF004 , opcode: 0x1E01D56B
[vm] fun: 0xE3E18 , opcode: 0xBDC03BEB
[vm] fun: 0xD7F4C , opcode: 0x558037EB
[vm] fun: 0xD5518 , opcode: 0x719F196B
[vm] fun: 0xE3E18 , opcode: 0x418037EB
[vm] fun: 0xD6E00 , opcode: 0xB9400FEC
[vm] fun: 0xE0BE8 , opcode: 0x7A9D03EB
[vm] fun: 0xCF004 , opcode: 0x3F03058D
[vm] fun: 0xE3E18 , opcode: 0xFDC033ED
[vm] fun: 0xE9564 , opcode: 0x60907D8D
[vm] fun: 0xE9564 , opcode: 0x18105D8C
[vm] fun: 0xE9564 , opcode: 0x1E1F798C
[vm] fun: 0xD2420 , opcode: 0x57871DAC
[vm] fun: 0xD5518 , opcode: 0x759F018C
[vm] fun: 0xE3E18 , opcode: 0x55803BEC
[vm] fun: 0xE9564 , opcode: 0x71901D80
[vm] fun: 0xE0BE8 , opcode: 0x340003EC
[vm] fun: 0xD8B64 , opcode: 0x16EC654D
[vm] fun: 0xE8054 , opcode: 0xBF00019F
[vm] fun: 0xE9564 , opcode: 0x6F877DAE
[vm] fun: 0xD2420 , opcode: 0x639F1DAE
[vm] fun: 0xD5A78 , opcode: 0x610C01CD
[vm] fun: 0xE4980 , opcode: 0x02EC6D4D
[vm] fun: 0xD0B2C , opcode: 0x3D001DAD
[vm] fun: 0xDF458 , opcode: 0x48EB35AD
[vm] fun: 0xE6BF4 , opcode: 0x9D00058C
[vm] fun: 0xCFAC4 , opcode: 0x572001A0
[vm] fun: 0xE6BF4 , opcode: 0x5E80216B
[vm] fun: 0xD1AE8 , opcode: 0x3FFFFEAC
[vm] fun: 0xCF004 , opcode: 0x61800508
[vm] fun: 0xE8054 , opcode: 0x5804011F
[vm] fun: 0xD1AE8 , opcode: 0xBCFFF8C1
[vm] fun: 0xCF004 , opcode: 0xF58043FF
[vm] fun: 0xE1468 , opcode: 0xEAC73BD4
tvmAsm to Asm
这里拿个比较简单的举例 ADRP [vm] fun: 0xD0A88, opcode: 05000000
对arm汇编熟悉的人那么不难看出来,这个handler内部解析opcode执行对应操作,实际上就对应一条arm64指令。我的还原方法是通过观察arm64-v8a解析opcode的方式,与反汇编操作进行对比,以判断是否相符。
把sub_D0A88代码还原出来和arm64(ADRP)编码对比一样,由此判定该方法为ADRP。
void sub_D0A88(uint64_t opcode, uint64_t ctx) {
// integer d = UInt(Rd);
uint32_t Rd = opcode & 0x1f;
// imm = SignExtend(immhi:immlo:Zeros(12), 64);
uint64_t imm = (0x3fff & ((opcode >> 0x11) & 0x3000)) | ((0x3ffff & (opcode >> 5)) << 0xe);
uint64_t PC = *(uint64_t*)(ctx + 0x108);
// bits(64) base = PC[]; base<11:0> = Zeros(12);
uint64_t base = (PC & 0xfffffffffffff000);
// 写回寄存器 X[d] = base + imm
*(uint64_t*)(ctx + (Rd << 3)) = base + imm;
}
还原例子
这里我进行了对sec2023决赛题目的还原操作。左边是我所还原"tvm"的arm64汇编代码,右边是|_|sher大佬的解题算法。可以发现它们在大体上非常相似。虽然我只花了一两天的时间来完成这个工作,但是已经有了一个相当不错的结果。
3.总结
编程也就图一乐收收心找个电子厂上班了
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2023-9-26 14:52
被a'ゞCicada编辑
,原因: