获取中...

-

Just a minute...

这里一道做了好久的题目,巨坑🐷

脱掉upx壳后ida查看

main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main_0()
{
HANDLE v1; // [esp+D0h] [ebp-14h]
HANDLE hObject; // [esp+DCh] [ebp-8h]

sub_4110FF(); // 打印一堆东西,输入存在Source里
::hObject = CreateMutexW(0, 0, 0); // 找已经存在的线程
j_strcpy(Dest, Source); // 拷贝字符串给Dest
hObject = CreateThread(0, 0, StartAddress, 0, 0, 0);// 创建线程
v1 = CreateThread(0, 0, sub_41119F, 0, 0, 0); // 创建线程
CloseHandle(hObject);
CloseHandle(v1);
while ( dword_418008 != -1 )
;
sub_411190();
CloseHandle(::hObject);
return 0;
}

sub_411190(判断)

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] ) // 输入的和418004这个地方比较
exit(0);
}
return printf("\nflag{%s}\n\n", Dest); // 如果相同就是flag
}

流程挺简单,就是输入字符串存在Source中,然后拷贝给Dest,然后进入sub_411190这个函数中,然后将Source与off_418004这里存的数比较,如果相同,输出的flag是Dest也就是输入的字符串。off_418004中存的是TOiZiZtOrYaToUwPnToBsOaOapsyS,但是不对。应该是对Source进行处理了。

创建两个线程

交叉引用找到这里,这个地方也是main函数创建的第一个线程StartAddress

StartAddress(第一个线程)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void __stdcall StartAddress_0(int a1)
{
while ( 1 )
{
WaitForSingleObject(hObject, 0xFFFFFFFF); // WaitForSingleObject函数用来检测hHandle事件的信号状态,在某一线程中调用该函数时,线程暂时挂起,如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函数照样返回
if ( dword_418008 > -1 ) // dword_418008是1D
{
sub_41112C(Source, dword_418008); //这里对Source进行了处理
--dword_418008; // 减1
Sleep(0x64u); //休眠
}
ReleaseMutex(hObject); // 释放线程拥有的互斥体的控制权。
}
}

有一个关键的函数sub_41112C,这里应该就是加密部分了。

sub_41119F(第二个线程)

查看一下main函数中创建的第二个线程sub_41119F

1
2
3
4
5
6
7
8
9
10
11
12
13
void __stdcall sub_411B10(int a1)
{
while ( 1 )
{
WaitForSingleObject(hObject, 0xFFFFFFFF); // 等待
if ( dword_418008 > -1 )
{
Sleep(0x64u); // 休眠
--dword_418008; // 减一
}
ReleaseMutex(hObject); // 释放
}
}

两个进程因为都有sleep,所以这两个进程是交替执行。首先执行的一个线程,对字符进行操作后减一休眠然后执行第二个线程,第二个线程没有对字符进行操作,只是减一之后进入休眠,然后又开始执行第一个线程,也就是说off_418004是奇数的时候才会对字符进行操作,当是偶数的时候会进入第二个线程进行减一的操作并不会对字符串进行操作。

堆栈不平衡

然后看第一个线程的加密部分,f5报错,是因为堆栈不平衡,之后卡了好久。。。

IDA中Options->General选中Stack pointer可以查看堆栈指针,定位到sub_411940

1
2
3
4
5
6
7
8
9
10
11
.text:004119F1     loc_4119F1:                             ; CODE XREF: sub_411940:loc_4119DE↑j
.text:004119F1 0D8 pop edi
.text:004119F2 0D4 pop esi
.text:004119F3 0D0 pop ebx
.text:004119F4 0CC add esp, 0CCh
.text:004119FA 000 cmp ebp, esp
.text:004119FC 000 call j___RTC_CheckEsp
.text:00411A01 000 mov esp, ebp
.text:00411A03 000 pop ebp
.text:00411A04 -04 retn
.text:00411A04 sub_411940 endp ; sp-analysis failed

