[网鼎杯 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] BYREF
char Destination[80]; // [esp+44h] [ebp-64h] BYREF
DWORD flOldProtect; // [esp+94h] [ebp-14h] BYREF
size_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; // eax
int i; // [esp+Ch] [ebp-4h]
for ( i = 0; i <= 23; ++i )
{
result = &a1[i];
if ( (i & 1) != 0 )
a1[i] -= i; //i为偶数
else
a1[i] ^= i; //i为奇数
}
return result;
}
可以看出wrong函数的功能是把a1里每个元素按照下标的奇偶进行相应的加密处理
然后来到omg函数
omg
函数
int __cdecl omg(char *a1)
{
int v2[24]; // [esp+18h] [ebp-80h] BYREF
int 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?");
else
return puts("wrong ~~ But seems a little program");
}
查看unk_430C0
部分
直接给出字符串了,破解一下吧(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函数,但是无法打开
红色标注的下面那里像是一段乱码一样,导致无法反汇编出来结果吧,然后尝试找出调用了encrypt函数的地址,od动调一下,看看这个函数到底在干什么
在这里发现了encrypt函数和finally函数分别被调用
既然由前面的函数得到的flag是个假的,那最后正确的flag一定是通过这两个函数处理的,下一步就是分析encrypt函数
OD打开后 在0x401833地址处下断点
然后F9运行至断点处,在应用中随意输入24位字符
然后F7步入
可以看到这里的函数是已经解密了,然后可以直接olldump脱壳,直接保存新的exe
然后再用ida打开它
encrypt函数
int __cdecl start(int a1)
{
int v2[19]; // [esp+1Ch] [ebp-6Ch] BYREF
int 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;
}
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; // eax
char v3[9]; // [esp+13h] [ebp-15h] BYREF
int 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!!!");
else
return 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}