โซลูชันที่ 1: C (Mac OS X x86_64), 109 ไบต์
แหล่งที่มาสำหรับ golf_sol1.c
main[]={142510920,2336753547,3505849471,284148040,2370322315,2314740852,1351437506,1208291319,914962059,195};
โปรแกรมข้างต้นจะต้องรวบรวมการเข้าถึงการดำเนินการในส่วน __DATA
clang golf_sol1.c -o golf_sol1 -Xlinker -segprot -Xlinker __DATA -Xlinker rwx -Xlinker rwx
จากนั้นเพื่อรันโปรแกรมให้เรียกใช้สิ่งต่อไปนี้:
./golf_sol1 $(ruby -e 'puts "\xf5\xff\xff\xfe\xff\xff\x44\x82\x57\x7d\xff\x7f"')
ผล:
น่าเสียดายที่ Valgrind ไม่ได้มองหาหน่วยความจำที่จัดสรรจากการเรียกใช้ระบบดังนั้นฉันจึงไม่สามารถตรวจพบรอยรั่วที่ตรวจพบได้ดี
อย่างไรก็ตามเราสามารถดู vmmap เพื่อดูหน่วยความจำที่จัดสรรขนาดใหญ่ (MALLOC metadata)
VIRTUAL REGION
REGION TYPE SIZE COUNT (non-coalesced)
=========== ======= =======
Kernel Alloc Once 4K 2
MALLOC guard page 16K 4
MALLOC metadata 16.2M 7
MALLOC_SMALL 8192K 2 see MALLOC ZONE table below
MALLOC_TINY 1024K 2 see MALLOC ZONE table below
STACK GUARD 56.0M 2
Stack 8192K 3
VM_ALLOCATE (reserved) 520K 3 reserved VM address space (unallocated)
__DATA 684K 42
__LINKEDIT 70.8M 4
__TEXT 5960K 44
shared memory 8K 3
=========== ======= =======
TOTAL 167.0M 106
TOTAL, minus reserved VM space 166.5M 106
คำอธิบาย
ดังนั้นฉันคิดว่าฉันต้องอธิบายสิ่งที่เกิดขึ้นจริงที่นี่ก่อนที่จะไปสู่โซลูชันที่ได้รับการปรับปรุง
ฟังก์ชั่นหลักนี้ใช้ในการประกาศประเภทที่หายไปของ C (ดังนั้นจึงมีค่าเริ่มต้นเป็น int โดยที่เราไม่จำเป็นต้องเขียนอักขระเสีย) รวมถึงวิธีการทำงานของสัญลักษณ์ ตัวเชื่อมโยงนั้นสนใจว่าจะหาสัญลักษณ์main
ที่โทรไปหาได้หรือไม่ ดังนั้นที่นี่เรากำลังสร้างอาร์เรย์หลักของ int ซึ่งเรากำลังเริ่มต้นด้วย shellcode ของเราที่จะถูกดำเนินการ ด้วยเหตุนี้หลักจะไม่ถูกเพิ่มลงในเซกเมนต์ __TEXT แต่เป็นเซกเมนต์ __DATA เหตุผลที่เราต้องคอมไพล์โปรแกรมด้วยเซ็กเมนต์ __DATA ที่สามารถเรียกใช้งานได้
shellcode ที่พบใน main มีดังต่อไปนี้:
movq 8(%rsi), %rdi
movl (%rdi), %eax
movq 4(%rdi), %rdi
notl %eax
shrq $16, %rdi
movl (%rdi), %edi
leaq -0x8(%rsp), %rsi
movl %eax, %edx
leaq -9(%rax), %r10
syscall
movq (%rsi), %rsi
movl %esi, (%rsi)
ret
สิ่งนี้กำลังทำอยู่คือเรียกใช้ฟังก์ชัน syscall เพื่อจัดสรรหน้าหน่วยความจำ (syscall mach_vm_allocate ใช้ภายใน) RAX ควรเท่ากับ 0x100000a (บอก syscall ว่าเราต้องการฟังก์ชั่นใด) ในขณะที่ RDI ถือเป้าหมายสำหรับการจัดสรร (ในกรณีของเราเราต้องการให้เป็น mach_task_self ()) RSI ควรเก็บที่อยู่เพื่อเขียนตัวชี้ไปยังหน่วยความจำที่สร้างขึ้นใหม่ (ดังนั้นเราแค่ชี้ไปที่ส่วนบนกองซ้อน) RDX ถือขนาดของการจัดสรร (เราเพิ่งผ่านใน RAX หรือ 0x100000a เพื่อบันทึกเป็นไบต์), R10 ถือธง (เราสามารถระบุได้ จัดสรรได้ทุกที่)
ตอนนี้ก็ไม่ชัดเจนว่า RAX และ RDI ได้รับค่าของพวกเขาจากที่ใด เรารู้ว่า RAX ต้องเป็น 0x100000a และ RDI ต้องเป็นค่า mach_task_self () ส่งคืน โชคดีที่ mach_task_self () เป็นแมโครจริงสำหรับตัวแปร (mach_task_self_) ซึ่งเป็นที่อยู่หน่วยความจำเดียวกันทุกครั้ง (ควรเปลี่ยนเมื่อทำการรีบูท) ในอินสแตนซ์เฉพาะของฉัน mach_task_self_ นั้นเกิดขึ้นที่ 0x00007fff7d578244 เพื่อลดคำแนะนำเราจะส่งผ่านข้อมูลนี้จาก argv แทน นี่คือเหตุผลที่เรารันโปรแกรมด้วยนิพจน์นี้$(ruby -e 'puts "\xf5\xff\xff\xfe\xff\xff\x44\x82\x57\x7d\xff\x7f"')
สำหรับอาร์กิวเมนต์แรก สตริงคือค่าสองค่าที่รวมกันโดยที่ค่า RAX (0x100000a) มีเพียง 32 บิตและมีส่วนเสริมหนึ่งส่วนที่ใช้กับมัน (ดังนั้นจึงไม่มีค่าเป็นไบต์ว่างเราไม่ได้ค่าที่จะได้รับดั้งเดิม) ค่าถัดไปคือ RDI (0x00007fff7d578244) ซึ่งถูกเลื่อนไปทางซ้ายโดยเพิ่มขยะ 2 ไบต์เพิ่มไว้ที่ส่วนท้าย (อีกครั้งเพื่อไม่รวมไบต์ว่างเราเพียงแค่เลื่อนกลับไปทางขวาเพื่อให้กลับเป็นต้นฉบับ)
หลังจาก syscall เรากำลังเขียนไปยังหน่วยความจำที่จัดสรรใหม่ของเรา เหตุผลนี้เป็นเพราะหน่วยความจำที่จัดสรรโดยใช้ mach_vm_allocate (หรือ syscall นี้) เป็นหน้า VM จริงและไม่ได้ถูกเพจโดยอัตโนมัติในหน่วยความจำ ค่อนข้างจะถูกสงวนไว้จนกว่าข้อมูลจะถูกเขียนถึงพวกเขาแล้วหน้าเหล่านั้นจะถูกแมปลงในหน่วยความจำ ไม่แน่ใจว่าจะเป็นไปตามข้อกำหนดหรือไม่หากมีการจองไว้เท่านั้น
สำหรับวิธีแก้ไขปัญหาถัดไปเราจะใช้ประโยชน์จากความจริงที่ว่า shellcode ของเราไม่มีไบต์ว่างดังนั้นสามารถย้ายออกนอกรหัสโปรแกรมของเราเพื่อลดขนาด
โซลูชันที่ 2: C (Mac OS X x86_64), 44 ไบต์
แหล่งที่มาสำหรับ golf_sol2.c
main[]={141986632,10937,1032669184,2,42227};
โปรแกรมข้างต้นจะต้องรวบรวมการเข้าถึงการดำเนินการในส่วน __DATA
clang golf_sol2.c -o golf_sol2 -Xlinker -segprot -Xlinker __DATA -Xlinker rwx -Xlinker rwx
จากนั้นเพื่อรันโปรแกรมให้เรียกใช้สิ่งต่อไปนี้:
./golf_sol2 $(ruby -e 'puts "\xb8\xf5\xff\xff\xfe\xf7\xd0\x48\xbf\xff\xff\x44\x82\x57\x7d\xff\x7f\x48\xc1\xef\x10\x8b\x3f\x48\x8d\x74\x24\xf8\x89\xc2\x4c\x8d\x50\xf7\x0f\x05\x48\x8b\x36\x89\x36\xc3"')
ผลลัพธ์ควรเป็นเหมือนเดิมเนื่องจากเราทำการจัดสรรขนาดเดียวกัน
คำอธิบาย
ปฏิบัติตามแนวคิดเดียวกันกับโซลูชัน 1 โดยมีข้อยกเว้นว่าเราได้ย้ายโค้ดการรั่วไหลออกไปนอกโปรแกรม
shellcode ที่พบใน main ตอนนี้มีดังต่อไปนี้:
movq 8(%rsi), %rsi
movl $42, %ecx
leaq 2(%rip), %rdi
rep movsb (%rsi), (%rdi)
สิ่งนี้จะเป็นการคัดลอก shellcode ที่เราส่งผ่านใน argv ให้อยู่หลังรหัสนี้ (ดังนั้นหลังจากที่มันได้ทำการคัดลอกแล้วมันจะรัน shellcode ที่แทรกไว้) สิ่งที่เราโปรดปรานก็คือกลุ่ม __DATA จะมีขนาดหน้าอย่างน้อยที่สุดดังนั้นแม้ว่ารหัสของเราจะไม่ใหญ่เราก็สามารถเขียนได้อย่างปลอดภัยมากขึ้น ข้อเสียคือทางออกที่ดีที่สุดที่นี่ไม่จำเป็นต้องมีการคัดลอก แต่จะเรียกและดำเนินการ shellcode ใน argv โดยตรง แต่น่าเสียดายที่หน่วยความจำนี้ไม่มีสิทธิ์ดำเนินการ เราสามารถเปลี่ยนสิทธิ์ของหน่วยความจำนี้ได้อย่างไรก็ตามจะต้องใช้รหัสมากกว่าการคัดลอก กลยุทธ์ทางเลือกคือการเปลี่ยนสิทธิ์จากโปรแกรมภายนอก (แต่จะเพิ่มเติมในภายหลัง)
shellcode ที่เราส่งไปยัง argv มีดังต่อไปนี้:
movl $0xfefffff5, %eax
notl %eax
movq $0x7fff7d578244ffff, %rdi
shrq $16, %rdi
movl (%rdi), %edi
leaq -0x8(%rsp), %rsi
movl %eax, %edx
leaq -9(%rax), %r10
syscall
movq (%rsi), %rsi
movl %esi, (%rsi)
ret
นี่เหมือนกับโค้ดก่อนหน้าของเรามีความแตกต่างเพียงเพราะเรารวมถึงค่าสำหรับ EAX และ RDI โดยตรง
โซลูชันที่เป็นไปได้ 1: C (Mac OS X x86_64), 11 ไบต์
แนวคิดของการแก้ไขโปรแกรมจากภายนอกทำให้เรามีทางออกที่เป็นไปได้ในการย้าย leaker ไปยังโปรแกรมภายนอก ตำแหน่งที่โปรแกรมจริงของเรา (การส่ง) เป็นเพียงโปรแกรมจำลองและโปรแกรม leaker จะจัดสรรหน่วยความจำบางส่วนในโปรแกรมเป้าหมายของเรา ตอนนี้ฉันไม่แน่ใจว่าสิ่งนี้จะอยู่ภายในกฎสำหรับความท้าทายนี้ แต่การแบ่งปันนั้นยังคงอยู่
ดังนั้นหากเราต้องใช้ mach_vm_allocate ในโปรแกรมภายนอกโดยมีเป้าหมายที่ตั้งไว้ในโปรแกรมการท้าทายของเรานั่นอาจหมายถึงโปรแกรมการท้าทายของเราจะต้องมีอะไรบางอย่างในสายของ:
main=65259;
โดยที่ shellcode นั้นเป็นเพียงการกระโดดสั้น ๆ เพื่อตัวเอง (infinite jump / loop) ดังนั้นโปรแกรมยังคงเปิดอยู่และเราสามารถอ้างอิงได้จากโปรแกรมภายนอก
โซลูชันที่เป็นไปได้ 2: C (Mac OS X x86_64), 8 ไบต์
สนุกพอเมื่อฉันดูที่เอาต์พุต valgrind ฉันเห็นว่าอย่างน้อยตาม valgrind หน่วยความจำรั่ว ดังนั้นทุกโปรแกรมจึงมีหน่วยความจำรั่ว ด้วยกรณีนี้เราสามารถสร้างโปรแกรมที่ไม่ทำอะไรเลย (แค่ออก) และนั่นจะทำให้หน่วยความจำรั่ว
ที่มา:
main(){}
==55263== LEAK SUMMARY:
==55263== definitely lost: 696 bytes in 17 blocks
==55263== indirectly lost: 17,722 bytes in 128 blocks
==55263== possibly lost: 0 bytes in 0 blocks
==55263== still reachable: 0 bytes in 0 blocks
==55263== suppressed: 16,316 bytes in 272 blocks