虚拟化保护合集

VMP合集

(阶段性输出)代码虚拟化保护

题目清单

  • DDCTF2018 黑盒破解
  • RCTF2018 Simple vm
  • RCTF2018 Magic
  • 护网杯2018 rerere

原理介绍

基本原理

虚拟机保护技术是指,将程序代码转换为自定义的中间操作码(OpCode,当操作码只占一个字节可称作ByteCode),OpCode通过一种解释执行系统或者模拟器(Emulator)解释执行,实现代码基本功能。

逆向这种程序,一般需要对emulator结构进行逆向,结合opcode进行分析,得到各个操作码对应的基本操作,从而理解程序功能。(图片来自:https://mp.weixin.qq.com/s/4Nfso1OuHeQgCTGYv2IF5Q)

JVM虚拟机

JVM虚拟可以提供一种与平台无关的编程环境,这是虚拟化思想的一种成功应用。下图为JVM虚拟机的基本架构(图片来自:geeksforgeeks )

VMP虚拟机

VMP虚拟机保护技术指的是将基于x86汇编系统的可执行代码转换为字节码指令系统的代码,达到保护原有指令不被轻易逆向和修改的目的。从本质上来说,就是创建一套虚拟指令系统对原本的x86汇编指令系统进行一次封装,将原本的汇编指令转换为另一种表现形式。

虚拟指令有自己的机器码,但和原本的x86汇编机器码完全不一样,而且常常是一堆无意义的代码,他们只能由VM虚拟解释器(Dispatcher)来解释并执行。我们在逆向时看到的汇编代码其实不是x86汇编代码,而是字节码(伪指令),它是由指令执行系统定义的一套指令和数据组成的一串数据流,所以虚拟机的脱壳很难写出一个通用的脱壳机,原则上只要虚拟指令集一变动,原本的伪指令的解释就会发生变化。

要逆向被VM SDK保护起来的原始代码,只有手工分析这段虚拟指令,找到虚拟指令和原始汇编的对应关系,然后重写出原始程序的代码,完成算法的逆向和分析。

详情待施工……

题目

DDCTF2018 黑盒破解

解题的基本思路是通过分析二进制文件,得到opcode及其对应的基本操作,通过构造passcode,令程序输出binggo字样。

前期准备分析

拿到文件完成基本分析后加载到IDA。

首先通过字符串搜索,并交叉引用定位到主函数,分析程序的基本逻辑:输入password(即给的txt文件名中的数字),然后输入passcode,令程序输出Binggo。

main函数关键判断如下

对flag查找引用发现一个函数,但继续查找引用遇到障碍,所以开动态调试,看一下程序运行的情况。发现sub_401A48可能是一个重要函数,进入查看。

这段代码F5之后不是很友好,但还是看得到大概意思是:根据输入参数调用函数,也就是虚拟机的Dispatcher位置,被调用的各个函数就是虚拟机的各个Handler。下图的汇编更加直接的展示了这个意思,call eax表示调用Handler。

得到passcode与其对应的操作

在这段调用Handel的CFG块上方是判断是否会执行调用的关键代码。根据汇编代码推断算法,并写出idc脚本。在推断算法时要注意根据动态调试来确定各个值得变化,不然很容易出错。

idc脚本如下,要注意这个脚本中的值0x123E010在每一次执行过程中会发生变化,因为它是输入参数存放的地址,具体方法是动态调试执行到参数存放时得到地址值,再根据它修改脚本。爆破出各种可能的函数跳转。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<idc.idc>
static main(){
auto i,j,p,q,v14;
for(i = 0;i<8;i++){
p = Byte(0x123E010+4*(i+72)+8);
v14 = Dword(0x123E010+8*(p+0x54)+8);
Message("%x",p);
for(j = 0 ;j<255;j++){
if(Byte(0x603900+j)==Byte(0x123E010+p+0x198)){
q = j;
break;
}
}
Message("%x %c %x\n",q , q, v14);
}
}

运行结果如下


1324 $ 400dc1
9738 8 400e7a
1943 C 400f3a
774 t 401064
c730 0 4011c9
d545 E 40133d
c875 u 4012f3
1423 # 4014b9


现在要开始分析各个handler的作用,最为特殊的一个handler是sub_40133d,进入这个函数后发现代码如下:

这段代码有个地方比较坑,其实这个将flag置1并不是问题的关键,这个题目要求的是输出“Binggo”字样,所以这里的三个函数并不要去逆向分析,关键在于前面几句,将a1+0x120里的20个字节输出,我们要做的就是构造输出的字符串。在构造之前我们先要分析出其他handler的作用才能根据这些handler构造字符串。

各个handler的分析过程比较痛苦,但在分析时要有一个意识,虚拟机的堆栈或寄存器是创建在真实堆栈之上的,比如说在这个题目中有很多类似如下的代码。

1
mov     edx, [rax+120h]

其实这当中的[rax+120h]只是模拟一个变量或者一个寄存器,在整个handler中一共用到了四个变量,下面是四个变量的枚举,以及经过分析得出的四变量的作用。


[rax+120h] –> 当前指针位置a[index]

[rax+298h] –> 当前字符参数的下一个字符passcode[next]

[rax+299h] –> 临时变量temp


最终得到下表

opcode 功能
$ temp = a[index]
8 a[index] = temp
C temp += passcode[next] -33
t temp -= passcode[next] +33
0 index++
E if(passcode[next]==’s’) print(a)
u index–
# temp = passcode[temp+passcode[next]-48]-49
构造passcode

根据上表构造passcode,这里要注意,由于每次取到的a[index]无法静态分析获得,只能每构造一个字符,再动态调试获得下一个a[index]。最后要注意由于输出handler是输出20个字符,而Binggo只有六个字符,所以要注意构造第七个字符加上’\x00’使其截断。构造方式有很多种,下面是一种从前向后的构造方法。


$ –> 取得a[index]的值为50h

t/ –> 将50h变为’B’

8 –> 将’B’放回到a[index]

0 –> index++


重复上述操作得到”Binggo”字符串,对应的passcode为’$t/80$C)80$CI80$CX80$Cg80$Cj80 ‘,然后再通过’#J1’构造’\x00’最后将index移回字符串开头并输出对应passcode为’uuuuuuuEs ‘。

