C语言反汇编选择语句(二)之switch与shellcode

1、代码示例:

首先附上一段switch示例代码:
代码简要:输入一个数C,然后switch判断符合哪种case,根据相应的case对 t 进行赋值,最后输出 t 的值。

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语句,然后执行后面的第一句。
在这里插入图片描述

3、汇编实现:
(1)、总体代码:
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, 10default语句开始
		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()函数时,首先将会有一堆保护栈代码的机制,就有

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 ,那么进行指定对应的跳转。

//		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模块之后。

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中需要用到的这里注释与否不影响运行。
在这里插入图片描述

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后面的不知名空间,然后造成程序报错。**如下:
在这里插入图片描述
我们可以发现造成错误的原因是因为我们没有将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的栈空间。
在这里插入图片描述
执行完 ret 指令,我们发现 esp + 4,即之前 ret 指令 pop 掉了sellcode 的首地址,此时 esp 指向的是后面的空间地址。
在这里插入图片描述
因此,我们可以在执行shellcode之前将需要的 New_eip(哈哈,自定义的名字) push 在内存单元中,在shellcode 的结尾代码加上 ret 指令,那么 pop eip 之后,eip 将会等于上图中第二个框框中的代码New_eip。如下图,因为我们接下来是让程序执行printf的指令,因此我们需要观察 New_eip与printf相差的值X,然后 " add New_eip, x " 和 ” push New_eip “。
在这里插入图片描述
实践如下:
首先获取 eip 的值。
在这里插入图片描述
然后将 eip 变成我们需要的值(即 printf 的 eip 值),然后将其保存。如下,我们可以很好的发现eip 距离相差的值为 2。因此add 2后,便可以直接push ebx了。
在这里插入图片描述

6、最终shellcdoe代码

如下,为代码实现部分,
在这里插入图片描述
在这里插入图片描述
最后,附上带有shellcode的源码嘛:

// 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;
}



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值