为什么要写这篇教程呢?
本文章仅提供学习,切勿将其用于不法手段!
因为想要成为一名白帽黑客,汇编语言是必须要掌握的!
无论是二进制漏洞挖掘,还是逆向工程!汇编语言,都是硬性基础之一!当然,你还需要学会C语言!为什么要学会C语言呢?IDA软件 会将 二进制代码 翻译成 汇编语言 和 C语言 !
你不会汇编语言,不会C语言,想要进行逆向工程,以及更深层次的 二进制漏洞挖掘,是非常困难!如果你希望挖掘 二进制漏洞 ,你还要学会 代码审计 !
你必须看得懂,汇编语言代码 和 C语言代码,这是硬性要求!
想要参加CTF竞赛,汇编语言 和 C语言 都是重要的底层基础知识。
现在,我们来接着讲一下二进制逆向方面的知识!漏洞挖掘的知识,会放到下一节中继续进行!
为什么要这样做呢?想要进行漏洞挖掘,首先要看得懂汇编指令代码,只有能够读懂汇编代码,才能去分析汇编代码,从而发现0day漏洞!
我们来看一下上面的图,看看里面的汇编代码,有效解读代码内容,是逆向工程的基础要求之一!
第一条指令 CALL ZwQueryInformationThread
这条指令,很明显,是调用一个Windows API 函数,这个函数的名称(这个函数的符号)为 ZwQueryInformationThread ,我们来查看一下这个函数的相关信息!
ZwQueryInformationThread 是Windows操作系统中一个未公开的(undocumented)内核函数,它用于查询线程的信息。尽管微软没有正式文档化这个函数,但开发者社区和逆向工程人员经常通过它来获取线程的详细信息,如线程的基本信息、优先级、亲和性等。
函数原型为:
NTSTATUS ZwQueryInformationThread(
_In_ HANDLE ThreadHandle,
_In_ THREADINFOCLASS ThreadInformationClass,
_Out_ PVOID ThreadInformation,
_In_ ULONG ThreadInformationLength,
_Out_opt_ PULONG ReturnLength
);
函数参数解释为:
ThreadHandle
:目标线程的句柄。这个句柄必须具有适当的访问权限,才能查询请求的线程信息。ThreadInformationClass
:指定要查询的线程信息类型。这个参数是一个枚举值,定义了不同的线程信息类别,如线程的基本信息、优先级、亲和性等。ThreadInformation
:指向接收查询结果的缓冲区的指针。缓冲区的大小和内容类型取决于ThreadInformationClass
参数的值。ThreadInformationLength
:ThreadInformation
缓冲区的大小(以字节为单位)。ReturnLength
:如果提供,此参数将接收实际写入ThreadInformation
缓冲区的字节数。这可以用于处理可变长度的信息类别。
函数返回值为:
函数返回一个NTSTATUS值,用于指示操作的成功或失败。如果操作成功,返回值通常是STATUS_SUCCESS 。
现在我们知道了,它是一个未被公开的内核函数,主要用于获取目标线程的相关信息!
接下来,我们看下一条汇编指令:STATUS_SUCCESS
TEST EAX,EAX
这条汇编指令作用是什么呢?
这条指令在这段汇编代码中,主要用于判断 ZwQueryInformationThread 函数的返回值!
更加准确地说,是判断 ZwQueryInformationThread 函数的返回值是否为 0 !
STATUS_SUCCESS
是 Windows 操作系统中表示操作成功完成的 NTSTATUS
状态码。在 x64 架构和 x86 架构下,STATUS_SUCCESS
的值都是 0x00000000
(十六进制表示)(也就是十进制的 0 )!
如果 ZwQueryInformationThread 函数的返回值为0,则说明,这个函数操作已被成功执行,没有报错!
记下来,我们再来看一下,上面这张图!
SUB RSP,38 这条指令,一般会出现在 PUSH RBP 和 MOV RBP,RSP 的指令序列之后!
PUSH RBP 和 MOV RBP,RSP 共同构成了 函数序言(函数前言,也可以理解为是每个功能函数的开始部分)!
SUB RSP,38 这条指令的主要作用是为当前函数的局部变量分配内存空间!
在这段汇编代码中,这个函数主要为 调用 ZwQueryInformationThread 函数的上层函数开辟局部变量空间!
我们需要知道,传入 ZwQueryInformationThread 函数的参数变量,也是会占用 ZwQueryInformationThread 函数的上层函数的栈内存空间的!
在 X64汇编语言教程(白帽黑客系列课程)(一)这篇文章中,我们讲解过 WINDOWS 64位系统的 MSVC 编译环境下,RDX寄存器用于存储当前函数的第一个参数内容,RCX寄存器用于存储当前函数的第二个参数内容,R8寄存器用于存储当前函数的第三个参数内容,R9寄存器用于存储当前函数的第四个参数内容。
我们需要知道,ZwQueryInformationThread 函数 有5个参数!
在 WINDOWS 64位系统的 MSVC 编译环境下,一般使用4个寄存器(RDX、RCX、R8、R9)来传递前四个参数(当然,如果存在浮点型参数,会使用到 XMM系列的寄存器)!
那么,ZwQueryInformationThread 函数的第5个参数如何传递呢?
使用栈空间来进行传递!
使用 AND QWORD PTR [RSP+20],00000000_00000000 这条汇编指令来传递 ZwQueryInformationThread 函数的第5个参数!
也就是说,从 [RSP+20] 地址开始,到 [RSP+27] 地址结束,一共8个字节,存放着 ZwQueryInformationThread 函数的第5个参数!
需要注意的是,虽然 TEST 和 AND 指令 同样都是对 操作数1 和 操作数2 进行 AND 与运算,但 TEST 指令不会改变操作数1的内容,而仅会改变RFLAGS标志寄存器的内容(尤其是ZF零标志位)!而 AND 指令 也会改变RFLAGS标志寄存器的内容(尤其是ZF零标志位),同时 会将 AND (与运算)结果存储到 操作数1所在的 内存空间 或 寄存器中!
MOV R9D,1 这条汇编指令的作用是向 ZwQueryInformationThread 函数传递第4个参数!
LEA R8,[RSP+40] 这条汇编指令的作用是向 ZwQueryInformationThread 函数传递第3个参数!
MOV RCX,-2 这条汇编指令的作用是向 ZwQueryInformationThread 函数传递第2个参数!
LEA EDX,[R9+10] 这条汇编指令的作用是向 ZwQueryInformationThread 函数传递第1个参数!
在这段汇编代码中,QWORD 代表8个字节,R9D 是 R9寄存器 的低 32 位部分!
在 WINDOWS 64位 汇编语言中,当操作数是一个内存地址时,我们需要使用 类似于 QWORD PTR 这样的操作符 去指明要访问的数据项的空间大小!QWORD PTR 代表 要访问的数据项的空间大小 为 8个字节!
在这段汇编代码中,首先将 R9寄存器的低32位设置为 00000000 00000000 00000000 00000001 ,然后 将 [R9+10] 这个数据偏移所指向的内存空间起始地址 放入 EDX寄存器中!
LEA 这个汇编指令的作用是 获得 内存地址 !
LEA EDX,[R9+10] 这个汇编指令的作用是 获得 R9+10 的计算结果!
像不像 C语言 中的 指针赋值?!
例如:
uintptr_t segment_point_r9 = 0x1234567890abcdef; // 初始化一个64位的地址值
void *segment_point_rdx = (void *)(segment_point_r9 + 10); // 对地址进行偏移(增加10个字节)
这样来看,是不是更加容易理解了?!
指针变量的本质,就是内存地址,以及内存地址上的数据!
下一章节中,还会提到 JP、JNP 、JMP 等逆向工程中经常会遇到的汇编指令!
(未完待续)