passcode:

$t/80$C)80$CI80$CX80$Cg80$Cj80#J1uuuuuuuEs

RCTF2018 Simple vm

方法一

这道题目给出了Emulator和虚拟机的opcode,通过逆向分析Emulator中各个handler的作用以及opcode的意义推断正确输入从而得到flag

分析

运行vm_rel看一下程序要求,发现是要猜输入

把vm_rel拖入IDA里面进行字符串搜索,并未发现“Input Flag”等字符串。进入主函数看代码流程,猜测vm_rel其实是虚拟机emulator,p.bin才是真正的函数。

把p.bin拖到hexwin里面,发现字符串“Input Flag”、“Wrong”、“Right”,证实上述猜测。

动态调试对p.bin的行为进行分析

注意输出”Input Flag“和接受输入部分的代码可以直接在Pseudocode窗口调试,分别将断点下在putchar()和getchar()处,通过观察寄存器edx的值来推测循环次数。但是在调试输入字符串处理和判断结果并输出部分,最好在汇编窗口调试,观察值的变化,便于找到比较字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//输出“Input Flag”部分的代码
next = *(_DWORD *)&pcode[next];
v18 = next;
next += 4;
v10 = *(_DWORD *)&pcode[v18];
dword_6010A4 = v10;
//循环11次
++dword_6010A4;
v6 = dword_6010A4;
v14 = (char)pcode[v6];
c = v14;
putchar(c);
v15 = &pcode[*(signed int *)&pcode[next]];
if ( *v15 ){
next = *(_DWORD *)&pcode[next + 4];
--*v15;
}else{
next += 8;
}
v18 = next;
next += 4;
v10 = *(_DWORD *)&pcode[v18];
dword_6010A4 = v10;
//接受输入部分的代码,输入字符串长度为0x1F
//接受的字符串存储在\xF81362的地址里(地址每次调试不同),统一称为*input
++dword_6010A4;
v14 = getchar();
c = v14;
v8 = dword_6010A4;
v12 = c;
pcode[v8] = v12;
v15 = &pcode[*(signed int *)&pcode[next]];
if ( *v15 ){
next = *(_DWORD *)&pcode[next + 4];
--*v15;
}else{
next += 8;
}
//处理输入;处理后的字符串统一称为*output
temp1 = ~(input[i]&(32+i));
temp2 = ~(temp1&input[i]);
temp3 = ~(temp1&(32+i));
output = ~(temp2&temp3);
//上述四条命令相当于xor(input[i]&(32+i))
//判断结果并输出
cmp(output,0x1018431415474017101D4B121F49481853540157515305565A08585F0A0C5809)
求解

