2024 强网杯mips
flag: flag{QeMu_r3v3rs3in9_h@ck6} mips_bin分析 先用ida打开mips_bin文件,通过搜索字符串找到ftext函数。 int __cdecl ftext(int argc, const char **argv, const char **envp) { int *v3; // $v0 int *v4; // $s0 _BYTE *v5; /
flag: flag{QeMu_r3v3rs3in9_h@ck6}
mips_bin分析
先用ida打开mips_bin文件,通过搜索字符串找到ftext函数。
int __cdecl ftext(int argc, const char **argv, const char **envp)
{
int *v3; // $v0
int *v4; // $s0
_BYTE *v5; // $a0
int i; // $v1
char v7; // $a1
int v8; // $s2
int v9; // $s0
int *v11; // $v0
int *v12; // $v1
int v13; // $a2
int v14; // $a1
int v15; // $a0
int v16; // $v1
int v17; // $v0
int v18; // $v1
int v19; // $v0
int flag[8]; // [sp+20h] [-38h] BYREF
int v21[6]; // [sp+40h] [-18h] BYREF
qmemcpy(v21, "sxrujtv`labiVzbp`vpg|", 21);
memset(flag, 0, sizeof(flag));
v3 = (int *)mmap((void *)0x23000, 0x1000u, 7, 2050, -1, 0);
v4 = 0;
if ( v3 != (int *)-1 )
v4 = v3;
v5 = &opcodes;
for ( i = 0; i != 96; ++i )
{
v7 = i & 3 ^ *v5;
*v5++ = v7;
}
v8 = fork();
if ( v8 )
{
do
{
v9 = waitpid(v8, 0, 1);
sleep(1);
}
while ( !v9 );
}
else
{
v11 = (int *)&opcodes;
v12 = v4;
do
{
v13 = v11[1];
v14 = v11[2];
v15 = v11[3];
*v12 = *v11;
v12[1] = v13;
v12[2] = v14;
v12[3] = v15;
v11 += 4;
v12[3] = v15;
v12 += 4;
}
while ( v11 != &stdio_user_locking );
v16 = v21[1];
v4[512] = v21[0];
v4[513] = v16;
v17 = v21[2];
v4[513] = v16;
v4[514] = v17;
v18 = v21[3];
v4[514] = v17;
v4[515] = v18;
v19 = v21[4];
v4[515] = v18;
v4[516] = v19;
*((_BYTE *)v4 + 2068) = HIBYTE(v21[5]);
puts("input your flag, be fast:");
read(0, flag, 32);
if ( strlen(flag) != 22 )
write(1, &unk_40EE5C, 4);
if ( ((int (__fastcall *)(int *))v4)(flag) )
write(1, "wrong\n", 7);
else
write(1, "right\n", 7);
}
return 0;
}
fake flag
这里就是通过创建子进程对opcode进行解密,然后再调用opcode的函数来检验flag。这里编写一个脚本来解密opcode,或者动调调试查看check函数。
import idc
ea_start=0x00000430010
for i in range(96):
tmp = idc.get_bytes(ea_start+i,1)
tmp = i & 3 ^ ord(tmp)
idc.patch_byte(ea_start+i,tmp)
print("ok")
解密完成后查看反汇编。
.data:00430010 .globl opcodes
.data:00430010 opcodes: # DATA XREF: _ftext+B4↑o
.data:00430010 00 80 40 25 move $t0, $a0
.data:00430014 3C 09 00 02 35 29 38 00 li $t1, 0x23800 # Load Immediate
.data:0043001C 24 0A 00 15 li $t2, 0x15 # Load Immediate
.data:00430020 24 0B 00 00 li $t3, 0 # Load Immediate
.data:00430024 11 60 00 06 beqz $t3, loc_430040 # Branch on Zero
.data:00430028 00 00 00 00 nop
.data:00430028
.data:0043002C
.data:0043002C loc_43002C:
.data:0043002C 21 29 00 01 addi $t1, 1 # Add Immediate
.data:00430030 21 08 00 01 addi $t0, 1 # Add Immediate
.data:00430034 21 4A FF FF addi $t2, -1 # Add Immediate
.data:00430038 11 40 00 0A beqz $t2, loc_430064 # Branch on Zero
.data:0043003C 00 00 00 00 nop
.data:0043003C
.data:00430040
.data:00430040 loc_430040:
.data:00430040 01 8C 60 26 xor $t4, $t4 # Exclusive OR
.data:00430044 01 AD 68 26 xor $t5, $t5 # Exclusive OR
.data:00430048 81 2C 00 00 lb $t4, 0($t1) # Load Byte
.data:0043004C 81 0D 00 00 lb $t5, 0($t0) # Load Byte
.data:00430050 01 8A 60 26 xor $t4, $t2 # Exclusive OR
.data:00430054 01 AC 68 26 xor $t5, $t4 # Exclusive OR
.data:00430058 11 A0 FF F4 beqz $t5, loc_43002C # Branch on Zero
.data:0043005C 00 00 00 00 nop
.data:0043005C
.data:00430060 24 0B 00 01 li $t3, 1 # Load Immediate
.data:00430064
.data:00430064 loc_430064:
.data:00430064 01 60 10 25 move $v0, $t3
.data:00430068 03 E0 00 08 jr $ra # Jump Register
.data:0043006C 00 00 00 00 nop
根据汇编写解密脚本。
enc = b"sxrujtv`labiVzbp`vpg|"
flag = ""
for i in range(21):
flag += chr(enc[i] ^ (21-i))
print(flag)
解出来flag是flag{dynamic_reverse},很显然是个fake flag,提示要动态调试,直接运行mips_bin文件输入这个flag会回显right,但是使用给的emu虚拟机来运行mips_bin就会提示wrong。
发现这个地方mmap映射了一个固定内存。

emu分析
在emu程序中搜索0x23000常量,定位到0x33D920位置,发现上下一片红,简单去一下花指令。
__int64 __fastcall sub_33D8E0(__int64 a1)
{
__int64 result; // rax
int v2; // [rsp+10h] [rbp-20h]
int i; // [rsp+14h] [rbp-1Ch]
int j; // [rsp+18h] [rbp-18h]
__int64 v5; // [rsp+20h] [rbp-10h]
__int64 v6; // [rsp+28h] [rbp-8h]
v5 = *(_QWORD *)(a1 + 528);
v2 = 0;
result = *(unsigned int *)(v5 + 128);
if ( *(_DWORD *)(v5 + 128) == 0x23000 )
{
result = (unsigned int)dword_C32318;
if ( dword_C32318 )
{
v6 = sub_33D48E(&unk_C33280);
for ( i = 0; i <= 21; ++i )
*(_BYTE *)(i + v6) ^= dword_C32324;
swap(v6, 7LL, 11LL);
result = swap(v6, 12LL, 16LL);
for ( j = 0; j <= 21; ++j )
{
result = enc[j];
if ( *(unsigned __int8 *)(j + v6) != (_DWORD)result )
{
v2 = 1;
break;
}
}
if ( !v2 && j == 22 )
dword_C3231C = 1;
}
}
return result;
}
主要的加密在sub_33D48E函数内,再简单去一些花指令。可以看出是一个魔改的rc4。
__int64 __fastcall sub_33D48E(__int64 a1)
{
unsigned int v1; // edx
__int64 result; // rax
unsigned __int8 v3; // [rsp+15h] [rbp-17Bh]
int i; // [rsp+18h] [rbp-178h]
int v5; // [rsp+1Ch] [rbp-174h]
unsigned int v6; // [rsp+20h] [rbp-170h]
int v7; // [rsp+24h] [rbp-16Ch]
int v8; // [rsp+28h] [rbp-168h]
int j; // [rsp+2Ch] [rbp-164h]
int v10; // [rsp+30h] [rbp-160h]
int v11; // [rsp+34h] [rbp-15Ch]
int v12; // [rsp+3Ch] [rbp-154h]
const char *v13; // [rsp+40h] [rbp-150h]
__int64 v14; // [rsp+48h] [rbp-148h]
__int64 v15[32]; // [rsp+80h] [rbp-110h] BYREF
__int16 v16; // [rsp+180h] [rbp-10h]
unsigned __int64 v17; // [rsp+188h] [rbp-8h]
v17 = __readfsqword(0x28u);
memset(v15, 0, sizeof(v15));
v16 = 0;
for ( i = 0; i <= 255; ++i )
*((_BYTE *)v15 + i) = i;
v5 = 0;
v6 = 0;
v13 = "6105t3";
do
{
v10 = *((unsigned __int8 *)v15 + v6);
v11 = (unsigned __int8)(v13++)[(int)(2 * (v6 / 6 - (((2863311531u * (unsigned __int64)v6) >> 32) & 0xFFFFFFFC)))];
v5 += v10 + v11;
v1 = v6++;
*((_BYTE *)v15 + v1) = *((_BYTE *)v15 + (unsigned __int8)v5);
*((_BYTE *)v15 + (unsigned __int8)v5) = v10;
}
while ( v6 != 256 );
v7 = 0;
v8 = 0;
v14 = sub_5C6D90(256LL);
for ( j = 0; j != 22; ++j )
{
v12 = *((unsigned __int8 *)v15 + (unsigned __int8)++v7);
v8 += v12;
*((_BYTE *)v15 + (unsigned __int8)v7) = *((_BYTE *)v15 + (unsigned __int8)v8);
*((_BYTE *)v15 + (unsigned __int8)v8) = v12;
v3 = ((((unsigned __int8)(*(_BYTE *)(j + 5 + a1) << 7) | (*(_BYTE *)(j + 5 + a1) >> 1)) << 6) ^ 0xC0 | ((unsigned __int8)((*(_BYTE *)(j + 5 + a1) << 7) | (*(_BYTE *)(j + 5 + a1) >> 1)) >> 2) ^ 0x3B) ^ 0xBE;
*(_BYTE *)(j + v14) = *((_BYTE *)v15 + (unsigned __int8)(*((_BYTE *)v15 + (unsigned __int8)v7) + v12)) ^ key[j & 3] ^ (((unsigned __int8)(((16 * (((32 * v3) | (v3 >> 3)) ^ 0xAD)) | ((unsigned __int8)(((32 * v3) | (v3 >> 3)) ^ 0xAD) >> 4)) ^ 0xDE) >> 5) | (8 * (((16 * (((32 * v3) | (v3 >> 3)) ^ 0xAD)) | ((unsigned __int8)(((32 * v3) | (v3 >> 3)) ^ 0xAD) >> 4)) ^ 0xDE)));
}
result = v14;
if ( v17 != __readfsqword(0x28u) )
sub_616930();
return result;
}
exp
对xor的值和输入的每个字符爆破。
def rc4_main(key, message):
s_box = rc4_init_sbox(key)
crypt = rc4_excrypt(message, s_box)
return crypt
def rc4_init_sbox(key):
s_box = list(range(256))
j = 0
for i in range(256):
j = (j + s_box[i] + ord(key[(i % len(key))])) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
return s_box
def rc4_excrypt(plain, box):
res = []
i = j = 0
for s in plain:
i = (i + 1) % 256
j = (j + box[i]) % 256
box[i], box[j] = box[j], box[i]
for t in range(0x7f):
v3 = (((((t << 7) & 0xff) | (t >> 1)) << 6) ^ 0xC0 | ((((t << 7) | (t >> 1)) & 0xff) >> 2) ^ 0x3B) ^ 0xBE
v3 &= 0xff
v4 = box[(box[i] + box[j]) & 0xff] ^ ((((((16 * (((32 * v3) | (v3 >> 3)) ^ 0xAD)) | (((((32 * v3) | (v3 >> 3)) ^ 0xAD) & 0xff) >> 4)) ^ 0xDE) & 0xff) >> 5) | (8 * (((16 * (((32 * v3) | (v3 >> 3)) ^ 0xAD)) | (((((32 * v3) | (v3 >> 3)) ^ 0xAD) & 0xff) >> 4)) ^ 0xDE)))
v4 &= 0xff
if v4 == s:
res.append(t)
break
return res
key = [0xDE, 0xAD, 0xBE, 0xEF]
rc4_key = "6105t3"
for k in range(0xff):
enc = [0xC4, 0xEE, 0x3C, 0xBB, 0xE7, 0xFD, 0x67, 0x1D, 0xF8, 0x97, 0x68, 0x9D, 0xB, 0x7F, 0xC7, 0x80, 0xDF, 0xF9, 0x4B, 0xA0, 0x46, 0x91]
enc[7], enc[11] = enc[11], enc[7]
enc[12], enc[16] = enc[16], enc[12]
for i in range(len(enc)):
enc[i] ^= key[i&3]
for i in range(len(enc)):
enc[i] ^= k
flag = rc4_main(rc4_key, enc)
if len(flag) == 22 and flag[-1] == ord('}'):
print(bytes(flag))
前面一部分可以在加密函数的参数的交叉引用中找到
