BUUCTF 2023 WEEK4|REVERSE kooh_slT

参考博客:
TLS回调函数–一文看懂(详)

NewstarCTF2023 week4 re

思路

main函数:

image-20240811165013781

calloc不仅分配内存,还会将分配的内存初始化为0。

创建了一个新的线程,线程的起始地址是StartAddress函数。CreateThread函数返回的线程句柄存储在hHandle

WaitForSingleObject(hHandle, 0xFFFFFFFF);

这里的WaitForSingleObject函数用于等待线程hHandle执行完毕。0xFFFFFFFF(即INFINITE)表示无限等待,直到线程执行完成。

sub401500明显是base64

进入StartAddress:

image-20240811171621563

在声明变量、创建快照、初始化之后

CreateToolhelp32Snapshot 创建一个进程快照,参数 2u 表示快照类型为所有进程。如果创建失败(即返回 INVALID_HANDLE_VALUE(HANDLE)-1),函数直接返回 0,表示失败。

pe.dwSize 必须设置为 PROCESSENTRY32W 结构体的大小,以便调用 Process32FirstW 函数时正确使用

似乎什么都没有做,看汇编,有一部分是灰色的,花指令:

image-20240811172330917

先把跳转函数nop

直接Crtl+N容易把后面信息也nop了,这里shift+F2

image-20240811185021197

剩了点,一会重新分析就好,按C分析,下面还有一部分

image-20240811185127542

全部重新分析后可以看到完整的函数:

image-20240811234931637

1
2
3
hSnapshot = CreateToolhelp32Snapshot(2u, 0);
if ( hSnapshot == (HANDLE)-1 )
return 0;
  • CreateToolhelp32Snapshot 传入的参数 2u 表示获取系统中所有的进程快照。成功时返回一个快照句柄,失败时返回 INVALID_HANDLE_VALUE (通常为 (HANDLE)-1)。
  • 如果 CreateToolhelp32Snapshot 返回 -1,函数立即返回 0,表示失败。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if ( Process32FirstW(hSnapshot, &pe) )
{
do
{
//在每次循环中,代码通过 `wcscmp` 比较当前进程名称 `pe.szExeFile` 与存储在 `word_4A7010` 的9个进程名称(每个名称长度为50个字节)进行比较
for ( i = 0; i < 9; ++i )
{
wcscmp(pe.szExeFile, (const wchar_t *)&word_4A7010[50 * i]);
OpenProcess(0x1FFFFFu, 0, pe.th32ProcessID);//调用 `OpenProcess` 尝试打开进程,标志值 `0x1FFFFFu` 代表所需的所有访问权限。
}
//在循环结束后(即没有匹配的进程名),关闭快照句柄,并调用 `sub_401500` 进行进一步的处理,然后返回 `1`,表示成功。
while ( Process32NextW(hSnapshot, &pe) );
CloseHandle(hSnapshot);
sub_401500(byte_4A7E74, dword_4A7E70, Str1);
return 1;
}
else//如果 `Process32FirstW` 失败,表示没有进程信息可枚举,直接关闭句柄并返回 `0`。
{
CloseHandle(hSnapshot);
return 0;
}

似乎中间有个死循环,怪不得动调了一阵子都动不了……

再改:

image-20240812003624975

终于对了:
image-20240812004235808

题目是TLS,看旁边函数

TlsCallback_0:

image-20240812000053323

学到一个很好的修改检测反调试方法:

image-20240812000405368

师傅说:(记笔记)image-20240812000131184

不太懂TLScallback,去网上搜索

首先是TLS:

Thread Local Storage,线程局部存储:各线程独立的数据存储空间。使用TLS技术可以在线程内部独立使用或修改进程的全局数据或静态数据, 就像对待自身的局部变量一样

TLS回调函数常用于反调试,主要是利用了TLS回调函数的调用要先于EP代码的执行

如果开启了TLS功能,PE文件头就会设置TLS表,IMAGE_NT_HEADERS-IMAGE_OPTIONAL_HEADER-IMAGE_DATA_DIRECTORY[9]描述了IMAGE_TLS_DIRECTORY结构体的位置。

# 作者(Author):chillysome
# 链接(URL):http://www.chillysome.top/index.php/2024/03/15/tls%e5%9b%9e%e8%b0%83%e5%87%bd%e6%95%b0-%e4%b8%80%e6%96%87%e7%9c%8b%e6%87%82%ef%bc%88%e8%af%a6%ef%bc%89/

所以上面while循环有9次,似乎也有些关系

继续搜,TLS回调函数,每当创建或终止进程的线程时会自动调用执行的函数。

参数顺序和定义是一样的,第一个参数是模块句柄、第二个标识调用TLS回调函数原因,有4:

1
2
3
4
5
6
7
8
#define DLL_PROCESS_ATTACH 1
#define DLL_THREAD_ATTACH 2
#define DLL_THREAD_DETACH 3
#define DLL_PROCESS_ATTACH 0
//主线程调用main前调用TLS回调函数,调用原因为DLL_PROCESS_ATTACH
//子线程启动前调用TLS,原因为DLL_THREAD_ATTACH
//子线程结束后调用TLS,原因为DLL_THREAD_DETACH
//主线程结束后调用TLS 原因为DLL_PROCESS_DETACH

而运行时调用顺序,1–2–3–0

运行流程:

  • 进入case1,检测调试器,存在则卡死进程;通过main函数中CreateThread,接着WaitForSingleObject等待线程结束
  • 线程进入case2,反反调试检擦,接着对sub_4a7000修改,动调进入sub_4013B0运行取出key;接着调用sub_401500进入sub_401170,base64加密
  • 进入case3,sub_401500的调用改为sub_4013B0
  • 退出线程,进入main中sub_401500,进行xxtea加密,比较不相同,退出进程
  • 进入case0,TLS最后的check函数,检测后输出True

动调:

(一定要应用修改于程序……(Edit–>

image-20240812010612755

image-20240812011907961

Qu7e3T0yEknVghYa6vf1oOsF94H+LxIqbtKpzw8U5CJN2XlZRPM/SdAWDGcjBimr

image-20240812012210949

image-20240812012718410

发现跳转至4013B0,

P命令构建函数,是xxtea加密,

取出key:

0x74,0x404,0xBF,0x2652

image-20240812011228728

取出密文:

dd 3400A0D0h, 0B23CFFEBh, 0CDE69111h, 32D0771h, 0FA1D9E6Ch
.data:004A7450 dd 9D15360Ah, 933EBF03h, 9F12DDA6h, 8C58DDA1h, 46BEE3E0h
.data:004A7464 dd 4476F65h, 3C44CEF9h

顺序求解就ok