设为首页
收藏本站
只需一步,快速开始
首页
Portal
论坛
BBS
问答
CTF社群
Group
CTF平台
每日签到
青少年CTF论坛
»
论坛
›
CTF
›
Reverse
›
查看内容
0 评论
0 收藏
分享
2019UNCTF easyvm
SHangwendada
发布于 2023-3-6 21:54:02
阅读 1429
查看全部
来自
日本
# [虚拟机逆向]UNCTF - 2019 EasyVm ## 前言 虚拟机逆向在Hgame2023中遇见过,这次刷题中又遇见了,写一篇文章总结一下 ## 什么是虚拟机逆向 虚拟机逆向是指对一个运行在虚拟机上的程序进行逆向工程。虚拟机是一种软件层,它模拟了一种计算机架构,允许程序在不同的平台上运行。在虚拟机上运行的程序通常使用一种特定的指令集,这个指令集不同于在物理机器上运行的指令集。 虚拟机逆向包括对虚拟机本身的分析,以及对在虚拟机上运行的程序的分析。对于虚拟机本身的分析,可以探究虚拟机的指令集、内存布局、代码执行流程等方面。对于在虚拟机上运行的程序的分析,可以通过反编译、动态调试等手段获取程序的源代码、调用栈信息、内存映射等信息,以此来理解程序的行为和工作原理。 虚拟机逆向常用于软件逆向工程、漏洞挖掘和安全评估等领域。 ## 前期准备 要进行虚拟机逆向,需要具备以下几点准备: 1. 计算机基础知识:逆向是计算机领域的高级技术,需要对计算机的结构和原理有一定的了解。 2. 操作系统和编程语言的基础:要逆向虚拟机,掌握一种或多种编程语言非常有帮助。同时熟悉操作系统的基础知识也是必要的,以便能够在不同的操作系统上进行虚拟机逆向。 3. 调试工具的使用:在虚拟机逆向过程中,需要使用各种调试器和分析工具,例如IDA、OllyDbg等,这需要对这些工具的使用方法有一定的掌握。 4. 熟悉汇编语言:虚拟机的实现常常会涉及到汇编语言,因此熟悉汇编语言是进行虚拟机逆向的必要条件。 5. 拥有调试虚拟机的实践经验:虚拟机逆向需要具有一定的实践经验,了解虚拟机的实现原理和逆向技巧,需要进行大量的实践操作才能熟练掌握。 ## 题解 ### 主函数 ~~~c __int64 __fastcall main(int a1, char **a2, char **a3) { unsigned int (__fastcall ***v3)(_QWORD, void *, void *, char *); // rbx char s[96]; // [rsp+10h] [rbp-80h] BYREF int v6; // [rsp+70h] [rbp-20h] unsigned __int64 v7; // [rsp+78h] [rbp-18h] v7 = __readfsqword(0x28u); memset(s, 0, sizeof(s)); v6 = 0; v3 = (unsigned int (__fastcall ***)(_QWORD, void *, void *, char *))operator new(0x28uLL); sub_400C1E(v3, a2); puts("please input your flag:"); scanf("%s", s); if ( strlen(s) != 32 ) { puts("The length of flag is wrong!"); puts("Please try it again!"); } if ( (**v3)(v3, &unk_602080, &unk_6020A0, s) ) { puts("Congratulations!"); printf("The flag is UNCTF{%s}", s); } return 1LL; } ~~~ 可以发现主函数非常的简洁就是做了长度的判断,然后还有一个v3作为一个函数指针然后将输入的函数作为传入参数进行了一些判断 分析完毕,我们主要目标就是跟进这个函数指针,查看对传入的字符串做了一些什么操作。 首先我们看到sub_400C1E这个函数是对v3进行了操作的然后传入参数为a2,我们先分析该函数对v3指针做了一些什么操作 ### sub_400C1E ~~~c __int64 __fastcall sub_400C1E(__int64 a1) { __int64 result; // rax *(_QWORD *)a1 = off_4010A8; *(_QWORD *)(a1 + 8) = 0LL; *(_BYTE *)(a1 + 16) = 0; *(_BYTE *)(a1 + 17) = 0; *(_BYTE *)(a1 + 18) = 0; *(_DWORD *)(a1 + 20) = 0; *(_QWORD *)(a1 + 24) = 0LL; result = a1; *(_QWORD *)(a1 + 32) = 0LL; return result; } ~~~ 可以看到这个就是以a1为基地址,然后对一些偏移量进行了赋值操作,我们点开这个off_4010A8看看里面是一些什么东西 ~~~assembly .rodata:00000000004010A8 06 08 40 00 00 00 00 00 off_4010A8 dq offset sub_400806 ; DATA XREF: sub_400C1E+8↑o .rodata:00000000004010B0 7C 0C 40 00 00 00 00 00 dq offset sub_400C7C .rodata:00000000004010B8 9A 0C 40 00 00 00 00 00 dq offset sub_400C9A .rodata:00000000004010C0 B8 0C 40 00 00 00 00 00 dq offset sub_400CB8 .rodata:00000000004010C8 D6 0C 40 00 00 00 00 00 dq offset sub_400CD6 .rodata:00000000004010D0 FA 0C 40 00 00 00 00 00 dq offset sub_400CFA .rodata:00000000004010D8 1E 0D 40 00 00 00 00 00 dq offset sub_400D1E .rodata:00000000004010E0 42 0D 40 00 00 00 00 00 dq offset sub_400D42 .rodata:00000000004010E8 56 0D 40 00 00 00 00 00 dq offset sub_400D56 .rodata:00000000004010F0 70 0D 40 00 00 00 00 00 dq offset sub_400D70 .rodata:00000000004010F8 84 0D 40 00 00 00 00 00 dq offset sub_400D84 .rodata:0000000000401100 B0 0D 40 00 00 00 00 00 dq offset sub_400DB0 .rodata:0000000000401108 DC 0D 40 00 00 00 00 00 dq offset sub_400DDC .rodata:0000000000401110 56 0E 40 00 00 00 00 00 dq offset sub_400E56 .rodata:0000000000401118 D0 0E 40 00 00 00 00 00 dq offset sub_400ED0 ~~~ 是一堆函数的地址表,那么显然,该虚拟机就是通过a1进行取址然后调用函数,对栈空间,寄存器之类的东西进行操控,我们首先看到第一个函数 ### sub_400806 ~~~c __int64 __fastcall sub_400806(__int64 a1, __int64 a2, __int64 a3, __int64 a4) { *(a1 + 8) = a2 + 9; *(a1 + 24) = a3; *(a1 + 32) = a4; while ( 2 ) { switch ( **(a1 + 8) ) { case 0xA0: (*(*a1 + 8LL))(a1); continue; case 0xA1: (*(*a1 + 16LL))(a1); continue; case 0xA2: (*(*a1 + 24LL))(a1); *(a1 + 8) += 11LL; continue; case 0xA3: (*(*a1 + 32LL))(a1); *(a1 + 8) += 2LL; continue; case 0xA4: (*(*a1 + 40LL))(a1); *(a1 + 8) += 7LL; continue; case 0xA5: (*(*a1 + 48LL))(a1); ++*(a1 + 8); continue; case 0xA6: (*(*a1 + 56LL))(a1); *(a1 + 8) -= 2LL; continue; case 0xA7: (*(*a1 + 64LL))(a1); *(a1 + 8) += 7LL; continue; case 0xA8: (*(*a1 + 72LL))(a1); continue; case 0xA9: (*(*a1 + 80LL))(a1); *(a1 + 8) -= 6LL; continue; case 0xAA: (*(*a1 + 88LL))(a1); continue; case 0xAB: (*(*a1 + 96LL))(a1); *(a1 + 8) -= 4LL; continue; case 0xAC: (*(*a1 + 104LL))(a1); continue; case 0xAD: (*(*a1 + 112LL))(a1); *(a1 + 8) += 2LL; continue; case 0xAE: if ( *(a1 + 20) ) return 0LL; *(a1 + 8) -= 12LL; continue; case 0xAF: if ( *(a1 + 20) != 1 ) { *(a1 + 8) -= 6LL; continue; } return 1LL; default: puts("cmd execute error"); return 0LL; } } } ~~~ 分析之后发现是一个典型的while+switch,利用传入的参数进行寻址。我们通过动态调试来查看指令运行的先后顺序。然后把表抄下来,发现是如下结果 **0xA9u 0xA3u 0xA5u 0xA6u 0xA4u 0xABu 0xA7u 0xAEu 0xA2u 0xADu 0xAFu** 然后我们再分析如何得出每一个语句是干啥的 这里我只举例一点,其他的都是类似操作。 我们首先输入32个字符躲避长度判断,通过断点跳转来到该函数 我们分析0xA0指令的操作方式 首先我们需要看到a1中存储的到底是什么东西。 unsigned char ida_chars[] = { 0xA8, 0x10, 0x40 }; 通过经验就可以发现这是小端序存储的一段地址为0x4010A8,那么我们就要知道该虚拟机的基地址为0x4010A8,看到0xA0 偏移量为8也就是0x4010b0我们跳转到该地址 ~~~ass .rodata:00000000004010B0 dq offset sub_400C7C ~~~ 此处就是调用了sub_400C7C函数,进去看看 ~~~c __int64 __fastcall sub_400C7C(__int64 a1) { __int64 result; // rax result = a1; ++*(a1 + 16); return result; } ~~~ 对a1地址偏移16进行了一个++操作 ~~~ass [heap]:00000000013BFEC0 db 0 ~~~ 本身该处是0,那么之前可以看到sub_400C1E函数对一堆偏移量进行了置0操作,这里猜测他们都是寄存器 那么a1+16也就是寄存器r1,a1+17就是寄存器r2 那么结合上面在总结一下就可以得到如下指令表 | 操作码 | 对应指令集合 | | :------: | :---------------: | | *(a1+16) | 寄存器r1(占1字节) | | *(a1+17) | 寄存器r2(占1字节) | | *(a1+18) | 寄存器r3(占1字节) | | *(a1+19) | 寄存器r4(占1字节) | | *(a1+20) | 寄存器r5(占4字节) | | 0xA0 | r1++ | | 0xA1 | r2++ | | 0xA2 | r3++ | | 0xA3 | r1 -= r3 | | 0xA4 | r1 ^= r2 | | 0xA5 | r2 ^= r1 | | 0xA6 | r1 = 0xCD | | 0xA7 | r2 = r1 | | 0xA8 | r3 = 0xCD | | 0xA9 | r1 = input[r3] | | 0xAA | r2 = input[r3] | | 0xAB | func1() | | 0xAC | func2() | | 0xAD | func3() | | 0xAE | 判断r5的值 | | 0xAF | 判断r5的值 | 然后我们看到函数中的a4就是我们输入的值,然后再看到函数中的a3有一串字符和我们输入的字符长度一样,那么肯定是我们的check字符 ### exp 然后我们写一个反编译代码,就是通过之前的指令操作,进行反编译 ~~~python opcode = [0xa9, 0xa3, 0xa5, 0xa6, 0xa4, 0xab, 0xa7, 0xae, 0xa2, 0xad, 0xaf] for i in opcode: if i == 0xa0: print("r1++") if i == 0xa1: print("r2++") if i == 0xa2: print("r3++") if i == 0xa3: print("r1 -= r3") if i == 0xa4: print("r1 ^= r2") if i == 0xa5: print("r2 ^= r1") if i == 0xa6: print("r1 = 0xcd") if i == 0xa7: print("r2 = r1") if i == 0xa8: print("r3 = 0xcd") if i == 0xa9: print("r1 = input[r3]") if i == 0xaa: print("r2 = input[r3]") if i == 0xab: print("fun1()") if i == 0xac: print("func2()") if i == 0xad: print("func3()") if i == 0xae: print("if(r5==0)") if i == 0xaf: print("if(r5!=1)") ~~~ 输出结果为: ~~~python r1 = input[r3] r1 -= r3 r2 ^= r1 r1 = 0xcd r1 ^= r2 fun1() r2 = r1 if(r5!=0) r3++ func3() if(r5!=1) ~~~ 然后我们根据该逻辑写一个解密exp ~~~pytho res = [0xF4, 0x0A, 0xF7, 0x64, 0x99, 0x78, 0x9E, 0x7D, 0xEA, 0x7B, 0x9E, 0x7B, 0x9F, 0x7E, 0xEB, 0x71, 0xE8, 0x00, 0xE8, 0x07, 0x98, 0x19, 0xF4, 0x25, 0xF3, 0x21, 0xA4, 0x2F, 0xF4, 0x2F, 0xA6, 0x7C] flag = '' temp = 0 for i in range(0,32): flag += chr((temp ^ res[i] ^ 0xcd) + i) temp = res[i] print(flag) ~~~ 得到flag:942a4115be2359ffd675fa6338ba23b6
回复
举报
使用道具
分享
提升卡
置顶卡
沉默卡
喧嚣卡
变色卡
千斤顶
照妖镜
上一篇:
Reverse ezandroid 题目解析
下一篇:
被修改了的程序WP
全部回复
暂无回帖,快来参与回复吧
返回列表
发新帖
本版积分规则
高级模式
B
Color
Image
Link
Quote
Code
Smilies
您需要登录后才可以回帖
登录
|
立即注册
SHangwendada
注册会员
主题
1
回复
4
粉丝
1
加好友
发私信
热点排行
1
题目:再签到一次
2
【新平台】回复换FLAG啦!
3
【活动】你知道小光的答案吗?
4
【活动】小光的答案之书
5
LiHua's checkme WP
6
入门需要怎么做?
7
取证-SSH-04
8
misc中怎么分辨各工具加密后的数据
9
sqlmap连接不到目标RUL
10
因为是一个小白,有一道题不会 请各位大佬 出手帮一下 misc的
快速回复
返回顶部
返回列表