[网鼎杯 2020 青龙组]jocker

SMC(self-Modifying Code): 自修改代码,程序在执行某段代码的过程中会对程序的代码进行修改,只有在修改后的代码才是可汇编,可执行的。在程序未对该段代码进行修改之前,在静态分析状态下,均是不可读的字节码。

查壳:32位无壳程序,直接拖ida

首先shift+f12搜索字符串,但是除了main函数中的一句请输入flag提示之外在没有什么有用的。

main函数

// positive sp value has been detected, the output may be wrong!
int __cdecl main(int argc, const char **argv, const char **envp)
{char Str[50]; // [esp+12h] [ebp-96h] BYREFchar Destination[80]; // [esp+44h] [ebp-64h] BYREFDWORD flOldProtect; // [esp+94h] [ebp-14h] BYREFsize_t v7; // [esp+98h] [ebp-10h]int i; // [esp+9Ch] [ebp-Ch]
​__main();puts("please input you flag:");if ( !VirtualProtect(encrypt, 0xC8u, 4u, &flOldProtect) )exit(1);scanf("%40s", Str);v7 = strlen(Str);if ( v7 != 24 ){puts("Wrong!");exit(0);}strcpy(Destination, Str);wrong(Str);omg(Str);for ( i = 0; i <= 186; ++i )*((_BYTE *)encrypt + i) ^= 0x41u;if ( encrypt(Destination) )finally(Destination);return 0;
}

可以看到第一个条件是flag长度要为24

然后将str先复制到Destination这里,再调用wrong函数对str进行修改。

wrong函数

char *__cdecl wrong(char *a1)
{char *result; // eaxint i; // [esp+Ch] [ebp-4h]
​for ( i = 0; i <= 23; ++i ){result = &a1[i];if ( (i & 1) != 0 )a1[i] -= i;       //i为偶数elsea1[i] ^= i;       //i为奇数}return result;
}

可以看出wrong函数的功能是把a1里每个元素按照下标的奇偶进行相应的加密处理

然后来到omg函数

omg函数

int __cdecl omg(char *a1)
{int v2[24]; // [esp+18h] [ebp-80h] BYREFint i; // [esp+78h] [ebp-20h]int v4; // [esp+7Ch] [ebp-1Ch]
​v4 = 1;qmemcpy(v2, &unk_4030C0, sizeof(v2));for ( i = 0; i <= 23; ++i ){if ( a1[i] != v2[i] )v4 = 0;}if ( v4 == 1 )return puts("hahahaha_do_you_find_me?");elsereturn puts("wrong ~~ But seems a little program");
}

查看unk_430C0部分

[网鼎杯 2020 青龙组]jocker-编程之家

 

直接给出字符串了,破解一下吧(shift + E直接导出字符串)

key = 0x66, 0x6B, 0x63, 0x64, 0x7F, 0x61, 0x67, 0x64, 0x3B, 0x56, 0x6B, 0x61, 0x7B, 0x26, 0x3B, 0x50, 0x63,       0x5F, 0x4D, 0x5A, 0x71, 0x0C, 0x37, 0x66
flag = ''
for i in range(24):if i % 2 == 1:flag += chr(key[i] + i)else:flag += chr(key[i] ^ i)
print(flag)
#结果: 
#flag{fak3_alw35_sp_me!!}

假的flag

在omg后面还有一个循环,最后的一点应该是在这里的

循环中还有一个encrypt函数,但是无法打开

[网鼎杯 2020 青龙组]jocker-编程之家

[网鼎杯 2020 青龙组]jocker-编程之家

[网鼎杯 2020 青龙组]jocker-编程之家

红色标注的下面那里像是一段乱码一样,导致无法反汇编出来结果吧,然后尝试找出调用了encrypt函数的地址,od动调一下,看看这个函数到底在干什么

在这里发现了encrypt函数和finally函数分别被调用

既然由前面的函数得到的flag是个假的,那最后正确的flag一定是通过这两个函数处理的,下一步就是分析encrypt函数

OD打开后 在0x401833地址处下断点

[网鼎杯 2020 青龙组]jocker-编程之家

 

然后F9运行至断点处,在应用中随意输入24位字符

然后F7步入

[网鼎杯 2020 青龙组]jocker-编程之家

 

可以看到这里的函数是已经解密了,然后可以直接olldump脱壳,直接保存新的exe

然后再用ida打开它

encrypt函数

int __cdecl start(int a1)
{int v2[19]; // [esp+1Ch] [ebp-6Ch] BYREFint v3; // [esp+68h] [ebp-20h]int i; // [esp+6Ch] [ebp-1Ch]
​v3 = 1;qmemcpy(v2, &unk_403040, sizeof(v2));for ( i = 0; i <= 18; ++i ){if ( (char)(*(_BYTE *)(i + a1) ^ aHahahahaDoYouF[i]) != v2[i] ){puts("wrong ~");v3 = 0;exit(0);}}puts("come here");return v3;
}

[网鼎杯 2020 青龙组]jocker-编程之家 

 

aHahahahaDoYouF中的字符串是hahahaha_do_you_find_me?

unk_403040中的字符通过shift + E 可以直接导出

然后写个脚本破解一下

v2 = [14, 13, 9, 6, 19, 5, 88, 86, 62, 6, 12, 60, 31, 87, 20, 107, 87, 89, 13]
xor = 'hahahaha_do_you_find_me?'
flag = []
for i in range(0, 19):flag.append(v2[i] ^ ord(xor[i]))
for i in range(0, 19):print(chr(flag[i]), end = '')

得到结果是

flag{d07abccf8a410c

flag明显少了一部分啊,但是它最后还有一个

finally函数

int __cdecl sub_40159A(int a1)
{unsigned int v1; // eaxchar v3[9]; // [esp+13h] [ebp-15h] BYREFint v4; // [esp+1Ch] [ebp-Ch]
​strcpy(v3, "%tp&:");v1 = time(0);srand(v1);v4 = rand() % 100;v3[6] = 0;*(_WORD *)&v3[7] = 0;if ( (v3[(unsigned __int8)v3[5]] != *(_BYTE *)((unsigned __int8)v3[5] + a1)) == v4 )return puts("Really??? Did you find it?OMG!!!");elsereturn puts("I hide the last part, you will not succeed!!!");
}

这段代码具体是什么意思真的没搞明白,但是前面的加密算法是异或,所以尝试一下异或

得到的flag现在没有最后一位 },那么剩下的字符串里肯定是最后一个字符异或一个数得到}

flag = [] 
v3 = [37, 116, 112, 38, 58]#既是‘%tp&:’
key = ord('}') ^ 58
for i in range(5):flag.append(chr(v3[i] ^ key))
print(''.join(flag))

最后得到

b37a}

flag

flag{d07abccf8a410cb37a}