理清楚上述逻辑之后就编写程序求解。

1
2
3
4
5
6
7
8
9
#include<stdio.h>
int main()
{
char str[] = {0x10,0x18,0x43,0x14,0x15,0x47,0x40,0x17,0x10,0x1D,0x4B,0x12,0x1F,0x49,0x48,0x18,0x53,0x54,0x01,0x57,0x51,0x53,0x05,0x56,0x5A,0x08,0x58,0x5F,0x0A,0x0C,0x58,0x09};
int i;
for(i = 0;i<32;i++){
printf("%c",str[i]^(32+i))
}
}

09a71bf084a93df7ce3def3ab1bd61f6

方法二

直接用angr解

待施工……

RCTF2018 Magic

这道题涉及到寻找程序入口、打补丁、vm保护、rc4等知识点,基本思路是首先找到程序入口,发现存在一个时间验证和一个输入字符串验证,时间验证的算法十分复杂,可以直接逆也可以调用源程序算法爆破,爆破出时间之后,给程序打好补丁继续调试发现还有一个输入验证,这个输入验证首先对输入进行rc4编码,然后用vm进行加密,最后进行对比,在破解时,先破解虚拟机emulator,然后根据它的opcode推断出vm保护代码,得到rc4加密后的字符串,最后利用rc4加解密算法一致的方式,直接利用源程序得到解密就可以得到部分flag,将着部分flag输入patch过的程序得到另一半flag。

寻找入口函数

运行程序发现如下信息

1
2
3
C:\CTF\tmp\VM>magic.exe
flag only appears at a specific time, range [2018-05-19 09:00, 2018-05-21 09:00)
Better luck next time :)

拖入IDA中,查找字符串,返现与上述输出相关的信息,但是交叉引用跳转到的main函数并无相关输出。在main函数上下断点,动态调试发现在执行main函数之前已经输出,所以判断这个main函数并不是真正的程序入口。交叉引用找到调用main的函数sub_4011B0,在sub_4011B0开始设一个断点,在main函数开始设一个断点,然后利用IDA函数跟踪功能找到这两个断点之间调用puts函数的的函数sub_402218。往上看可以看到函数sub_402357调用了sub_402218,再往上没有找到sub_402357的调用信息。初步判定这里就是入口函数。

时间验证

打开sub_402357发现如下关键判断:

对dword_4099D0[0]交叉引用找到如下函数,将其命名为check_time。要想执行main函数必须使得dword_402357的值为1,进入check_time函数,发现代码逻辑为:首先返回在(0x5B028A8F, 0x5AFFE78F]之间的的时间戳,以时间戳为种子取随机数对Table表做异或运算,再通过sub_4027ED对Table表进行变化,并返回v4和v3。如果想达到前文阐述的目的,只有让v4 == 0x7000 & v3 == 0条件成立。

进入sub_4027E0函数发现反编译代码十分奇怪,可以选择直接看汇编,但我在https://www.52pojie.cn/thread-742361-1-1.html这篇文章中,发现了一种调用原程序的方法,暴力求解time的方法。这个方法十分巧妙的借用源程序中的算法和数据,得到满足条件的time,程序如下:

下面代码还存在问题,待施工……

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
typedef unsigned int(*test)();
static UINT time = 0x5AFFE78F + 1;
UINT myfun(int) { //通过这种形式遍历每一个time
return time++;
}
char e1_copy[256] = {0};
int main()
{
UINT64 * ptr1 = (UINT64 *)0x40A38C;//time64
UINT64 * ptr2 = (UINT64 *)0x40A414; //srand
UINT64 * ptr3 = (UINT64 *)0x40A3FC;//rand
UINT64 * ptr4 = (UINT64 *)0x40A3DC;//memset
HMODULE h = LoadLibraryA("D:\\magic.exe");
memcpy(e1_copy, (void *)0x405020, 256); //备份E1表,重新运算的时候需要还原E1表
test test1 = (test)0x402268;
*ptr1 = (UINT64)myfun;
*ptr2 = (UINT64)srand;
*ptr3 = (UINT64)rand;
*ptr4 = (UINT64)memset;
UINT val;
while (true)
{
memcpy((void *)0x405020, e1_copy, 256); // 重置E1表
val = test1();
if (val != 0)
{
printf("time:%x\nkey:%x", time -1 , val); //0x322ce7a4
//time:5b00e398
//key: 322ce7a4
break;
}
}
return 0;
}

