|
楼主 |
发表于 2020-2-20 22:33:33
|
显示全部楼层
C语言反汇编(二)之switch与shellcode
本帖最后由 Xor0ne 于 2020-2-20 22:36 编辑
文章目录
1、代码示例:
2、反汇编:
3、汇编实现:
(1)、总体代码:
(2)、知识前提:
(3)、代码解析:
4、 转变为shellcode:
5、shellcode地址的调转
6、最终shellcdoe代码
1、代码示例:
首先附上一段switch示例代码:
代码简要:输入一个数C,然后switch判断符合哪种case,根据相应的case对 t 进行赋值,最后输出 t 的值。
- ```c
- int main(int argc, char* argv[])
- {
- printf("Hello World!\n");
- int c,t;
- scanf("%d",&c);
- switch(c)
- {
- case 0 : t = 0;break;
- case 1 : t = 1;
- case 2 : t = 2;break;
- default: t = 10;
- }
- printf("%d\n",t);
- return 0;
- }
- ```
复制代码
#### 2、反汇编:
如下图,可以发现switch与 if 选择语句有点类似。
**参数赋值**: 首先将swtich中的参数 C (在内存单元【ebp - 4】)放入 寄存器 ecx 中,然后再将 ecx 赋值给内存单元【ebp - 0Ch】(这个单元是之后需要进行比较的)。
**条件判断:** 然后用cmp指令依次将内存单元【ebp - 0Ch】与case参数:0,1,2,如果满足 je (相等),那么 je 便会根据case参数的满足情况,跳转到相应的case代码中。
**break的作用:** 由于汇编是顺序执行的,如果case 中没有break,那么下一个case也将会被顺位执行,例如:case 1 中不含有break,因此当 C = 1 时,先执行 t = 1,之后将顺位执行 case 2,即 t = 2,然后break跳出swtich语句。
**break执行原理:** jmp + 地址,直接跳转到指定地址。在这里我们只刚好跳出switch语句,然后执行后面的第一句。
![在这里插入图片描述](?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzkwMTAzOA==,size_16,color_FFFFFF,t_70)
#### 3、汇编实现:
##### (1)、总体代码:
- ```c
- int main(int argc, char* argv[])
- {
- printf("Hello World!\n");
- int c,t;
- scanf("%d",&c);
- _asm{
- // add ebx,2 ;shellcode的定址
- // push ebx
- mov eax, [ebp-4]
- cmp eax, 0
- je L0
- cmp eax, 1
- je L1
- cmp eax, 2
- je L2
- mov eax, 10 ;default语句开始
- mov [ebp-8], eax
- jmp L
-
- L0: mov eax, 0
- mov [ebp-8], eax
- jmp L
- L1: mov eax, 1
- mov [ebp-8], eax
- //jmp L 因为case 1不含有break语句,因此去掉jmp
- L2: mov eax, 2
- mov [ebp-8], eax
- jmp L
- L: nop
- //ret
- }
- printf("%d\n",t);
- return 0;
- }
- ```
复制代码
##### (2)、知识前提:
(1)、c 和 t 的位置:在进行main()函数时,首先将会有一堆保护栈代码的机制,就有
- ```c
- push ebp
- mov ebp, esp
- ```
复制代码
ebp是此时栈空间的基址,当我们定义 " int c, t ; " 时,计算机将参数从左至右压入栈内存空间中,因此 C存放在【ebp - 4】; t 存放在 【ebp - 8】当中。
##### (3)、代码解析:
(1)、实现swtich功能:
如下,在此我们仿照反汇编代码,将参数C的值(存放在【ebp - 4 】)赋值给eax,然后将case情况依次和eax进行比较,如果满足 je ,那么进行指定对应的跳转。
- ```c
- // add ebx,2 ;这两句在shellcode执行时会用上
- // push ebx
-
- mov eax, [ebp-4]
- cmp eax, 0
- je L0
- ```
复制代码
例如,当 C = 1(case中不含有break),不断 cmp 之后,将会 je 跳转到 L1 模块,由于不含break,因此将会执行 L2,因此我们将 L2放在 L1模块之后。
- ```c
- L1: mov eax, 1
- mov [ebp-8], eax
- //jmp L 因为case 1不含有break语句,因此去掉jmp
- L2: mov eax, 2
- ```
复制代码
(2)、case-break功能:我们将每个case都放入一个对应的代码块中,当switch满足条件时,便 je + 相对应的代码块。
由于函数是从上到下执行的,因此,我们将default模块设置在switch代码的最下面。
**注意:** L代码块最后面有一个ret语句,这是下面shellcode中需要用到的这里注释与否不影响运行。
![在这里插入图片描述](?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzkwMTAzOA==,size_16,color_FFFFFF,t_70)
#### 4、 转变为shellcode:
**拓展:** 首先简要介绍一下ret指令,
>ret指令一般与call指令联合使用类似于函数的调用。
>call的本质其实就是:
(1)、push eip (2)、jmp 寄存器
即将当前主程序中的 eip 入栈,然后 jmp 跳转到相应的子程序。
ret的本质为:
pop eip
即将之前栈中的地址出栈,从而返回原来的主程序中。
上述代码单步调试没有问题的情况下,我们将_asm汇编语言中的指令机器码提取出来,变成十六进制的格式。
利用一下代码进行执行。
![在这里插入图片描述]()
这个是之前的一个示例代码,首先我们将shellcode的首地址赋值给eax寄存器,然后将eax寄存器的值(即shellcode的首地址)push入栈,之后执行ret指令(即 pop eip ),此时 eip = shellcode的首地址 ,然后程序按照eip 开始执行 shellcode。**但是每次执行完shellcode 最后一句话时,我们发现程序并没有回到 _asm 代码块,而是会继续执行shellcode后面的不知名空间,然后造成程序报错。**如下:
![在这里插入图片描述](?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzkwMTAzOA==,size_16,color_FFFFFF,t_70)
我们可以发现造成错误的原因是因为我们没有将shellcode地址结尾进行调转,以至于它回不来了。也就是它的 eip 没有变回到原来的eip 以至于产生错误。
#### 5、shellcode地址的调转
上面我们发现错误的原因是因为 eip 的地址没有进行复。因此我们可以设想在执行shellcode 前将 eip 地址保存到某个位置,然后执行借宿后我们再将 eip 的值取出,这样我们便可以回到原来的位置了。
但是由于 eip 是一个特殊的寄存器,不能直接改变 eip 的值。但是我们可以通过 call/ret间接的改变 eip 的值。(关于 call/ret 我们前面有讲解)
(1)、首先,由于call/ret 是通过 push 和 pop 来改变eip的值,因此我们观察 eip变化 与栈空间的联系。
如下是执行完 push eax, shellcode的栈空间。
![在这里插入图片描述](?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzkwMTAzOA==,size_16,color_FFFFFF,t_70)
执行完 ret 指令,我们发现 esp + 4,即之前 ret 指令 pop 掉了sellcode 的首地址,此时 esp 指向的是后面的空间地址。
![在这里插入图片描述](?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzkwMTAzOA==,size_16,color_FFFFFF,t_70)
因此,我们可以在执行shellcode之前将需要的 New_eip(哈哈,自定义的名字) push 在内存单元中,在shellcode 的结尾代码加上 ret 指令,那么 pop eip 之后,eip 将会等于上图中第二个框框中的代码New_eip。如下图,因为我们接下来是让程序执行printf的指令,因此我们需要观察 New_eip与printf相差的值X,然后 " add New_eip, x " 和 ” push New_eip “。
![在这里插入图片描述](?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzkwMTAzOA==,size_16,color_FFFFFF,t_70)
实践如下:
首先获取 eip 的值。
![在这里插入图片描述]()
然后将 eip 变成我们需要的值(即 printf 的 eip 值),然后将其保存。如下,我们可以很好的发现eip 距离相差的值为 2。因此add 2后,便可以直接push ebx了。
![在这里插入图片描述](?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzkwMTAzOA==,size_16,color_FFFFFF,t_70)
#### 6、最终shellcdoe代码
如下,为代码实现部分,
![在这里插入图片描述]()
![在这里插入图片描述]()
最后,附上带有shellcode的源码嘛:
- ```c
- // test0219_switch.cpp : Defines the entry point for the console application.
- //
- #pragma comment(linker, "/section:.data,RWE")
- #include "stdafx.h"
- char shellcode[] = "\x83\xc3\x02\x53\x8b\x45\xfc\x83\xf8\x00\x74\x14\x83\xf8\x01\x74\x19\x83\xf8\x02\x74\x1c\xb8\x0a\x00\x00\x00\x8b\x45\xfc\x83\xf8\x00\x74\x14\x83\xf8\x01\x74\x19\x83\xf8\x02\x74\x1c\xb8\x0a\x00\x00\x00\x89\x45\xf8\xeb\x12\xb8\x01\x00\x00\x00\x89\x45\xf8\xb8\x02\x00\x00\x00\x89\x45\xf8\xeb\x00\xc3";
- int main(int argc, char* argv[])
- {
- printf("Hello World!\n");
- int c,t;
- scanf("%d",&c);
-
- _asm{
- lea eax,shellcode
- push eax
- call next
- next:pop ebx
- ret
- }
- /*** C语言代码:
- switch(c)
- {
- case 0 : t = 0;break;
- case 1 : t = 1;
- case 2 : t = 2;break;
- default: t = 10;
- }
- */
- /*
- _asm{
- add ebx,2
- push ebx
- mov eax, [ebp-4]
- cmp eax, 0
- je L0
- cmp eax, 1
- je L1
- cmp eax, 2
- je L2
- mov eax, 10
- mov [ebp-8], eax
- jmp L
-
- L0: mov eax, 0
- mov [ebp-8], eax
- jmp L
- L1: mov eax, 1
- mov [ebp-8], eax
- //jmp L
- L2: mov eax, 2
- mov [ebp-8], eax
- jmp L
- L: ret
- }
-
- */
- printf("%d\n",t);
- return 0;
- }
- ```
复制代码
|
|