逆向工程 - 第1部分(基本编程概念)
本帖最后由 默小白 于 2019-1-28 12:10 编辑转自:https://xz.aliyun.com/t/3840
翻译自:https://medium.com/bugbountywriteup/bolo-reverse-engineering-part-1-basic-programming-concepts-f88b233c63b7
https://xzfile.aliyuncs.com/media/upload/picture/20190113012510-034bd906-168f-1.jpg
在逆向工程学习过程我发现自己想要一个简单的指南,以便在浏览汇编代码时查找内容。 虽然我非常相信阅读源代码和手册来获取信息,但我完全理解想在一篇文章里面拥有简洁,易于理解,信息量大的代码愿望。 “逆向工程”系列正是如此! 在本系列文章中,我将向您展示逆向工程代码需要注意的事项。本系列文章将使初学者逆向工程师更容易掌握许多不同的概念在理想的情况下!
前言
在本文中,您将看到C ++代码和汇编代码的屏幕截图以及有关你看到的这些截图为什么是你看到的这样的一些解释。此外,本系列文章不会介绍汇编的基础知识,它只会提供模式和反编译代码,以便您可以大致了解要查找的内容/如何解释汇编代码。
在本文中,我们将介绍:
[*]变量初始化
[*]基本输出
[*]数学运算
[*]函数
[*]循环(For循环/While循环)
[*]条件声明(IF声明/switch声明)
[*]用户输入
请注意:本教程是在Microsoft Visual Studio 2015中使用visual C ++制作的(我知道,过时的版本)。一些汇编代码(即带有cin的用户输入)将反映出这一点。此外,我使用IDA Pro作为我的反汇编工具。
初始化变量
编程时变量非常重要,这里我们可以看到一些重要的变量:
a string #字符串变量
an int # 整型变量
a Boolean #布尔型变量
a char #字符型变量
a double #双精度变量
a float #单精度变量
a char array #字符型数组
https://xzfile.aliyuncs.com/media/upload/picture/20190113012726-546ed37e-168f-1.jpg
请注意:在C ++中,'string'不是原始变量,但我认为无论如何都很重要。现在,让我们来看看程序集:
https://xzfile.aliyuncs.com/media/upload/picture/20190113012805-6bcf436e-168f-1.jpg
初始化变量
在这里,我们可以看到IDA如何表示变量的空间分配。如您所见,我们在实际初始化每个变量之前为每个变量分配空间
https://xzfile.aliyuncs.com/media/upload/picture/20190113012841-8131b3fe-168f-1.jpg
一旦分配了空间,我们将我们想要设置每个变量的值移动到我们为所述变量分配的空间中。虽然这里初始化了大部分变量,但下面你会看到C ++字符串的启动。
https://xzfile.aliyuncs.com/media/upload/picture/20190113012926-9c1cd6d0-168f-1.jpg
如您所见,初始化字符串需要调用内置函数进行。
基本输出
前言信息:在本节中,我将讨论推入堆栈并用作printf函数参数的项目。函数参数的概念将在本文后面更详细地解释。
虽然本教程是用Visual C ++构建的,但我选择使用printf而不是cout来输出
https://xzfile.aliyuncs.com/media/upload/picture/20190113013043-ca0277f8-168f-1.jpg
现在,我们来看看程序集:
首先,字符串文字:
https://xzfile.aliyuncs.com/media/upload/picture/20190113013131-e64694ee-168f-1.jpg
如您所见,字符串文字被压入堆栈作为printf函数的参数被调用。
现在,我们来看看其中一个变量输出:
https://xzfile.aliyuncs.com/media/upload/picture/20190113013205-fa7c7668-168f-1.png
如您所见,首先将变量intvar移入EAX寄存器,然后将其用指示整数输出的字符串文字‘’%i’一起送入堆栈中。然后将这些变量从堆栈中取出,并作为在调用printf函数时的参数。
数学运算
在本节中,我们将讨论如下函数:
[*]加法
[*]减法
[*]乘法
[*]除法
[*]按位与
[*]按位或
[*]按位异或
[*]按位非
[*]按位右移
[*]按位左移
https://xzfile.aliyuncs.com/media/upload/picture/20190113013318-2668c560-1690-1.png
我们来将每个函数分解为汇编:
首先我们将A表示为十六进制oA,表示十进制10,B表示为十六进制的oF,表示十进制的15。
https://xzfile.aliyuncs.com/media/upload/picture/20190113013352-3ac01d06-1690-1.png
我们使用"add"进行加法:
https://xzfile.aliyuncs.com/media/upload/picture/20190113013426-4ea00ff2-1690-1.png
使用"sub"进行减法:
https://xzfile.aliyuncs.com/media/upload/picture/20190113013527-73481b60-1690-1.png
使用"imul"进行相乘:
https://xzfile.aliyuncs.com/media/upload/picture/20190113013556-8490fc84-1690-1.png
我们使用"idiv"操作码进行相除。在这种情况下,我们还使用'cdq'来加倍EAX的大小,以便我们可以适应除法运算的输出。
https://xzfile.aliyuncs.com/media/upload/picture/20190113013640-9e7eeb24-1690-1.png
使用"AND"进行按位与运算:
https://xzfile.aliyuncs.com/media/upload/picture/20190113013757-ccc61e76-1690-1.png
使用"OR"进行按位或运算:
https://xzfile.aliyuncs.com/media/upload/picture/20190113013910-f859781c-1690-1.png
使用"XOR"进行按位异或运算:
https://xzfile.aliyuncs.com/media/upload/picture/20190113013944-0c320994-1691-1.png
使用’NOT’进行按位非运算:
https://xzfile.aliyuncs.com/media/upload/picture/20190113014026-2519f5fc-1691-1.png
按位非
使用‘sar’进行右移:
https://xzfile.aliyuncs.com/media/upload/picture/20190113014052-34c3aa34-1691-1.png
使用'shl'进行按位左移:
https://xzfile.aliyuncs.com/media/upload/picture/20190113014120-45bb788a-1691-1.png
# 函数调用
在本节中,我们将介绍3种不同类型的函数:
1.一个基本的空函数
2.一个返回整数的函数
3.一个接受参数的函数
https://xzfile.aliyuncs.com/media/upload/picture/20190113014310-86f2addc-1691-1.jpg
首先,让我们看看调用newfunc()和newfuncret()这两个函数,他们实际上都没有接受任何参数。
https://xzfile.aliyuncs.com/media/upload/picture/20190113014347-9d37d734-1691-1.png
如果我们对newfunc()函数进行调用,我们可以看到它输出出来的是"Hello!I’m a new function "
https://xzfile.aliyuncs.com/media/upload/picture/20190113014435-b9944340-1691-1.png
https://xzfile.aliyuncs.com/media/upload/picture/20190113014459-c84985e4-1691-1.png
正如你所见,这个函数确实使用了retn操作码,但只是返回到了上一个位置。(这样程序能够在函数调用完成后继续。)现在,让我们看一下用c++的rand()函数生成随机整数的newfuncret()函数,然后返回所说的整数。
https://xzfile.aliyuncs.com/media/upload/picture/20190113014536-de75ed8a-1691-1.png
https://xzfile.aliyuncs.com/media/upload/picture/20190113014616-f6320d46-1691-1.png
首先为A变量分配空间。然后调用rand()函数,将其返回值放入EAX寄存器。接下来,将EAX变量移入A变量空间,将A做为rand()的结果。最后,将A变量移入EAX,以便函数可以将其用作返回值。
现在我们已经了解了如何调用函数以及函数返回内容时的样子,让我们来谈谈使用参数调用函数:
首先,让我们再看一下调用申明
https://xzfile.aliyuncs.com/media/upload/picture/20190113014709-15b70c5c-1692-1.png
https://xzfile.aliyuncs.com/media/upload/picture/20190113014738-270155b2-1692-1.png
尽管C ++中的字符串需要调用basic_string函数,但无论数据类型如何,调用带参数的函数的概念都是相同的。首先,将变量放入寄存器中,然后将寄存器压入栈中,最后调用该函数。
我们来看看函数的代码:
https://xzfile.aliyuncs.com/media/upload/picture/20190113014806-37772e08-1692-1.jpg
https://xzfile.aliyuncs.com/media/upload/picture/20190113014833-47bc4c4e-1692-1.png
所有这个函数都是接受一个字符串,一个整数和一个字符,并使用printf打印出来。如您所见,首先在函数顶部分配3个变量,然后将这些变量作为printf函数的参数压入堆栈。十分简单。
# 循环
现在我们有函数调用,输出,变量和数学运算下来,让我们继续进行流控制。首先,我们将从for循环开始:
https://xzfile.aliyuncs.com/media/upload/picture/20190113014930-69bfaf48-1692-1.png
https://xzfile.aliyuncs.com/media/upload/picture/20190113015000-7b493504-1692-1.png
在我们将汇编代码分解为更小的部分之前,让我们看看总体布局。如您所见,当for循环开始时,它有2个选项; 它可以转到右侧的框(绿色箭头)并返回,也可以转到左侧的框(红色箭头)并循环回到for循环的开头。
https://xzfile.aliyuncs.com/media/upload/picture/20190113015037-91df4cfe-1692-1.png
首先,我们通过比较i变量和max变量来检查我们是否达到了最大值。如果i变量不大于或等于max变量,我们继续向左并输出i变量然后将i加1 并继续回到循环的开始。如果i变量实际上大于或等于max,我们只需退出for循环并返回。
现在,让我们来看看while循环:
https://xzfile.aliyuncs.com/media/upload/picture/20190113015109-a4af9d2a-1692-1.png
https://xzfile.aliyuncs.com/media/upload/picture/20190113015139-b6537c5e-1692-1.png
在这个循环中,我们所做的就是生成0到20之间的随机数。如果数字大于10,我们退出循环并打印“I’m out!!”否则会继续循环。
在程序集中,生成A变量并最初设置为0,然后我们通过将A与十六进制数0A(表示十进制数)进行比较来初始化循环。如果A不大于或等于10,我们生成一个新的随机数然后设置为A,我们继续回到比较。如果A大于或等于10,我们打破循环,打印出“I’m out!”,然后返回。
# If 语句
接下来,我们将讨论if语句。首先,我们来看看代码:
https://xzfile.aliyuncs.com/media/upload/picture/20190113015233-d6c9dae6-1692-1.png
该函数生成0到20之间的随机数,并将所述数字存储在变量A中。如果A大于15,程序将输出出“greater than 15”。如果A小于15但大于10,程序将输出出“less than 15,greater than 10”。此模式将持续到A小于5,在这种情况下程序将输出“less than 5”。
现在,我们来看看汇编图:
https://xzfile.aliyuncs.com/media/upload/picture/20190113015310-ed068354-1692-1.png
如您所见,程序集的结构与实际代码类似。这是因为IF语句只是“If X Then Y Else Z”。 如果我们看一下顶部的第一组箭头,我们可以看到A变量和十六进制oF之间的比较,它代表十进制15.如果A大于或等于15,程序将输出“greater than 15”然后返回。否则,程序将比较A到十六进制oA,表示十进制10.此模式将继续,直到程序输出并返回。
# Switch 语句
Switch语句很像IF语句,除了在Switch语句中,一个变量或语句与一些“case”(或可能的等价)进行比较。我们来看看我们的代码:
https://xzfile.aliyuncs.com/media/upload/picture/20190113015340-fe841470-1692-1.png
在此函数中,我们将变量A设置为等于0到10之间的随机数。然后,我们使用Switch语句将A与多个case进行比较。如果A等于任何可能的情况,将打印case编号,然后程序将退出Switch语句,函数将返回。
现在,我们来看看汇编图:
https://xzfile.aliyuncs.com/media/upload/picture/20190113015415-1354a766-1693-1.png
与IF语句不同,switch语句不遵循“If X Then Y Else Z”规则,相反,程序只是简单地将条件语句与case进行比较,并且只有在所述case是条件语句等效的情况下才执行一个case。请先看看最初的2个方框:
https://xzfile.aliyuncs.com/media/upload/picture/20190113015446-25db7162-1693-1.png
首先,程序生成随机数,并将其定为A。然后,程序通过首先将临时变量(var_D0)设为等于A来初始化switch语句,然后确保var_D0满足至少一个可能的情况。如果var_D0需要默认,程序会按照绿色箭头向下到最终返回部分(见下文)。否则,程序会启动切换跳转到等效case的部分:
https://xzfile.aliyuncs.com/media/upload/picture/20190113015521-3abeed8e-1693-1.png
在var_D0(A)等于5 的情况下,代码将跳转到上述case部分,输出“5”然后跳转到返回部分。
用户输入
在本节中,我们将使用C ++ cin函数来介绍用户输入。首先,我们来看看代码:
https://xzfile.aliyuncs.com/media/upload/picture/20190113015555-4eebf040-1693-1.png
在这个函数中,我们简单地使用C ++ cin函数将一个字符串输入到变量句子中,然后我们通过printf输出句子。
我们把它分解成汇编语句。首先,C ++ cin部分:
https://xzfile.aliyuncs.com/media/upload/picture/20190113015624-6062a5c6-1693-1.png
此代码只是初始化字符串句子,然后调用cin函数并将输入设置为句子变量。让我们看看更近一点的cin调用:
https://xzfile.aliyuncs.com/media/upload/picture/20190113015653-71806096-1693-1.png
首先,程序将句子变量的内容设置为EAX,然后将EAX推送到堆栈以用作cin函数的参数,然后调用cin函数并将其输出移动到ECX中,然后将其压入堆栈中在printf的语句:
https://xzfile.aliyuncs.com/media/upload/picture/20190113015728-86bab3a8-1693-1.png
谢谢!
希望本文能够让您对基本编程概念在汇编中的表示方式有所了解。请密切关注本系列的下一部分,逆向工程 - 第2部分(高级编程概念)!
pdf自提链接:https://pan.baidu.com/s/1X_63LQsqzkYsmcf1Bvs34w密码:b4b3 默小白 发表于 2019-1-23 16:59
链接:https://pan.baidu.com/s/1X_63LQsqzkYsmcf1Bvs34w密码:b4b3
谢谢收下了 xxkz 发表于 2019-1-23 16:36
汇总一个PDF可好
链接:https://pan.baidu.com/s/1X_63LQsqzkYsmcf1Bvs34w密码:b4b3 谢谢楼主分享!! 真的好深奥呀。 好深奥呀。 汇总一个PDF可好 讲的不错 总算有基础开始的东西了
感谢感谢 谢谢楼主,了解c++之后再慢慢研究一下 谢谢楼主分享