TAMUctf 2021 — Write-up ข้อ Shellcode Golf 2

Datafarm
4 min readMay 12, 2021

เมื่อไม่กี่สัปดาห์ก่อนหน้านี้แอดได้ลองเล่น TAMUctf 2021 มา https://ctftime.org/event/1320 ถึงจะเล่นได้ไม่กี่ข้อก็ถือว่าได้เล่นอะนะ 5555 บวกกับไม่รู้จะเขียนบทความเกี่ยวกับอะไรดี ก็เลยจะมาเขียน Write up ข้อ Shellcode Golf 2 มาให้อ่านกัน ซึ่งข้อนี้อยู่ในหมวด pwn เป็นข้อที่ไม่ยากมาก โจทย์ให้ binary มา เป้าหมาย คือ ต้อง exploit server ผ่าน binary ตัวนี้เพื่อ RCE แล้วเอา flag ไปตอบเอาคะแนน

เริ่มมาแอดเอาไป decompile แต่จริง ๆ ข้อนี้ใช้ objdump ดูเอาก็ได้นะ มันไม่ได้ซับซ้อน แต่เพื่อความง่ายในการเขียนบทความ ขออธิบายจากการ decompile เอาละกันนะ

void main(void){code *__s;// map memory ขึ้นมาใหม่และ set permission เป็น 777 (RWX)__s = (code *)mmap((void *)0x1337,7,0,0x22,-1,0);mprotect(__s,7,7);// รับ user input 7 bytes ไปที่ 0x1337fgets((char *)__s,7,stdin);// call 0x1337 -> execute โค้ดที่ address 0x1337(*__s)();return;}

แอดไม่ได้เปลี่ยนชื่อตัวแปรเลยนะครับเพราะว่าขก. 5555555 หลอก ๆ แอดเห็นว่าโค้ดมันอ่านออกอยู่แล้วเลยไม่ได้เปลี่ยนไรเลย ทีนี้การทำงานคร่าว ๆ ดูจากโค้ดคือโปรแกรมจะ map memory ขึ้นมาและกำหนด permission ของ memory นั้นให้สามารถ read, write, execute ได้ ต่อมาก็รอรับ user input 6 bytes (รับ 7 แหละแต่สุดท้ายแล้วจะเหลือ 6 เพราะตัวสุดท้ายจะกลายเป็น null bytes ‘\0’) ไปเก็บไว้ที่ __s สุดท้ายก็เรียกฟังก์ชัน __s() นั่นเอง

จากตรงนี้ก็จะรู้เลยว่าต้องทำยังไง ตัว binary เปิด NX bit ก็จริงแต่ memory ที่ map ขึ้นมาใหม่นั้นมี permission ที่สามารถ execute ได้ครับ และ fgets() ก็รับ user input ไปเก็บไว้ที่ __s อยู่แล้ว วิธี exploit ก็แค่ใส่ shellcode ที่ไปเรียก shell ให้เราได้ไปที่ __s สุดท้าย โปรแกรมก็จะไป execute shellcode ให้เราปุ๊ป ก็ได้ RCE ปั๊ปเลย ทีนี้ไปหา shellcode 6 bytes กัน เปิด http://shell-storm.org/shellcode/ นี่เว็บประจำของแอดเลย 55555 พิมพ์หาเลย 6 bytes

แอดไม่เคยเจอครับ อย่างต่ำ ๆ shellcode ที่เป็น execve(/bin/bash) ก็ 27 bytes ละนะ จบเลยใช้สูตร Control C + Control V ไม่ได้ 5555555555555 งั้นเราต้องมาทำ shellcode เองละล่ะ

โจทย์นี้ความยากง่ายคือ ขนาดของ shellcode นี่แหละครับ แอดคิดว่าปกติ shellcode ขนาดยิ่งเล็กก็ยิ่งดีนะ แต่นี่มันจำกัดไว้แค่ 6 bytes เลย ซึ่งถ้าโจทย์มาแนวนี้ถ้าคนรู้เรื่อง binary exploit อยู่แล้ว จะรู้เลยว่าให้ไป debug ดูเลยว่าตอนที่มันเรียก __s() อ่ะ ค่าต่าง ๆ ใน stack หรือ registers เนี่ยมันถูกเซ็ทไว้เป็นค่าอะไรบ้างและแทนที่ต้องมานั่ง set ค่าใน registers เองทั้งหมดก็ใช้ประโยชน์จากตรงนั้นแทน ดังนั้นในการ exploit แอดจะใส่ shellcode ให้โปรแกรมไปเรียก syscall ซักตัวที่สามารถจะทำให้แอดได้ RCE

Syscall

Syscall เป็นเหมือน API / Interface ที่เอาไว้ใช้สำหรับ Process ที่กำลังทำงานใน user mode สามารถเรียกใช้คำสั่ง Low Level ระดับ OS ได้ ซึ่งตอนที่ execute syscall นี้จะอยู่ใน kernel mode พอเสร็จแล้วก็จะกลับมา user mode ซึ่งแต่ละ syscall ก็จะมีหมายเลขกำกับเอาไว้ลองเข้าไปดูได้ที่ https://filippo.io/linux-syscall-table/ ซึ่งเมื่อมีการเรียก syscall แล้ว Process จะเข้าไปดูที่ rax ว่าเป็นหมายเลขอะไร เช่น ถ้า rax เป็น 0 ก็จะเรียกใช้ read นั่นเอง ส่วน parameters ต่าง ๆ ก็จะถูกส่งผ่าน rdi, rsi, rdx ครับ

มา debug กันเลยดีกว่า

แอดใช้ gdb ในการ debug จัดแจง set breakpoint ให้เรียบร้อย โดยแอด set breakpoint ไว้ที่ call rdx ครับ จากนั้นก็ run เลยแอดใส่ input เป็น AAAAAAA โปรแกรมจะมาหยุดที่ call rdx แอด si ไป 1 ที จากนั้นก็มาดูค่าใน registers กัน

จะเห็นว่าตอนนี้ registers ต่าง ๆ เป็นแบบนี้

rax = 0rdx = 0x10000 -> AAAAAA ตรงน่าจะเป็น memory ที่ถูก map ขึ้นมา เพราะ input เราอยู่ที่ address นี้rsi = 0x41414141 -> AAAA input เราเองrdi = address บางอย่างไม่รู้ว่ามันคืออะไร ลืมเช็ค

จากรูปมี registers แบบนี้เลยตอนที่ call rdx ถ้าใครอยากลองหาวิธีที่ทำ shellcode เพื่อ RCE ก็ลองได้เลยครับ แล้วเดี๋ยวมาดูวิธีของแอดกัน

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

จากที่ลองไปลองมา แอดใช้วิธีนี้ครับ แอดจะใช้ shellcode เพื่อเรียก read() เพื่อให้โปรแกรม read shellcode อีกอันที่จะไปเรียก execve(“/bin/sh”,0,0) จาก stdin อีกรอบนึงเข้าไปที่ 0x1000 นั่นแหละเพราะมัน RWX ได้ สรุปง่าย ๆ แบบนี้ครับ

  1. ใส่ read() shellcode ไปก่อน 6 bytes กำหนดขนาดที่จะรับเยอะ ๆ ไว้
  2. โปรแกรมรัน read() shellcode รอรับ input จาก stdin อีกรอบ
  3. แอดส่ง execve() shellcode อีกอันนึงเข้าไป ซึ่งอันนี้ขนาดจะเยอะเท่าไหร่ก็ได้แล้วครับ
  4. โปรแกรมรัน execve() shellcode เรียก /bin/sh เพื่อ spawn shell

ได้วิธีแล้ว มา gen shellcode กัน แอดใช้ https://defuse.ca/ ในการ convert จาก assembly เป็น code ให้

จาก shellcode ด้านบนพอโปรแกรมมา call ที่ 0x1000 ก็จะมี instructions หน้าตาแบบนี้

พอ execute code เสร็จ registers ก็จะกลายเป็นแบบนี้ครับ

ที้นี้พอ execute syscall ปุ๊ป โปรแกรมก็จะมาเรียก read(0, 0x10000, 0x10000) ครับ เท่ากับว่าโปรแกรมจะ read 2 รอบ

ลองมา exploit กัน แอดใช้ strace ในการ trace system call แบบปกติของโปรแกรม

จะเห็นว่าโปรแกรม read() 1 รอบแล้วจบเลย ทีนี้มาลองใช้ shellcode แอดบ้าง

จะเห็นว่าพอใช้ shellcode แอดแล้วโปรแกรมมันจะ read() 2 รอบตามที่แอดบอกไปเลยครับ ทีนี้มาลอง RCE กัน แอดไปหา shellcode สำหรับ spawn shell มาจากที่เดิมครับที่ shell-storm แอดเลือกอันนี้มา http://shell-storm.org/shellcode/files/shellcode-806.php ก็จัด exploit ไปเลยครับ

เท่านี้ก็จะได้ shell แล้วครับ ทีนี้ก็เอาวิธีนี้ไปยิงเครื่อง server ของโจทย์ข้อนี้ ก็จะได้ RCE แล้วก็เอา flag ไปตอบได้ละครับ แต่อันนี้แอดไม่มีรูปตอนยิง server นะ เพราะตอนแรกก็ไม่ได้กะจะเขียนเรื่องนี้เลยไม่ได้แคปไว้

สุดท้ายนี้ก็จะบอกว่า

— โจทย์ข้อนี้ไม่ได้ยากมากครับ เพราะโจทย์ไม่ได้ซับซ้อนเลยไม่ต้องนั่งหาช่องโหว่ด้วย ส่วนเรื่อง Shellcode, syscall ถ้าใครศึกษาพวก binary exploit ก็น่าจะเข้าใจกันดีอยู่แล้ว มาเจอโจทย์แบบนี้ยังไงก็ทำได้แน่นอน

— มันมีข้อก่อนหน้านี้ด้วย ชื่อ shellcode golf แต่ว่าแอดไม่ได้เอามาเขียนนะ เพราะแอดก็ทำแบบเดียวกับข้อนี้เลยครับ

— ถ้ามีตรงไหนที่แอดอธิบายไปผิดก็บอกกันได้นะ แอดจะได้แก้ให้ถูกต้องครับ

--

--

No responses yet