本帖最后由 默小白 于 2019-1-28 10:13 编辑
翻译自:https://medium.com/@danielabloom/bolo-reverse-engineering-part-2-advanced-programming-concepts-b4e292b2f3e
BOLO:逆向工程- 第2部分(高级编程概念)
前言
在本文中,我们将分解以下编程概念并分析每条指令的反编译汇编版本:
对于BOLO:逆向工程系列的第1部分,https://www.52pojie.cn/thread-857768-1-1.html。
请注意:虽然本文使用IDA Pro来反汇编编译代码,但IDA Pro的许多功能(即图形,伪代码转换等)都可以在其他免费反汇编程序(如radare2)的插件和构建中找到。此外,在准备本文时,我冒昧地将反汇编代码中的一些变量名称从IDA预设(如“v20”)更改为它们在C代码中对应的内容。这样做是为了使每个部分更容易理解。最后,请注意,此C代码已编译为64位可执行文件,并使用IDA Pro的64位版本进行反汇编。在计算数组大小时尤其如此,因为32位寄存器(即eax)的大小通常加倍并转换为64位寄存器(即rax)。
虽然第1部分介绍并描述了循环和IF语句等基本编程概念,但本文旨在解释在逆向工程时必须解密的更高级主题。
数组
让我们从数组开始,首先,让我们看一下整个代码:
基本数组- 代码
现在,我们来看一下整个反编译程序集:
基本数组- 反编译的程序集概述
如您所见,12行代码变成了相当大的代码块。但不要被吓倒!请记住,我们在这里所做的就是设置数组!让我们一点一点地分解它:
声明一个数组 – 代码
声明一个数组 - 反汇编 在使用整数文字初始化数组时,编译器只需通过局部变量初始化长度。
编辑:上面标有“声明一个数组 - 反汇编”的照片实际上标注错误。虽然是,但是当使用整数文字初始化数组时,编译器首先通过局部变量初始化长度,上面的屏幕截图实际上是stack canary的初始化。Stack Canaries用于检测溢出攻击,如果不加以解决,可能会导致执行恶意代码。在编译期间,编译器为唯一可以使用的litArray元素分配了足够的空间,litArray [0](见下面标有“局部变量- 数组”的照片)- 正如你所看到的,唯一被分配的litArray元素是litArray [0])。编译器优化可以显着提高应用程序的速度。 对困惑感到抱歉!
局部变量- 数组
用变量声明一个数组- 代码
用变量声明一个数组 – 汇编
首先,将数组长度保存到局部变量(ArraySize),然后计算最大和最小索引值以及数组的总长度,然后使用它们计算内存中数组的基本位置。 声明一个带有预定义对象的数组 - 代码
声明一个带有预定义对象的数组 – 汇编
当声明具有预定义索引定义的数组时,编译器只是将每个预定义对象保存到其自己的变量中,该变量表示数组中的索引(即objArray4 = objArray [4]) 初始化数组索引 - 代码
初始化数组索引– 汇编
就像声明具有预定义索引定义的数组一样,在初始化(或设置)数组中的索引时,编译器会为所述索引创建一个新变量。
从数组中检索项目- 代码
从数组中检索项目– 汇编
从数组中检索项时,该项从数组中的索引中获取并设置为所需的变量。
用变量创建矩阵- 代码 用变量创建矩阵- 汇编
创建矩阵时,首先将行和列大小设置为它们的行和列变量。接下来,计算行和列的最大和最小索引,并用于计算内存中矩阵的基本位置/总大小。
输入矩阵- 代码 输入矩阵- 汇编
当输入矩阵时,首先使用矩阵的基本位置计算所需索引的位置。接下来,将所述索引位置的内容设置为期望的输入(即1337)。
从矩阵中检索- 代码
从矩阵中检索- 汇编
当从矩阵中检索时,再次执行与在矩阵索引的输入序列期间执行的计算相同的计算,但是不是将某些内容输入到索引中,而是检索索引的内容并将其设置为期望的变量(即,MatrixLeet)。
指针
现在我们已经了解了如何使用和查看汇编数组,让我们继续讨论指针。
nt num = 10 pointer = &num
接下来,我们将num变量(即10)的内容设置为指针变量的内容。
打印num – 汇编
我们打印出num变量。
打印*pointer – 汇编
我们打印出指针变量。
打印num地址–汇编
我们使用lea(加载有效地址)操作码而不是mov打印出num变量的地址。
打印指针变量num的地址- 汇编
我们通过指针变量打印num变量的地址。
打印指针地址- 汇编
我们使用lea(加载有效地址)操作码而不是mov来打印指针变量的地址。
动态内存分配
我们列表中的下一个项目是动态内存分配。在本教程中,我将使用以下内容分解内存分配:
1. malloc
2. calloc
3. realloc
malloc - 动态内存分配
首先,我们来看看代码:
使用malloc进行动态内存分配- 代码
在这个函数中,我们使用malloc分配11个字符,然后将“Hello World”复制到分配的内存空间中。
现在,我们来看看汇编代码:
请注意:在整个汇编代码中,您可能会看到“nop”说明。这些说明是我在本文的准备阶段专门安排的,这样我就可以轻松地在整个汇编代码中定位和注释。
使用malloc进行动态内存分配 - 汇编
使用malloc时,首先将分配的内存大小(0x0B)首先移入edi寄存器。接下来,调用_malloc系统函数来分配内存。然后将分配的存储区存储在ptr变量中。接下来,“ Hello World ”字符串被分解为“ Hello Wo ”和“ rld ”,因为它被复制到分配的存储空间中。最后,打印出新复制的“ Hello World ”字符串,并使用_ free系统函数释放分配的内存。
calloc - 动态内存分配
首先,我们来看看代码:
使用calloc进行动态内存分配- 代码
与malloc技术非常相似,分配了11个字符的空间,然后将“Hello World”字符串复制到所述空间中。然后,打印出新重定位的“Hello World”并释放分配的内存空间。使用calloc进行动态内存分配 - 汇编
使用calloc和malloc进行动态内存分配的汇编代码基本相同。首先,使用_calloc系统函数分配11个字符(0x0B)的空间。然后,“ Hello World ”字符串被分解为“ Hello Wo ”和“ rld ”,因为它被复制到新分配的存储区中。接下来,打印出新重定位的“ Hello World ”字符串,并使用_free系统函数释放分配的内存区域。
realloc - 动态内存分配
首先,让我们看一下代码:
使用realloc进行动态内存分配- 代码
在此函数中,使用malloc分配11个字符的空间。然后,在通过使用realloc重新分配所述存储器位置以适合21个字符之前,将“ Hello World”复制到新分配的存储器空间中。最后,将“ 1337 h4x0r @nonymoose ”复制到新重新分配的空间中。最后,在打印之后,释放内存。
现在,我们来看看汇编代码:请注意:在整个汇编代码中,您可能会看到“nop”说明。这些说明是我在本文的准备阶段专门安排的,这样我就可以轻松地在整个汇编代码中定位和注释。
使用realloc 进行动态内存分配- 汇编
首先,使用malloc分配内存,就像在上面的“ malloc – 动态内存分配”部分中一样。然后,在打印出新重定位的“ Hello World ”字符串后,realloc(_realloc系统调用)被称为ptr变量(表示代码中的mem_alloc变量),并传入大小为0x15(十进制21)。接下来,“ 1337 h4x0r @nonymoose ”被分解为“ 1337 h4x ”,“ 0r @nony ”,“ moos ”和“ e”“因为它被复制到新重新分配的内存空间中。最后,使用_free系统调用释放空间
套接字编程
接下来,我们将通过分解一个非常基本的TCP客户端- 服务器聊天系统来介绍套接字编程。
在我们开始分解服务器/客户端代码之前,重要的是指出文件顶部的以下代码行:
定义端口号
此行将PORT变量定义为1337。此变量将在客户端和服务器中用作用于创建连接的网络端口。
服务器
首先,让我们看一下代码:
服务器- 代码
首先,使用AF_INET域,SOCK_STREAM类型和协议代码0创建套接字文件描述符“ server ” 。接下来,配置套接字选项和地址。然后,套接字绑定到网络地址/端口,服务器开始在所述服务器上侦听,最大队列长度为3。收到连接后,服务器将其接受到sock变量中,并将传输的值读入值变量。最后,服务器在函数返回之前通过连接发送serverhello字符串。
现在,我们将其分解为汇编:
启动服务器变量
首先,创建并初始化服务器变量。
server = socket(...) - 汇编
接下来,分别通过使用通过edx,esi和edi寄存器传递的协议,类型和域设置调用_socket系统函数来创建套接字文件描述符“ server ” 。
setockopt(...) - 汇编
然后,调用setsockopt来设置' server '套接字文件描述符上的套接字选项。
地址初始化- 汇编
接下来,通过adress.sin_family,address.sin_addr.s_addr和address.sin_port初始化服务器的地址。
bind(...) -汇编
在地址和套接字配置时,服务器使用_bind系统调用绑定到网络地址。
listen(...) - 汇编
绑定后,服务器通过传入' server '套接字文件描述符并且最大队列长度为3来侦听套接字。
sock = accept(...) – 汇编
建立连接后,服务器接受套接字连接到sock变量。
value = read(...) - 汇编
然后,服务器使用_read系统调用将传输的消息读入值变量。
send(...) - 汇编
最后,服务器通过s变量(代表代码中的serverhello)发送serverhello消息。
客户端
首先,让我们看一下代码:
客户端- 代码
首先,使用AF_INET域,SOCK_STREAM类型和协议代码0创建套接字文件描述符' sock ' 。接着,在使用server_addr.sin_family和server_addr.sin_port设置地址信息之前,使用memset将server_addr的内存区域填充为0。接下来,在客户端连接到服务器之前,使用inet_pton将地址信息从文本转换为二进制格式。连接后,客户端发送它的helloclient字符串,然后将服务器的响应读入值变量。最后,打印出值变量并返回函数。
现在,让我们分解汇编:
客户端变量初始化- 汇编
首先,初始化客户端的局部变量。
sock = socket(...)- 汇编
'sock'套接字文件描述符是通过调用_socket系统函数并分别通过edx,esi和edi寄存器传递协议,类型和域信息来创建的。
memset(...)- 汇编
接下来,使用_memset系统调用将server_address变量(在程序集中表示为's')填充为'0'(0x30)。
客户端- 地址配置- 汇编
然后,配置服务器的地址信息。
inet_pton(...)- 汇编
接下来,使用_inet_pton系统调用将地址从文本转换为二进制格式。请注意,由于代码中未明确定义地址,因此假定为localhost(127.0.0.1)。
connect(...)– 汇编
客户端使用_connect系统调用连接到服务器。
send(...)- 汇编
连接后,客户端将helloClient字符串发送到服务器。
value = read(...)
最后,客户端使用_read系统调用将服务器的回复读入值变量。
线程
最后,我们将介绍C语言中的线程基础知识。
首先,让我们看一下代码:
线程- 代码
如您所见,程序首先打印“ This is is the thread ”,然后使用pthread_create函数创建一个指向* mythread函数的新线程。完成* mythread函数后(在睡眠1秒后打印“ Hello from mythread ”),新线程使用pthread_join函数连接回主线程并打印“ This is is the thread ”。
现在,让我们分解汇编:
打印“This is before the thread”- 汇编
首先,程序打印“This is before the thread”。
创建一个新线程– 汇编
接下来,使用_pthread_create系统调用创建一个新线程。这个线程指向mythread,因为它是启动例程。
mythread函数- 汇编
正如您所看到的,mythread函数只需在打印“ Hello from mythread ” 之前休眠一秒钟。
请注意:在mythread函数中,您将看到两个'nop'。这些内容专门用于在本文准备阶段轻松导航。
将mythread函数的线程连接回主线程– 汇编
从mythread函数返回后,新线程使用_pthread_join函数与主线程连接。
打印“This is after the thread” -汇编
最后,打印出“This is after the thread ”并且函数返回。
结束陈述
我希望这篇文章能够阐明一些更高级的编程概念及其底层汇编代码。既然我们已经涵盖了所有主要的编程概念,那么BOLO中的下几篇文章:逆向工程系列将专门针对不同类型的攻击和易受攻击的代码,以便您能够更快地通过静态分析识别封闭源中的漏洞和攻击程序。
emmmmm
pdf自提:链接:https://pan.baidu.com/s/1R7DDPWuhKPxPyYFUFo9oQA 密码:adso |