• 2020.05.05 嗯, 立下flag, 有空一定补其他题

magic

题目:magic

本题大概有三个任务: 1.找到真正的main函数

2.算出运行时间

3.RC4+VM逆向还原

寻找真正的main函数
拿到题, 运行一下, 输出如下:

D:\Downloads>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函数。

// write access to const memory has been detected, the output may be wrong!
int sub_4011B0()
{
char *v0; // rdx
signed __int64 v2; // rsi
signed __int64 v3; // rax
void *v4; // rsi
signed int v5; // ebp
__int64 v6; // rdx
char v7; // cl
char *v8; // rax
char v9; // dl
signed int v10; // eax
int v11; // ebx
signed __int64 v12; // r12
_QWORD *v13; // rax
char *v14; // rdi
size_t v15; // rbp
__int64 v16; // rax
__int64 v17; // rbx
signed __int64 v18; // r13
__int64 v19; // rcx
size_t v20; // rdx
int result; // eax
char v24; // [rsp+20h] [rbp-A8h]
char v25; // [rsp+5Ch] [rbp-6Ch]
unsigned __int16 v26; // [rsp+60h] [rbp-68h]

v0 = &v24;
memset(&v24, 0, 0x68uLL);
if ( unk_409670 )
GetStartupInfoA((LPSTARTUPINFOA)&unk_409670);
_RBX = &unk_409AF8;
v2 = *(_QWORD *)(__readgsqword(0x30u) + 8);
while ( 1 )
{
v3 = _InterlockedCompareExchange((volatile signed __int64 *)&unk_409AF8, v2, 0LL);
if ( !v3 )
{
v4 = &unk_409AF0;
v5 = 0;
if ( unk_409AF0 == 1 )
goto LABEL_42;
goto LABEL_8;
}
if ( v2 == v3 )
break;
Sleep((unsigned __int64)&unk_409670);
}
v4 = &unk_409AF0;
v5 = 1;
if ( unk_409AF0 == 1 )
{
LABEL_42:
amsg_exit(&unk_409670, &unk_409AF0, v0, 31LL);
if ( unk_409AF0 == 1 )
goto LABEL_43;
LABEL_11:
if ( v5 )
goto LABEL_12;
goto LABEL_44;
}
LABEL_8:
if ( unk_409AF0 )
{
dword_409004 = 1;
}
else
{
unk_409AF0 = 1;
initterm(&unk_409670, &unk_409AF0, &unk_40B030, &unk_40B018);
}
if ( unk_409AF0 != 1 )
goto LABEL_11;
LABEL_43:
initterm(&unk_409670, &unk_409AF0, &unk_40B010, &unk_40B000);
unk_409AF0 = 2;
if ( v5 )
goto LABEL_12;
LABEL_44:
_RAX = 0LL;
__asm { xchg rax, [rbx] }
LABEL_12:
if ( TlsCallback_0 )
TlsCallback_0(&unk_409670, &unk_409AF0, 2LL, 0LL, 0LL);
sub_403940(&unk_409670, &unk_409AF0, v0);
qword_4096A0 = (__int64)SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)&unk_409670);
sub_403DA0();
sub_404850(&unk_409670, &unk_409AF0, v6, nullsub_1);
sub_403740();
v7 = 0;
qword_4099C8 = 0x400000LL;
v8 = (char *)acmdln;
if ( acmdln )
{
while ( 1 )
{
v9 = *v8;
if ( *v8 <= 32 )
{
if ( !v9 || !(v7 & 1) )
{
if ( v9 )
{
do
++v8;
while ( *v8 && *v8 <= 32 );
}
qword_4099C0 = (__int64)v8;
break;
}
v7 = 1;
}
else if ( v9 == 34 )
{
v7 ^= 1u;
}
++v8;
}
}
if ( unk_409670 )
{
v10 = 10;
if ( v25 & 1 )
v10 = v26;
dword_405000 = v10;
}
v11 = dword_409020;
v12 = 8LL * (dword_409020 + 1);
v13 = malloc((size_t)&unk_409670);
v14 = (char *)Code;
v15 = (size_t)v13;
if ( v11 > 0 )
{
v16 = (unsigned int)(v11 - 1);
v17 = 0LL;
v18 = 8 * v16 + 8;
do
{
v19 = *(_QWORD *)&v14[v17];
v4 = (void *)(strlen(v14) + 1);
*(_QWORD *)(v15 + v17) = malloc((size_t)v14);
v20 = *(_QWORD *)&v14[v17];
v17 += 8LL;
memcpy(v14, v4, v20);
}
while ( v18 != v17 );
v13 = (_QWORD *)(v15 + v12 - 8);
}
*v13 = 0LL;
Code = v15;
sub_403310((__int64)v14, (__int64 *)v4);//real入口点0.0
_initenv = qword_409010;
result = main((int)v14, (const char **)v4, (const char **)Code);
dword_40900C = result;
if ( !dword_409008 )
exit((int)v14);
if ( !dword_409004 )
{
cexit();
result = dword_40900C;
}
return result;
}

