BUUCTF - Youngter-drive笔记与思考 (线程)

First Post:

Last Update:

插图ID: 89210183

Ⅰ. 解题步骤(省略细节的描述)

Ⅱ. 知识拓展(对各函数作用进行解释)

Ⅰ.

    如下为IDA分析得到的main函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//main函数(主流程)
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
HANDLE v4; // [esp+D0h] [ebp-14h]
HANDLE hObject; // [esp+DCh] [ebp-8h]

sub_4110FF();
::hObject = CreateMutexW(0, 0, 0);
j_strcpy(Destination, &Source);
hObject = CreateThread(0, 0, StartAddress, 0, 0, 0);
v4 = CreateThread(0, 0, sub_41119F, 0, 0, 0);
CloseHandle(hObject);
CloseHandle(v4);
while ( dword_418008 != -1 )
;
sub_411190();
CloseHandle(::hObject);
return 0;
}

    sub_4110FF()函数作为输入,输入内容保存在 Source

Source内容复制到Destination

    分别为 hObjectv4 各创建一个线程,并且前者中包括一个 StartAddress 函数,后者则包括 sub_41119F 函数

    有一个特别的指需要注意:dword_418008 该值将分别在上述两个函数中变换,当前值为1D—-> 30

    以及最后的 sub_411190 用于比较结果

    如下为两个进程内人函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//StartAddress函数
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);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//sub_411B10函数
void __stdcall sub_411B10(int a1)
{
while ( 1 )
{
WaitForSingleObject(hObject, 0xFFFFFFFF);
if ( dword_418008 > -1 )
{
Sleep(0x64u);
--dword_418008;
}
ReleaseMutex(hObject);
}
}

    注意到,两个函数均作 –dword_418008,但只有一方对 Source 进行 sub_41112C,以及各自都有一个Sleep(0x64),可知两个线程交替进行。

    如下为 sub_411190 函数内容

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

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

    函数逻辑较为简单:每次取 Source[dword_418008] ,根据If条件进行运算,总共运算次数应为 15 次

    解密脚本不留神给删掉了,就不放了,其他师傅那肯定都能找到。

    最后是比较函数。本没什么可说的地方,但本题稍有不同。

1
2
3
4
5
6
7
8
9
10
11
12
//sub_411880函数
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", Destination);
}

    由此代码可知其判断字符数应为 29 个。

    密文为:TOiZiZtOrYaToUwPnToBsOaOapsyS 其字符数也是 29

    但上述分析中明显可以看出,最终的flag长度应为 30 个字符,最后一个字符并没有确切方法,通过遍历得出为 ‘E’

Ⅱ.

1
2
3
4
5
HANDLE CreateMutexW(//创建或打开一个已命名或未命名的互斥对象。
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCWSTR lpName
);//本题中将hObject所指线程置空
1
2
3
4
5
6
7
8
9
HANDLE CreateThread(//创建一个线程以在调用进程的虚拟地址空间内执行
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
__drv_aliasesMem LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);//本题中为StartAddress函数建立一个子线程以运行该函数
//v4同理

以下将拓展些许有关“线程”的概念:

    一个程序在运行时占用整个进程,一个进程可以建立多个线程。这些线程能够并行(指同时进行代码处理)以加快程序的运行速度。线程的定义不在这里赘述,以下内容为线程在运用过程中的知识。

    线程能分为 “对等线程”  “分离线程” 和 “主线程”

    当一个处理器在处理一个线程时遇到慢速系统调用(sleep、read等)等需要消耗较多时间的处理需求时控制便通过上下文切换传送到下一个对等进程

参考本题:

StartAddresssub_41119F 均有一个sleep函数当该进程进行到该函数时,控制自动切换到另外一个线程并运行,并在另外一个线程中遇到Sleep,则又切换回原进程,因此才有加密 15 次

    但上述也提到,线程是并行的。这两个线程并不是严谨的交替,而是因为Sleep(0x64)这段时间足够将线程中的所有内容运行结束而有余,因此才造成了交替运行的结果

   注:Sleep函数的参数以毫秒为单位

    和一个进程相关的线程将会组成一个对等线程池,独立于其他线程创立的子线程

    主线程是所有对等线程中优先级最高的线程(这是它们的唯一区别)

    不过对于上述线程的分类,还有一个更加合理的分类: “可结合”“分离”

    可结合的线程能够被任何其他线程回收或关闭,且在回收之前,其占用的内存资源不会释放;可分离的线程则不可被其他线程关闭,其内存资源将在终止时自动释放

    另外一个需要注意的是不同线程间的共享变量

    一个进程将被加载入一块虚拟内存,而其创造的所有线程都能够访问虚拟内存的任何地方

    也就是说,线程的虚拟内存总是共享的;相反的,其寄存器从不会共享,不同线程无法调用其他线程的寄存器

    既然虚拟内存是共享的,也就是说,每个线程的栈堆是共享的;只要线程能够获取其他线程的指针,就能够调用该线程的栈堆(由此也可推出:将一个线程中的变量入栈,则其他线程便能够调用它)