得到满足条件的time为:0x5b00e398,对程序打个补丁,跳过if时间戳合法判断,将srand的参数修改为我们爆破出来的time,汇编代码修改如下,将补丁应用到原有的二进制文件上的步骤为Edit->Patch Program/Apply patchs to input file…

给程序打好补丁之后,在这个函数结束的地方打上断点,继续观察程序的运行情况。

输入验证

调用check_time的函数sub_402357在执行完成之后回到函数sub_4032A0,经过几次跳转来到函数sub_403180,发现onexit()函数,这个函数的作用是注册一个函数,使得程序在exit()的时候自动调用这个被注册的函数。

执行完这个函数之后,程序终于开始执行main函数,main函数里面没有什么特别重要的内容,两个条件跳转都不会发生,那么肯定是执行了前面onexit()注册的函数,继续F8,跳转到一个有用函数sub_403260,这个函数中的call rax调用了一个关键函数sub_4023B1如下

有些变量名称已经根据其作用有些修改,这个check_rc4其实就是一个简单的rc4加密的过程,它首先生成s盒然后对输入input进行加密,在经过一次rc4后有一个if判断,这个if判断里的函数其实是虚拟机入口,这个虚拟机实现的时候用到了setjmp/longjmp,相关知识在这里:http://www.cs.cmu.edu/afs/cs/academic/class/15213-s02/www/applications/recitation/recitation7/B/r07-B.pdf

在利用setjmp/longjmp实现vm保护时,有一个地方特别巧妙,一开始vm_run函数首先将处理过的输入input复制到Count中,然后用signal注册一个函数。

它注册的函数如下

而在各个handler中可以看到如下一个操作

可以看到这里面有除法操作,而除数为0的时候会引起异常,这个时候就会转到signal注册的函数signfunc中去执行操作,执行完之后再返回到循环中去,取下一步操作。

其他handler作用的分析比较简单这里不赘述,0x405340处存储的是要执行的opcode,以下是对op凑得的分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
> AB 03 00   reg[3] = 0 
> AB 04 1A reg[4] = 0x1A
> AB 00 66 reg[0] = 0x66
> AA 05 02 reg[5] = reg[2] ;input_rc4ed
> A9 53 reg[5] += reg[3]
> A0 05 reg[5] = *(byte)reg[5] ;input_rc4ed[i]
> AB 06 CC reg[6] = 0xCC
> A9 56 reg[5] += reg[6]
> AB 06 FF reg[6] = 0xFF
> AC 56 reg[5] &= reg[6]
> AE 50 reg[5] ^= reg[0]
> AD 00 reg[0] = ~reg[0]
> AA 06 05 reg[6] = reg[5]
> AA 05 01 reg[5] = reg[1] ;cmpstr
> A9 53 reg[5] += reg[3]
> A0 05 reg[5] = *(byte)reg[5] ;cmpstr[i]
> AF 56 00 reg[5] = (reg[5] == reg[6])
> A7 01 if (reg[5] == 1) vip += 1
> CC
> A9 35 reg[3] += reg[5]
> AA 05 03 reg[5] = reg[3]
> AF 54 00 reg[5] = (reg[5] == reg[4])
> A6 D1 if (reg[5] == 0) vip +=0xD1
> CC
>

根据上述分析得到的opcode写出爆破求解程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<stdio.h>
int main()
{
char cmpstr[] = { 0x89, 0xC1, 0xEC, 0x50, 0x97, 0x3A, 0x57, 0x59, 0xE4, 0xE6, 0xE4, 0x42, 0xCB, 0xD9, 0x08, 0x22, 0xAE, 0x9D, 0x7C, 0x07, 0x80, 0x8F, 0x1B, 0x45, 0x04, 0xE8 };
char temp = 0x66;
for (int i = 0; i < 26; i++) {
for (int j = 0; j < 255; j++) {
char t = ((j + 0xcc) & 0xFF) ^ temp;
if (cmpstr[i] == t) {
printf("0x%x ", j);
}
}
temp = (~temp)&0xff;
}
}

得到的结果如下

1
0x23 0x8c 0xbe 0xfd 0x25 0xd7 0x65 0xf4 0xb6 0xb3 0xb6 0xf 0xe1 0x74 0xa2 0xef 0xfc 0x38 0x4e 0xd2 0x1a 0x4a 0xb1 0x10 0x96 0xa5