进去sub_403310函数:

__int64 __fastcall sub_403310(__int64 a1, __int64 *a2)
{
__int64 result; // rax

result = (unsigned int)dword_4090A0;
if ( !dword_4090A0 )
{
dword_4090A0 = 1;
result = sub_4032A0(a1, a2);//这里进去
}
return result;
}
__int64 __fastcall sub_4032A0(__int64 a1, __int64 *a2)
{
__int64 v2; // rdx
unsigned int i; // eax
__int64 *v4; // rbx

v2 = qword_4049A0[0];
i = qword_4049A0[0];
if ( LODWORD(qword_4049A0[0]) == -1 )
{
for ( i = 0; ; i = v2 )
{
v2 = i + 1;
if ( !qword_4049A0[v2] )
break;
}
}
if ( i )
{
v4 = &qword_4049A0[i];
a2 = &qword_4049A0[i - (unsigned __int64)(i - 1) - 1];
do
{
((void (__fastcall *)(__int64, __int64 *))*v4)(a1, a2);
--v4;
}
while ( v4 != a2 );
}
return sub_403240(a1, a2, v2, sub_403260);
}

qword_4049A0存放了两个函数的地址:

qword_4049A0    dq 0FFFFFFFFFFFFFFFFh, 402357h, 404990h, 0

进去sub_402357

__int64 __fastcall sub_402357(const char *a1, char **a2)
{
__int64 result; // rax
unsigned int v3; // ST2C_4

Time((__int64)a1, (__int64)a2);//第一步, 时间验证
result = dword_4099D0[0];
if ( !dword_4099D0[0] )
{
v3 = strtol(a1, a2, 0) ^ 0xBADD1917;
result = sub_402218((__int64)a1, (_DWORD)a2, 105, (unsigned __int64)&unk_405220);//输出flag only.....
}
return result;
}

Time正是这个程序的第一步验证:时间验证

而sub_402218则是输出信息的位置。

以上由调试可知, 就不赘述了。

时间验证

__int64 __fastcall Time(__time64_t *a1, __int64 a2)
{
__int64 result; // rax
unsigned int v3; // [rsp+20h] [rbp-10h]
int v4; // [rsp+24h] [rbp-Ch]
unsigned int v5; // [rsp+28h] [rbp-8h]
int i; // [rsp+2Ch] [rbp-4h]

v5 = time64(a1);
if ( v5 <= 0x5AFFE78F || v5 > 0x5B028A8F )
return 0LL;
srand((unsigned int)a1);
for ( i = 0; i <= 255; ++i )
byte_405020[i] ^= rand();
v4 = 0;
v3 = 0;
sub_4027ED((__int64)a1, a2, &v4, (__int64)byte_405020, &v3);
if ( v4 == 0x700 )
{
dword_4099D0[0] = v3;
result = v3;
}
else
{
dword_4099D0[0] = 0;
result = 0LL;
}
return result;
}