修改sp指针,把-04改成000就行了

sub_411940(加密函数)

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

v3 = *(_BYTE *)(a2 + a1); // v3是初始地址加29,是输入的字符串的最后一位
if ( (v3 < 97 || v3 > 122) && (v3 < 65 || v3 > 90) )// v3不是大写字母也不是小写字母
exit(0); // 退出
if ( v3 < 97 || v3 > 122 ) // v3是大写字母
{
result = off_418000[0]; // result指向off_418000的首地址
*(_BYTE *)(a2 + a1) = off_418000[0][*(char *)(a2 + a1) - 38];// v3是大写字母off_418000处减38
}
else // v3是小写字母
{
result = off_418000[0];
*(_BYTE *)(a2 + a1) = off_418000[0][*(char *)(a2 + a1) - 96];// v3是小写字母off_418000处减96
}
return result;
}

所以就是将输入的字符串存在Source中并拷贝给Dest,之后输入的字符串循环29次,偶数次的时候不做改变,奇数次的时候对字符串进行倒序判断,当输入的字符串的末尾是大写字母时,字符串QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasd的首地址处减38,当输入的字符串的末尾是小写字母时,字符串QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasd的首地址处减96,之后和字符串TOiZiZtOrYaToUwPnToBsOaOapsyS比较,如果相同,输入的就是flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)

下标从29开使输入长度有30,而在函数sub_411880进行check时只比较前29个字符,最后一位没有进行比较,所以随意添加一位
flag{ThisisthreadofwindowshahaIsESE}

总结

这是一道巨坑的题目

  • 坑1:这个题有两个线程,这两个线程依次掌握控制权,第一个线程掌握控制权的时候才会对输入的字符串进行操作。
  • 坑2:堆栈不平衡(这个到是做题中第一次见到的),一般出现的原因可能是程序代码有一些干扰代码,让IDA的反汇编分析出现错误。比如用push + n条指令 + retn来实际跳转,而IDA会以为retn是函数要结束,结果它分析后发现调用栈不平衡,因此就提示sp analysis failed.还有可能是因为编译器的优化,IDA无法正确识别一个函数体的结尾部分,通俗来说就是找不到C中的“大括号”应该位于哪里。比如说
    1
    2
    3
    4
    5
    6
    7
    int one_function( int a,int b);
    int another_function( int a, int b)
    {
    if ( a == 0 || b == 0 )
    return -1;
    return one_function(a,b);
    }

return one_function(a,b)这条语句,在某些编译器里可能会编译成这样的指令序列

1
2
3
mov esp, ebp
pop ebp
jmp one_funcion

而IDA是通过retn指令来识别函数的结束的,它不知道这里的意思,会把它当成一个函数内部的跳转,最后就会出现sp analysis failed了

  • 坑3:对字符串的操作是倒序的,这个小问题没注意到也浪费了好长时间
  • 坑4:最初分析的时候一直认为flag是29位的,回头仔细看才发现是30位,最后一位是没有进行判断的
    总之,通过这个题学到了很多

参考
https://bbs.pediy.com/thread-158896.html
https://bbs.pediy.com/thread-140002.html

相关文章
评论
分享
  • 网鼎杯部分wp

    pwnboom1分析远程已经打不通了,远程的偏移和本地的偏移不一样,只能复现一下本地的了。 首先看到流程图,代码量很大,有很大的switch语句和嵌套结构,可能是虚拟机或者是解析器。 从下图看出是一个C语言的解析器。 然后看了...

    网鼎杯部分wp
  • 数字中国创新大赛

    又是自闭的一天。。 game这一题是关于python字节码的题目,之前没有了解过,看了几篇关于python字节码的文章,死磕,手工还原。。 python字节码 12345678910111213141516171819202122...

    数字中国创新大赛
  • hitcontraining_uaf

    一道简单的uaf的题目 保护12345Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX...

    hitcontraining_uaf
Please check the parameter of comment in config.yml of hexo-theme-Annie!