由于rc4加解密过程一样,所以把上述字符串作为输入,利用源程序的rc4的密钥求解,把上述字符串作为输入可以通过脚本修改,脚本来自http://ahageek.com/blog/rctf2018-magic-writeup/

1
2
3
4
5
6
7
from idaapi import *
rc4ed = [0x23,0x8c,0xbe,0xfd,0x25,0xd7,0x65,0xf4,0xb6,0xb3,0xb6,0xf,0xe1,0x74,0xa2,0xef,0xfc,0x38,0x4e,0xd2,0x1a,0x4a,0xb1,0x10,0x96,0xa5]
rcx = idc.GetRegValue('rcx')
i = 0
for addr in range(rcx, rcx + 0x1A):
idc.PatchByte(addr, rc4ed[i])
i = i + 1

最后得到字符串如下

@ck_For_fun_02508iO2_2iOR}

在打过补丁的程序中输入上述flag,得到另一半flag:rctf{h

综上整个flag为:rctf{h@ck_For_fun_02508iO2_2iOR}

护网杯2018 rerere

程序流程分析

通过字符串搜索定位到主函数,发现如下代码。

1539654741030

上述代码是对输入参数长度的判断,长度必须要大于0x30,于是输入参数

1539654860058

在主函数中存在两个结构体函数

1
2
3
(*(void (__stdcall **)(_DWORD *, int))(*(_DWORD *)Memorya + 104))(v7, v4);
(*(void (**)(void))(*(_DWORD *)Memorya + 112))();
(*(void (__cdecl **)(_DWORD *))(*(_DWORD *)Memorya + 108))(v7);

动态调试进入这两个函数,发现第二个函数结构如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
int __thiscall VMDispatcher(unsigned __int8 **this)
{
unsigned __int8 **v1; // esi
int result; // eax

v1 = this;
while ( 1 )
{
result = *v1[9];
switch ( result )
{
case 0x43:
return result;
case 0x44:
(*((void (__thiscall **)(unsigned __int8 **))*v1 + 18))(v1);
break;
case 0x45:
(*((void (__thiscall **)(unsigned __int8 **))*v1 + 11))(v1);
break;
case 0x46:
(*((void (__thiscall **)(unsigned __int8 **))*v1 + 14))(v1);
break;
case 0x47:
(*((void (__thiscall **)(unsigned __int8 **))*v1 + 8))(v1);
break;
case 0x48:
(*((void (__thiscall **)(unsigned __int8 **))*v1 + 22))(v1);
break;
case 0x49:
(*((void (__thiscall **)(unsigned __int8 **))*v1 + 23))(v1);
break;
case 0x4A:
(*((void (__thiscall **)(unsigned __int8 **))*v1 + 9))(v1);
break;
case 0x4B:
(*((void (__thiscall **)(unsigned __int8 **))*v1 + 20))(v1);
break;
case 0x4C:
(*((void (__thiscall **)(unsigned __int8 **))*v1 + 25))(v1);
break;
case 0x4D:
(*((void (__thiscall **)(unsigned __int8 **))*v1 + 19))(v1);
break;
case 0x4E:
(*((void (__thiscall **)(unsigned __int8 **))*v1 + 4))(v1);
break;
case 0x4F:
(*((void (__thiscall **)(unsigned __int8 **))*v1 + 15))(v1);
break;
case 0x50:
(*((void (__thiscall **)(unsigned __int8 **))*v1 + 7))(v1);
break;
case 0x51:
(*((void (__thiscall **)(unsigned __int8 **))*v1 + 13))(v1);
break;
case 0x52:
(*((void (__thiscall **)(unsigned __int8 **))*v1 + 12))(v1);
break;
case 0x53:
(*((void (__thiscall **)(unsigned __int8 **))*v1 + 5))(v1);
break;
case 0x54:
(*((void (__thiscall **)(unsigned __int8 **))*v1 + 16))(v1);
break;
case 0x55:
(*((void (__thiscall **)(unsigned __int8 **))*v1 + 21))(v1);
break;
case 0x56:
(*((void (__thiscall **)(unsigned __int8 **))*v1 + 24))(v1);
break;
case 0x57:
(*((void (__thiscall **)(unsigned __int8 **))*v1 + 17))(v1);
break;
case 0x58:
(*((void (__thiscall **)(unsigned __int8 **))*v1 + 10))(v1);
break;
case 0x59:
(*((void (__thiscall **)(unsigned __int8 **))*v1 + 6))(v1);
break;
default:
(*((void (__thiscall **)(unsigned __int8 **))*v1 + 2))(v1);
break;
}
}
}

非常像dispatcher,初步判断这是一个vm。

VM结构分析

下图为VM初始化,this[1]~this[5]为寄存器,初始值为0;this[6]为输出参数字符串;this[9]为opcode。

1539592784926

然后是各个handler的分析,结果如下

操作码(操作数) 功能 说明
0x43 exit() 退出循环
0x53(a) this[a2] += this[a1]
0x59(a) this[a2] -= this[a1]
0x58(a) this[a2] *= this[a1]
0x45(a) this[a2] /= this[a1]
0x50(a) this[a2] = this[a1] + 1
0x4E(a) this[a2] = this[a1] - 1
0x47(a) this[a2] ^= this[a1]
0x4A(a) this[a2] &= this[a1]
0x52(a) this[8] = this[a1]
0x4F(a,b,c,d) this[8] = abcd
0x54(a) this[a2] = this[8]
0x51(a) result = this[a2]
0x46(a) this[a2] = this[6]
0x57(a)
0x55(a) if(this[4]!=0) {this[4]–;this[9] - a;} 跳转指令
0x48(a) if(v1==v2){this[5]=0;}if(v1<v2){this[5]=-1;}if(v1>v2){this[5]=1;} v1=this[a1];v2=this[a2]
0x44(a) if(this[5]==-1){this[9] + a;} 跳转指令
0x4D(a) if(this[5]==1){this[9] + a;} 跳转指令
0x4B(a) if(this[5]==0){this[9] + a;} 跳转指令
0x49() this[6]++
0x56() this[6]–
0x4C()

tips:

a1 = (a&0xF)+1 ; a2 = (a>>4)+1

this[9]指向下一条要执行的语句

最后对opcode进行分析,得到vm保护部分的代码含义。

第一段代码的含义是限制输入为[0~9][A~F]。

后几段是将48位输入分为6段,每段8byte,但是要注意是从后往前进行比较的。下面是以第二段为例的具体分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
> code2:
> 55 40 ;if(this[4]!=0){this[4]--;this[9]-0x40;}
> 4F 00 00 00 07 54 30 ;this[4]=0x7
> 47 11 ;this[2]^=this[2]
> t_code1:
> 56 ;index--
> 46 00 ;this[1]=input[index]
> 4F 00 00 00 30 54 20 ;this[3]=0x30
> 59 02 ;this[1]-=this[3]
> 4F 00 00 00 0A 54 20 ;this[3]=0xa
> 48 02 44 09 ;if(this[3]<this[1])jmp t_code2
> 4F 00 00 00 07 54 20 ;this[3]=0x7
> 59 02 ;this[1]-=this[3]
> t_code2:
> 4F 00 00 00 10 54 20 ;this[3]=0x10
> 58 12 ;this[2]*=this[3]
> 53 10 ;this[2]+=this[1]
> 55 2B ;if(this[4]!=0){this[4]--;jmp t_code1;}
> 4F 33 B4 88 AC 54 20 ;this[3]=0x33B488AC
> 48 12 47 00 4B 03 ;if(this[3]==this[2])jmp code3;this[1]^=this[1]
> 50 00 ;this[1]++
> 43 ;exit()
>
解密得flag

最后用python编写解密代码得到输入

1
2
C:\CTF\Challenge\Binary_sec\Reverse>task_huwang-refinal-4.exe E25BD838D2B62FE1B1579293CECDC5C7F349B0A4CA884B33
Great! Add flag{} to hash and submit

flag如下

flag{E25BD838D2B62FE1B1579293CECDC5C7F349B0A4CA884B33}

参考内容

https://mp.weixin.qq.com/s/4Nfso1OuHeQgCTGYv2IF5Q

https://www.anquanke.com/post/id/145553

https://blog.csdn.net/liutianshx2012/article/details/48466327?locationNum=7&fps=1

http://ahageek.com/blog/rctf2018-magic-writeup/

https://www.52pojie.cn/thread-742361-1-1.html

https://www.xctf.org.cn/media/infoattach/b805a17b85c54091975aab3709b7a5bb.pdf

http://www.cs.cmu.edu/afs/cs/academic/class/15213-s02/www/applications/recitation/recitation7/B/r07-B.pdf

0%