time64函数返回当前时间戳, 根据这段代码, 返回的时间戳应该在(0x5AFFE78F, 0x5B028A8F]这个范围内程序才会继续执行。

其后以时间戳做为随机数种子, 取随机数对byte405020数组异或运算, 再通过sub4027ED这个函数对byte_405020数组进行一些运算, 最终得到v4的值, 并判断v4的值是否等于0x700.

在后面的代码中可知, 如果result ==0, 那么程序将打印出

flag only appears at a specific time, range [2018-05-19 09:00, 2018-05-21 09:00)
Better luck next time :)

并退出。观察到0x5B028A8F - 0x5AFFE78F = 0x2A300, 所以time的范围算是比较小的了, 我们可以修改程序代码, 让程序自己跑出time。

修改如下:(IDA修改指令快捷键为Ctrl+Alt+K, 修改后patch上去即可。剩下的就看各位的汇编功底了!)

__int64 __fastcall sub_402268(__int64 a1, __int64 a2)
{
unsigned int v3; // edx
int v4; // [rsp+24h] [rbp-Ch]
int v5; // [rsp+28h] [rbp-8h]
int i; // [rsp+2Ch] [rbp-4h]

v5 = 0x5AFFE790;
do
{
if ( (unsigned int)++v5 > 0x5B028A8F )
return 0LL;
srand(a1);
for ( i = 0; i <= 255; ++i )
byte_405120[i] = rand() ^ byte_405020[i];//这里我取用了byte_405120来存放每次异或的值, 所以这个修改吧, 是永久的0.0, 要么另起一个程序要么记得这里改回来0.0
v4 = 0;
sub_4027ED(a1, a2, (__int64)&v4);
}
while ( v4 != 1792 );
dword_4099D0[0] = v3;
return 0LL;
}

在dword_4099D0[0] = v3;处下断, 即可跑出时间:0x5b00e398

为了接下来方便调试, 直接将v5设置为0x5b00e398, 即可进入第二步验证:输入验证

输入验证
经过调试, 找到输入验证的函数

__

int64 __fastcall sub_4023B1(const char *a1, __int64 a2)
{
__int64 v3; // [rsp+20h] [rbp-30h]
__int64 v4; // [rsp+28h] [rbp-28h]
__int64 v5; // [rsp+30h] [rbp-20h]
__int64 v6; // [rsp+38h] [rbp-18h]
unsigned int v7; // [rsp+43h] [rbp-Dh]
char v8; // [rsp+47h] [rbp-9h]
__int64 *Count; // [rsp+48h] [rbp-8h]

if ( !dword_4099D0[0] )
exit((int)a1);
v8 = 0;
v7 = dword_4099D0[0];
sub_402218(a1, a2, 49, (__int64)&unk_4052A0, dword_4099D0[0]);
v3 = 0LL;
v4 = 0LL;
v5 = 0LL;
v6 = 0LL;
Count = (__int64 *)((char *)&v3 + 4);
scanf(a1, a2, (char *)&v3 + 4, "%26s");
RC4((__int64)a1, a2, 26LL, (__int64)Count, (unsigned __int64)&v7);
if ( !(unsigned int)check((size_t)a1) )
return sub_402218(a1, a2, 6, (__int64)aC, dword_4099D0[0]);
RC4((__int64)a1, a2, 26LL, (__int64)Count, (unsigned __int64)&v7);
sub_401FFB(a1);
sub_402218(a1, a2, 35, (__int64)&unk_4052E0, dword_4099D0[0]);
puts(a1);
return sub_402218(a1, a2, 35, (__int64)&unk_4052E0, dword_4099D0[0]);
}

RC4

RC4的辨识度较高:

