逆向工程 - 第2部分(高级编程概念)
本帖最后由 默小白 于 2019-1-28 10:13 编辑翻译自:https://medium.com/@danielabloom/bolo-reverse-engineering-part-2-advanced-programming-concepts-b4e292b2f3e
https://cdn-images-1.medium.com/max/1600/1*oqEKTP5xujciVYG9Bwhjpg.jpegBOLO:逆向工程- 第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语句等基本编程概念,但本文旨在解释在逆向工程时必须解密的更高级主题。
数组
让我们从数组开始,首先,让我们看一下整个代码:
https://cdn-images-1.medium.com/max/1600/1*7TLK_pT0tScJ0ZCrcEigZQ.jpeg基本数组- 代码
现在,我们来看一下整个反编译程序集:
https://cdn-images-1.medium.com/max/1600/1*IRjog2fdpRLZiN7i3rx1pA.jpeg基本数组- 反编译的程序集概述
如您所见,12行代码变成了相当大的代码块。但不要被吓倒!请记住,我们在这里所做的就是设置数组!让我们一点一点地分解它:
https://cdn-images-1.medium.com/max/1600/1*Kjvmza3H_h-onqjVqlujYw.jpeg声明一个数组 – 代码
https://cdn-images-1.medium.com/max/1600/1*ct9wZJAeZOe-NZeu2_E6Dw.jpeg声明一个数组 - 反汇编在使用整数文字初始化数组时,编译器只需通过局部变量初始化长度。
编辑:上面标有“声明一个数组 - 反汇编”的照片实际上标注错误。虽然是,但是当使用整数文字初始化数组时,编译器首先通过局部变量初始化长度,上面的屏幕截图实际上是stack canary的初始化。Stack Canaries用于检测溢出攻击,如果不加以解决,可能会导致执行恶意代码。在编译期间,编译器为唯一可以使用的litArray元素分配了足够的空间,litArray (见下面标有“局部变量- 数组”的照片)- 正如你所看到的,唯一被分配的litArray元素是litArray )。编译器优化可以显着提高应用程序的速度。 对困惑感到抱歉!
https://cdn-images-1.medium.com/max/1600/1*r3F171GNfhE_FtIIHOr29Q.jpeg局部变量- 数组
https://cdn-images-1.medium.com/max/1600/1*2ZUdI4fZwfcJlG34BvsWeg.jpeg用变量声明一个数组- 代码
https://cdn-images-1.medium.com/max/1600/1*sbRpjwwlEUIYbhEqDO5P4Q.jpeg用变量声明一个数组 – 汇编
首先,将数组长度保存到局部变量(ArraySize),然后计算最大和最小索引值以及数组的总长度,然后使用它们计算内存中数组的基本位置。
https://cdn-images-1.medium.com/max/1600/1*0XKvDyLxaxMZ4ceuy_EEow.jpeg声明一个带有预定义对象的数组 - 代码
https://cdn-images-1.medium.com/max/1600/1*EWs2A8b6If16TEawoP0AjQ.jpeg声明一个带有预定义对象的数组 – 汇编
当声明具有预定义索引定义的数组时,编译器只是将每个预定义对象保存到其自己的变量中,该变量表示数组中的索引(即objArray4 = objArray )
https://cdn-images-1.medium.com/max/1600/1*rp6SXXxA2B2uluwh3oo_Tw.jpeg初始化数组索引 - 代码
https://cdn-images-1.medium.com/max/1600/1*VJb1v9kFiTK8yPHKRV6TtQ.jpeg初始化数组索引– 汇编
就像声明具有预定义索引定义的数组一样,在初始化(或设置)数组中的索引时,编译器会为所述索引创建一个新变量。
https://cdn-images-1.medium.com/max/1600/1*t9j9VRKOGURAyYSzym2WHQ.jpeg从数组中检索项目- 代码
https://cdn-images-1.medium.com/max/1600/1*L14utDNpm5LG6hjgkECZBg.jpeg从数组中检索项目– 汇编
从数组中检索项时,该项从数组中的索引中获取并设置为所需的变量。
https://cdn-images-1.medium.com/max/1600/1*MQ4opMMBLUdljXLKcZpfig.jpeg用变量创建矩阵- 代码
https://cdn-images-1.medium.com/max/1600/1*5Ui0v48VC3F-rBtJJYqYPg.jpeg用变量创建矩阵- 汇编
创建矩阵时,首先将行和列大小设置为它们的行和列变量。接下来,计算行和列的最大和最小索引,并用于计算内存中矩阵的基本位置/总大小。
https://cdn-images-1.medium.com/max/1600/1*c9JS0h151ONz0XWs9O9rmA.jpeg输入矩阵- 代码
https://cdn-images-1.medium.com/max/1600/1*FvMp_3V_qKTdBl8q4Z3vBw.jpeg输入矩阵- 汇编
当输入矩阵时,首先使用矩阵的基本位置计算所需索引的位置。接下来,将所述索引位置的内容设置为期望的输入(即1337)。
https://cdn-images-1.medium.com/max/1600/1*TGyQPmwumYxq8WV6MV9W_A.jpeg从矩阵中检索- 代码
https://cdn-images-1.medium.com/max/1600/1*I2XxTtaDUyBrFTEhZZ1yWg.jpeg从矩阵中检索- 汇编
当从矩阵中检索时,再次执行与在矩阵索引的输入序列期间执行的计算相同的计算,但是不是将某些内容输入到索引中,而是检索索引的内容并将其设置为期望的变量(即,MatrixLeet)。
指针
现在我们已经了解了如何使用和查看汇编数组,让我们继续讨论指针。
https://cdn-images-1.medium.com/max/1600/1*G005o4GYWY5xSl2tqOtL-w.jpeg指针- 代码
分析汇编:
https://cdn-images-1.medium.com/max/1600/1*U2K3gQ4Te5chwxk0EIwYhQ.jpegnt num = 10
首先我们将int num设置为10。
https://cdn-images-1.medium.com/max/1600/1*jlgJUgiUp1pqv1rafHWBeQ.jpegpointer = &num
接下来,我们将num变量(即10)的内容设置为指针变量的内容。
https://cdn-images-1.medium.com/max/1600/1*5F7MBnrw7sJlmOSYYlxl7Q.jpeg打印num – 汇编
我们打印出num变量。
https://cdn-images-1.medium.com/max/1600/1*6FIyzxO1uRgIwRMteIlsNg.jpeg打印*pointer – 汇编
我们打印出指针变量。
https://cdn-images-1.medium.com/max/1600/1*ZiD0laHjOh_fUJU4RcIGTQ.jpeg打印num地址–汇编
我们使用lea(加载有效地址)操作码而不是mov打印出num变量的地址。
https://cdn-images-1.medium.com/max/1600/1*uGFeln-rXi3pVI3nPA-seA.jpeg打印指针变量num的地址- 汇编
我们通过指针变量打印num变量的地址。
https://cdn-images-1.medium.com/max/1600/1*VSXplc4jCIJ8OtX7F5qVjw.jpeg打印指针地址- 汇编
我们使用lea(加载有效地址)操作码而不是mov来打印指针变量的地址。
动态内存分配
我们列表中的下一个项目是动态内存分配。在本教程中,我将使用以下内容分解内存分配:
1.malloc
2.calloc
3.realloc
malloc - 动态内存分配
首先,我们来看看代码:
https://cdn-images-1.medium.com/max/1600/1*w7MumYWP-Ivy73mqW9SrCA.jpeg使用malloc进行动态内存分配- 代码
在这个函数中,我们使用malloc分配11个字符,然后将“Hello World”复制到分配的内存空间中。
现在,我们来看看汇编代码:
请注意:在整个汇编代码中,您可能会看到“nop”说明。这些说明是我在本文的准备阶段专门安排的,这样我就可以轻松地在整个汇编代码中定位和注释。
https://cdn-images-1.medium.com/max/1600/1*ZAP2wcayxeIBx5EYxRvngQ.jpeg使用malloc进行动态内存分配 - 汇编
使用malloc时,首先将分配的内存大小(0x0B)首先移入edi寄存器。接下来,调用_malloc系统函数来分配内存。然后将分配的存储区存储在ptr变量中。接下来,“ Hello World ”字符串被分解为“ Hello Wo ”和“ rld ”,因为它被复制到分配的存储空间中。最后,打印出新复制的“ Hello World ”字符串,并使用_ free系统函数释放分配的内存。
calloc - 动态内存分配
首先,我们来看看代码:
https://cdn-images-1.medium.com/max/1600/1*ccIUL9tLor8dXsKaWMHc1A.jpeg使用calloc进行动态内存分配- 代码
与malloc技术非常相似,分配了11个字符的空间,然后将“Hello World”字符串复制到所述空间中。然后,打印出新重定位的“Hello World”并释放分配的内存空间。https://cdn-images-1.medium.com/max/1600/1*sr5pmd2IRpZ6koVNI_Nsrw.jpeg使用calloc进行动态内存分配 - 汇编
使用calloc和malloc进行动态内存分配的汇编代码基本相同。首先,使用_calloc系统函数分配11个字符(0x0B)的空间。然后,“ Hello World ”字符串被分解为“ Hello Wo ”和“ rld ”,因为它被复制到新分配的存储区中。接下来,打印出新重定位的“ Hello World ”字符串,并使用_free系统函数释放分配的内存区域。
realloc - 动态内存分配
首先,让我们看一下代码:
https://cdn-images-1.medium.com/max/1600/1*8Tk7R0hqRMd0sIdFajhraA.jpeg使用realloc进行动态内存分配- 代码
在此函数中,使用malloc分配11个字符的空间。然后,在通过使用realloc重新分配所述存储器位置以适合21个字符之前,将“ Hello World”复制到新分配的存储器空间中。最后,将“ 1337 h4x0r @nonymoose ”复制到新重新分配的空间中。最后,在打印之后,释放内存。
现在,我们来看看汇编代码:请注意:在整个汇编代码中,您可能会看到“nop”说明。这些说明是我在本文的准备阶段专门安排的,这样我就可以轻松地在整个汇编代码中定位和注释。
https://cdn-images-1.medium.com/max/1600/1*2ZnFm7kDakFjFenJ0FklWw.jpeg使用realloc 进行动态内存分配- 汇编
首先,使用malloc分配内存,就像在上面的“ malloc – 动态内存分配”部分中一样。然后,在打印出新重定位的“ Hello World ”字符串后,realloc(_realloc系统调用)被称为ptr变量(表示代码中的mem_alloc变量),并传入大小为0x15(十进制21)。接下来,“ 1337 h4x0r @nonymoose ”被分解为“ 1337 h4x ”,“ 0r @nony ”,“ moos ”和“ e”“因为它被复制到新重新分配的内存空间中。最后,使用_free系统调用释放空间
套接字编程
接下来,我们将通过分解一个非常基本的TCP客户端- 服务器聊天系统来介绍套接字编程。
在我们开始分解服务器/客户端代码之前,重要的是指出文件顶部的以下代码行:
https://cdn-images-1.medium.com/max/1600/1*tY_bs6YhpOvGziCu45KpGw.jpeg定义端口号
此行将PORT变量定义为1337。此变量将在客户端和服务器中用作用于创建连接的网络端口。
服务器
首先,让我们看一下代码:
https://cdn-images-1.medium.com/max/1600/1*w3CwKjfc4KV_II2Hws5xhg.jpeg服务器- 代码
首先,使用AF_INET域,SOCK_STREAM类型和协议代码0创建套接字文件描述符“ server ” 。接下来,配置套接字选项和地址。然后,套接字绑定到网络地址/端口,服务器开始在所述服务器上侦听,最大队列长度为3。收到连接后,服务器将其接受到sock变量中,并将传输的值读入值变量。最后,服务器在函数返回之前通过连接发送serverhello字符串。
现在,我们将其分解为汇编:
https://cdn-images-1.medium.com/max/1600/1*49iLJvYd-AlsUdrEEcsIvQ.jpeg启动服务器变量
首先,创建并初始化服务器变量。
https://cdn-images-1.medium.com/max/1600/1*ZpuIBq9nq0gEDAc1OEHaDg.jpegserver = socket(...) - 汇编
接下来,分别通过使用通过edx,esi和edi寄存器传递的协议,类型和域设置调用_socket系统函数来创建套接字文件描述符“ server ” 。
https://cdn-images-1.medium.com/max/1600/1*xGKVE1UNpzzpcT0xjN3nxQ.jpegsetockopt(...) - 汇编
然后,调用setsockopt来设置' server '套接字文件描述符上的套接字选项。
https://cdn-images-1.medium.com/max/1600/1*EJSEmZ04TP3dsolo13SndQ.jpeg地址初始化- 汇编
接下来,通过adress.sin_family,address.sin_addr.s_addr和address.sin_port初始化服务器的地址。
https://cdn-images-1.medium.com/max/1600/1*nef4-JYBdmhREbzN8rKOCA.jpegbind(...) -汇编
在地址和套接字配置时,服务器使用_bind系统调用绑定到网络地址。
https://cdn-images-1.medium.com/max/1600/1*l6C-wsr_FvMvNmbD3wZcYA.jpeglisten(...) - 汇编
绑定后,服务器通过传入' server '套接字文件描述符并且最大队列长度为3来侦听套接字。
https://cdn-images-1.medium.com/max/1600/1*EQUvUQzu5upSatxY3Dz3yA.jpegsock = accept(...)– 汇编
建立连接后,服务器接受套接字连接到sock变量。
https://cdn-images-1.medium.com/max/1600/1*CQR3e5VBZzKi5FJK3zHCng.jpegvalue = read(...) - 汇编
然后,服务器使用_read系统调用将传输的消息读入值变量。
https://cdn-images-1.medium.com/max/1600/1*we3LlVt5ndIF4PC1payxNg.jpegsend(...) - 汇编
最后,服务器通过s变量(代表代码中的serverhello)发送serverhello消息。
客户端
首先,让我们看一下代码:
https://cdn-images-1.medium.com/max/1600/1*ea_r5ne-yrsdn-fjRLmd4Q.jpeg客户端- 代码
首先,使用AF_INET域,SOCK_STREAM类型和协议代码0创建套接字文件描述符' sock ' 。接着,在使用server_addr.sin_family和server_addr.sin_port设置地址信息之前,使用memset将server_addr的内存区域填充为0。接下来,在客户端连接到服务器之前,使用inet_pton将地址信息从文本转换为二进制格式。连接后,客户端发送它的helloclient字符串,然后将服务器的响应读入值变量。最后,打印出值变量并返回函数。
现在,让我们分解汇编:
https://cdn-images-1.medium.com/max/1600/1*W37r9XgVCtZ6TDNgxm4bfg.jpeg客户端变量初始化- 汇编
首先,初始化客户端的局部变量。
https://cdn-images-1.medium.com/max/1600/1*OwitW1suc9Os6iPIksFXSA.jpegsock = socket(...)- 汇编
'sock'套接字文件描述符是通过调用_socket系统函数并分别通过edx,esi和edi寄存器传递协议,类型和域信息来创建的。
https://cdn-images-1.medium.com/max/1600/1*uUFWXASXdQ20B040tHkPZg.jpegmemset(...)- 汇编
接下来,使用_memset系统调用将server_address变量(在程序集中表示为's')填充为'0'(0x30)。
https://cdn-images-1.medium.com/max/1600/1*ZrtVFYK7napGfBerQ1y-EA.jpeg客户端- 地址配置- 汇编
然后,配置服务器的地址信息。
https://cdn-images-1.medium.com/max/1600/1*v10aph5xu1JLDKSUxdETOg.jpeginet_pton(...)- 汇编
接下来,使用_inet_pton系统调用将地址从文本转换为二进制格式。请注意,由于代码中未明确定义地址,因此假定为localhost(127.0.0.1)。
https://cdn-images-1.medium.com/max/1600/1*L17-VOfRIPWfMAAPvCfMNQ.jpegconnect(...)– 汇编
客户端使用_connect系统调用连接到服务器。
https://cdn-images-1.medium.com/max/1600/1*fg2ZHjL2DGKbdzPFNS00zA.jpegsend(...)- 汇编
连接后,客户端将helloClient字符串发送到服务器。
https://cdn-images-1.medium.com/max/1600/1*ErT9XjTZzKmY33f40zZB9w.jpegvalue = read(...)
最后,客户端使用_read系统调用将服务器的回复读入值变量。
线程
最后,我们将介绍C语言中的线程基础知识。
首先,让我们看一下代码:
https://cdn-images-1.medium.com/max/1600/1*To0jxrZSISH4GAqNvmgv2w.jpeg线程- 代码
如您所见,程序首先打印“ This is is the thread ”,然后使用pthread_create函数创建一个指向* mythread函数的新线程。完成* mythread函数后(在睡眠1秒后打印“ Hello from mythread ”),新线程使用pthread_join函数连接回主线程并打印“ This is is the thread ”。
现在,让我们分解汇编:
https://cdn-images-1.medium.com/max/1600/1*WCWATxghM6VGmureDrjtUQ.jpeg打印“This is before the thread”- 汇编
首先,程序打印“This is before the thread”。
https://cdn-images-1.medium.com/max/1600/1*_k9asasHdm6HeO2wxoBfQA.jpeg创建一个新线程– 汇编
接下来,使用_pthread_create系统调用创建一个新线程。这个线程指向mythread,因为它是启动例程。
https://cdn-images-1.medium.com/max/1600/1*OV1aYVCs2pshIcZFrbkg6A.jpegmythread函数- 汇编
正如您所看到的,mythread函数只需在打印“ Hello from mythread ” 之前休眠一秒钟。
请注意:在mythread函数中,您将看到两个'nop'。这些内容专门用于在本文准备阶段轻松导航。
https://cdn-images-1.medium.com/max/1600/1*jDHSodQZawnA1XJITslu5w.jpeg将mythread函数的线程连接回主线程– 汇编
从mythread函数返回后,新线程使用_pthread_join函数与主线程连接。
https://cdn-images-1.medium.com/max/1600/1*u-cxV5gW1OjS6D0SQMWsCw.jpeg打印“This is after the thread” -汇编
最后,打印出“This is after the thread ”并且函数返回。
结束陈述
我希望这篇文章能够阐明一些更高级的编程概念及其底层汇编代码。既然我们已经涵盖了所有主要的编程概念,那么BOLO中的下几篇文章:逆向工程系列将专门针对不同类型的攻击和易受攻击的代码,以便您能够更快地通过静态分析识别封闭源中的漏洞和攻击程序。
emmmmm
pdf自提:链接:https://pan.baidu.com/s/1R7DDPWuhKPxPyYFUFo9oQA密码:adso 期待PDF版本 {:1_921:}参与学习{:1_921:} 辛苦了,继续努力
图挂了么 看不到图了 感谢楼主分享,支持一下!
辛苦了,继续努力 为什么看不了图 好久不见!
支持此系列,对我的帮助非常大! 支持,期待pdf