查壳upx壳,直接拖入kali upx -d脱壳,尝试运行无果,拖入ida F5

MZbZO1.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int sub_411BD0()
{
printf(
"1111111111111111111111111111111111111111111111111111111111111111111111111111111\n"
"*******************************************************************************\n"
"************** ****************************************************\n"
"************** ******** ********************* *************\n"
"************** ********* ********************* ***************************\n"
"************** ********* ********************* ***************************\n"
"************** ********* ********************* ***************************\n"
"************** ******* ********************** ***************************\n"
"************** **** ************************* ***************************\n"
"************** * *************************** **************\n"
"************** *** ************************* ***************************\n"
"************** ****** *********************** ***************************\n"
"************** ******** ********************* ***************************\n"
"************** ********** ******************* ***************************\n"
"************** *********** ***************** *************\n"
"*******************************************************************************\n"
"1111111111111111111111111111111111111111111111111111111111111111111111111111111\n");
printf("input flag:\n");
return scanf("%36s", &Source);
}

sub_411BD0函数是把输入的 flag 存到全局缓冲区 Source 里面,而且最多只存 36 个字符

由API可知这道题涉及到多线程,CreateThread API 会创建新线程,CreateMutex 创建一个互斥变量,用于防止多线程中出现资源争用,此变量存于全局句柄::hObject中。创建了两线程StartAddresssub_41119F,跟进查看,两者代码相似,区别在与StartAddress中的一句sub_41112C(&Source, dword_418008);。两者交替执行,直至dword_418008 > -1

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
void __stdcall StartAddress_0(int a1)
{
while ( 1 )
{
WaitForSingleObject(hObject, 0xFFFFFFFF);
if ( dword_418008 > -1 )
{
sub_41112C(&Source, dword_418008);
--dword_418008;
Sleep(0x64u);
}
ReleaseMutex(hObject);
}
}


void __stdcall sub_411B10(int a1)
{
while ( 1 )
{
WaitForSingleObject(hObject, 0xFFFFFFFF);
if ( dword_418008 > -1 )
{
Sleep(0x64u);
--dword_418008;
}
ReleaseMutex(hObject);
}
}

跟入sub_41112C却警告positive sp value has been found,百度得知是栈平衡缺失所导致的问题(函数调用前的sp值与调用结束的sp值不相同),可手动调整sp值解决。

MZLlMd.png

在 选项 -> 常规中勾选堆栈指针,即可在地址旁显示sp的值

MZLoe1.png

调到警告处,可发现sp值确实发生了偏差,在sp值为0时,又运行了pop edp这条语句导致栈不平衡

MZOFfS.png

00411A03使用快捷键Alt+k,将新旧sp差值改为0x0,修改完成后即可F5得到伪代码

1573222975119

此函数就是加密函数,参数a1为缓冲区Source地址,a2为常量dword_418008,值为29,线程每执行一次减一,两线程公用。sub_411940判断了字符是不是字母再变换,大写字母变换成 off_418000[0][*(_BYTE *)(a2 + a1) - 38],小写字母变换成 off_418000[0][*(_BYTE *)(a2 + a1) - 96]off_418000[0] 是一个字符串,内容是 QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm。注意是两线程交替执行,所以只会有一半的字符改变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
char *__cdecl sub_411940(int a1, int a2)
{
char *result; // eax
char v3; // [esp+D3h] [ebp-5h]

v3 = *(_BYTE *)(a2 + a1);
if ( (v3 < 97 || v3 > 122) && (v3 < 65 || v3 > 90) )
exit(0);
if ( v3 < 97 || v3 > 122 )
{
result = off_418000[0];
*(_BYTE *)(a2 + a1) = off_418000[0][*(char *)(a2 + a1) - 38];
}
else
{
result = off_418000[0];
*(_BYTE *)(a2 + a1) = off_418000[0][*(char *)(a2 + a1) - 96];
}
return result;
}

看到sub_411190(),此函数用经变换过后的Sourceoff_418004[]逐位对比,off_418004[]内容为TOiZiZtOrYaToUwPnToBsOaOapsyS

1
2
3
4
5
6
7
8
9
10
11
int sub_411880()
{
int i; // [esp+D0h] [ebp-8h]

for ( i = 0; i < 29; ++i )
{
if ( Source[i] != off_418004[i] )
exit(0);
}
return printf("\nflag{%s}\n\n", Dest);
}

结合以上分析给出以下脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
off_418000 = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm"

off_418004 = "TOiZiZtOrYaToUwPnToBsOaOapsyS"

flag=''

for i in range(len(off_418004)):
if i %2 == 0:
flag += off_418004[i]
continue
for j,k in enumerate(off_418000):
if off_418004[i] == k:
if chr(j+38).isupper():
flag += chr(j+38)
else:
flag += chr(j+96)

print (flag)

但得出的flag提交发现不正确,百度知道还需添加一个字符E才能通过。之后发现变换后的字符串末尾应该有结束符,所以输入的 flag 应该比变换后的字符串多一个字符,因为多出来的那个字符经过变换会变成结束符。