__int64 __fastcall RC4(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, unsigned int a6)
{
_QWORD *v6; // rdi
__int64 v8; // [rsp+0h] [rbp-80h]
char v9; // [rsp+20h] [rbp-60h]
__int64 v10; // [rsp+140h] [rbp+C0h]

v10 = a4;
memset(&v8 + 4, 0, 0x100uLL);
v6 = &v8 + 36;
sub_401C67((__int64)v6, a2, a5, (__int64)&v9, a6);
return sub_401E02((__int64)v6, a2, v10);
}

在sub_401C67中密钥扩展:

for ( i = 0; (signed int)i <= 255; ++i )
{
*(_BYTE *)((signed int)i + a4) = i;//这里每一位都赋值位数
result = (signed int)i;
v7[i] = *(_BYTE *)(i % a5 + a3);
}

在sub_401E02中的加密运算:

__int64 __fastcall sub_401E02(__int64 a1, __int64 a2, __int64 a3, __int64 a4, unsigned int a5)
{
unsigned int v5; // eax
char v6; // ST0F_1
__int64 result; // rax
unsigned int i; // [rsp+14h] [rbp-Ch]
int v9; // [rsp+18h] [rbp-8h]
signed int v10; // [rsp+1Ch] [rbp-4h]

v10 = 0;
v9 = 0;
for ( i = 0; ; ++i )
{
result = i;
if ( i >= a5 )
break;
v10 = (unsigned __int8)(((unsigned int)((v10 + 1) >> 31) >> 24) + v10 + 1) - ((unsigned int)((v10 + 1) >> 31) >> 24);
v5 = (unsigned int)((v9 + *(unsigned __int8 *)(v10 + a4)) >> 31) >> 24;
v9 = (unsigned __int8)(v5 + v9 + *(_BYTE *)(v10 + a4)) - v5;
v6 = *(_BYTE *)(v10 + a4);
*(_BYTE *)(v10 + a4) = *(_BYTE *)(v9 + a4);
*(_BYTE *)(a4 + v9) = v6;
*(_BYTE *)(i + a3) ^= *(_BYTE *)((unsigned __int8)(*(_BYTE *)(v10 + a4) + *(_BYTE *)(v9 + a4)) + a4);//这里每一位都异或运算后的密钥, 所以RC4甚至可以直接导出这里的异或数组
}
return result;
}

check

__int64 __fastcall check(size_t Count, char *a2, __int64 a3, size_t input)
{
int v4; // eax
__int64 v5; // rdx
int v6; // ST12C_4
unsigned int v8; // [rsp+124h] [rbp-Ch]
signed int v9; // [rsp+128h] [rbp-8h]
int v10; // [rsp+12Ch] [rbp-4h]

strncpy((char *)Count, a2, input);
signal(Count, (void (__cdecl *)(int))a2);
v10 = 0;
v9 = 1;
v8 = 0;
r1 = (unsigned __int64)&byte_405320;//const常量
LODWORD(r2) = (unsigned __int64)&input_RC4;//RC4后的输入
while ( v9 )
{
v4 = setjmp((_JBTYPE *)Count);
if ( v4 == 0xA8 )
{
*(&r0 + (opcode[v10] >> 4)) -= *(&r0 + (opcode[v10] & 0xF));// A8
++v10;
}
else if ( v4 > 0xA8 )
{
if ( v4 == 0xAC )
{
*(&r0 + (opcode[v10] >> 4)) &= *(&r0 + (opcode[v10] & 0xF));
++v10;
}
else if ( v4 > 0xAC )
{
if ( v4 == 0xAE )
{
*(&r0 + (opcode[v10] >> 4)) ^= *(&r0 + (opcode[v10] & 0xF));
++v10;
}
else if ( v4 < 0xAE ) // AD
{
*(&r0 + opcode[v10]) = (unsigned __int8)~*((_BYTE *)&r0 + 4 * opcode[v10]);
++v10;
}
else
{
if ( v4 != 0xAF )
goto LABEL_43;
cmp_0 = opcode[v10] >> 4;
cmp_1 = opcode[v10] & 0xF;
if ( !setjmp((_JBTYPE *)Count) )
opcode[v10] = cmp_0 / opcode[v10 + 1];
v10 += 2;
}
}
else if ( v4 == 0xAA )
{
*(&r0 + opcode[v10]) = *(&r0 + opcode[v10 + 1]);
v10 += 2;
}
else if ( v4 > 0xAA )
{
*(&r0 + opcode[v10]) = opcode[v10 + 1];
v10 += 2;
}
else
{
*(&r0 + (opcode[v10] >> 4)) += *(&r0 + (opcode[v10] & 0xF));
++v10;
}
}
else if ( v4 == 0xA3 )
{
*(&r0 + (opcode[v10] >> 4)) |= *(&r0 + (opcode[v10] & 0xF));
++v10;
}
else if ( v4 > 0xA3 )
{
if ( v4 == 0xA6 )
{
if ( !r5 )
v10 += (char)opcode[v10];
++v10;
}
else if ( v4 > 0xA6 )
{
if ( r5 )
v10 += (char)opcode[v10];
++v10;
}
else
{
if ( v4 != 0xA5 )
goto LABEL_43;
v10 += opcode[v10] + 1;
}
}
else if ( v4 == 0xA0 )
{
*(&r0 + opcode[v10]) = *(unsigned __int8 *)(signed int)*(&r0 + opcode[v10]);
++v10;
}
else if ( v4 == 0xA2 )
{
v6 = v10 + 1;
*(&r0 + opcode[v6]) >>= *(&r0 + opcode[v6]);
v10 = v6 + 1;
}
else
{
if ( !v4 )
{
v5 = opcode[v10];
longjmp_0((_JBTYPE *)Count, (int)a2);
}
LABEL_43:
v9 = 0;
v8 = r5;
}
}
return v8;
}

则调试分析, 如下:

.bss:0000000000409040 r0              dd ?                    ; DATA XREF: sub_402930+32↑o
.bss:0000000000409040 ; sub_402930+4C↑o ...
.bss:0000000000409044 r1 dd ? ; DATA XREF: check+52↑w
.bss:0000000000409048 ; _DWORD *r2
.bss:0000000000409048 r2 dd ? ; DATA XREF: check+5F↑w
.bss:000000000040904C r3 dd ?
.bss:0000000000409050 r4 dd ?
.bss:0000000000409054 r5 dd ? ; DATA XREF: check:loc_403073↑r
.bss:0000000000409054 ; check:loc_40309A↑r ...
.bss:0000000000409058 r6 dd ?
.bss:000000000040905C dd ?
.bss:0000000000409060 cmp_0 dd ? ; DATA XREF: sub_402930+22↑r
.bss:0000000000409060 ; sub_402930+5B↑r ...
.bss:0000000000409064 cmp_1 dd ? ; DATA XREF: sub_402930+3C↑r
.bss:0000000000409064 ; check+35A↑w

AF的操作并不在0xAF中, 因为这里的除数永远等于0, 所以AF的操作在前面的除0异常处理signal中, 这里有点坑, IDA分析出来的和实际汇编代码看着不太一样。

else
{
if ( v4 != 0xAF )
goto LABEL_43;
cmp_0 = opcode[v10] >> 4;
cmp_1 = opcode[v10] & 0xF;
if ( !setjmp((_JBTYPE *)Count) )
opcode[v10] = cmp_0 / opcode[v10 + 1];
v10 += 2;
}
.text:00000000004029EC                 lea     rdx, sub_402930
.text:00000000004029F3 mov ecx, 8
.text:00000000004029F8 call signal

在sub_402930中:

void __fastcall __noreturn sub_402930(_JBTYPE *a1, void (__cdecl *a2)(int), __int64 a3, int a4)
{
if ( a4 == 8 )
{
signal((int)a1, a2);
*(&r0 + cmp_0) = *(&r0 + cmp_0) == *(&r0 + cmp_1);
longjmp_0(a1, (int)a2);
}
exit((int)a1);
}

最终, opcode为:

3 AB 03 00  : mov r3, 0x00
3 AB 04 1A : mov r4, 0x1A
3 AB 00 66 : mov r0, 0x66
s1:
3 AA 05 02 : mov r5, r2
2 A9 53 : add r5, r3
2 A0 05 : mov r5, [r5]
3 AB 06 CC : mov r6, 0xCC
2 A9 56 : add r5, r6
3 AB 06 FF : mov r6, 0xFF
2 AC 56 : and r5, r6
2 AE 50 : xor r5, r0
2 AD 00 : r0 = ~r0
3 AA 06 05 : mov r6, r5
3 AA 05 01 : mov r5, r1
2 A9 53 : add r5, r3
2 A0 05 : mov r5, [r5]
3 AF 56 00 : cmp r5, r6
3 A7 01 : jz s2
1 CC
s2 :
2 A9 35 : add r3, r5
3 AA 05 03 : mov r5, r3
3 AF 54 00 : cmp r5, r4
3 A6 D1 : jnz s1
1 CC

写成python大概是这样:

for r3 in range(26):
r5 += 0xCC
r5 &= 0xFF
r5 ^= r0
r0 = (~r0) & 0xFF
r6 = r5
r5 = const[r3]
if r5 != r6 :
break
r3 += 1
r5 = input[r3]

则求出RC4之后的输入为:

const = [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]

r0 = 0x66
for i in range(0, 26):
r5 = const[i]
r5 ^= r0
r5 = (r5 - 0xCC) & 0xFF
r0 = (~r0) & 0xFF
print('\\x' + hex(r5)[2:], end = '')

RC4解密:

from Crypto.Cipher import ARC4
key = b'\xA4\xE7\x2C\x32' * 4

obj = ARC4.new(key)

input = b"\x23\x8c\xbe\xfd\x25\xd7\x65\xf4\xb6\xb3\xb6\x0f\xe1\x74\xa2\xef\xfc\x38\x4e\xd2\x1a\x4a\xb1\x10\x96\xa5"

print(obj.decrypt(input))

得到输入:@ck_For_fun_02508iO2_2iOR}, 输入后输出以下信息, 摘掉眼镜打开高清视界0.0

the part of flag was protected by a magic spell!
@ck_For_fun_02508iO2_2iOR}
.843fFDCb52bc573DA7e336b4BCC97C6E.
.1adC4b19FEBA1Bf9D182FAe8Eac1AeBF.
.CB7EEFeD2B2D6dd76f bE D0 ec92.
.DD1C36EDBaf56 63b6 ad83 f5D a60D.
.28CCE56eaBbcF 0Bb9 ed7F 669 aff7.
. dC 83 4 bf a01 .
. DAB 2a0 CBD eB74 9eF6 0De 1Bf .
. E15 d55A276 7A4c fA7 eE72 dc7 .
. afB bE0fa2e 7Bf9 Eb14 6A5 891 .
. DCf c907BF9 aFBB 28eA 4dE aB1 .
. B25 c5B 16d d90f 0cb0 D78 Edd .
. aEA7 eDaD 07 743A 935 27d .
.D38f5b1FacEaBDeFBEEcbA4 0b9D0A0f.
.ce1A5DFCe012a0a62A5e2D8 8e38C9A.
.CC1b26fF12fC01f8aeB7cAC06c65FCbe.
.e663471A878EcE289bee7c11d7f8CF7b.
.--------------------------------.
@ck_For_fun_02508iO2_2iOR}
.--------------------------------.

最终flag为:rctf{h@ck_For_fun_02508iO2_2iOR}