Рішення 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).
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, який ми ініціалізуємо з нашим кодом shell, який буде виконуватися. Через це основний буде доданий не до сегмента __TEXT, а до сегмента __DATA, тому нам потрібно скомпілювати програму з виконуваним сегментом __DATA.
Шлакод, знайдений в основному, такий:
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 зайвих байта небажаного (знову ж таки, щоб виключити нульові байти, ми просто змістимо його назад вправо, щоб повернути його до оригіналу).
Після системного виклику ми записуємо до нашої щойно виділеної пам'яті. Причиною цього є те, що пам'ять, що виділяється за допомогою mach_vm_allocate (або цього системного виклику), є насправді сторінками VM, і вони автоматично не передаються в пам'ять. Швидше вони зарезервовані до тих пір, поки дані не будуть записані до них, а потім ці сторінки не відобразяться в пам'ять. Не була впевнена, чи відповідає вона вимогам, якщо вона лише зарезервована.
Для наступного рішення ми скористаємося тим, що наш код коду не має нульових байтів, і тому можемо перемістити його поза кодом нашої програми для зменшення розміру.
Рішення 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, за винятком того, що ми перемістили фрагмент коду, що витікає, поза програмою.
Шелкод, знайдений в основному, тепер такий:
movq 8(%rsi), %rsi
movl $42, %ecx
leaq 2(%rip), %rdi
rep movsb (%rsi), (%rdi)
Це в основному копіює код оболонки, який ми передаємо в argv, після цього коду (тому після його копіювання він запустить вставлений оболонку). На нашу користь - те, що сегмент __DATA матиме принаймні розмір сторінки, тож навіть якщо наш код не такий великий, ми все одно можемо «сміливо» писати більше. Мінус є ідеальним рішенням тут, навіть копія не потребуватиме, замість цього вона буде просто зателефонувати та виконати shellcode в argv безпосередньо. Але, на жаль, ця пам’ять не має прав на виконання. Ми могли б змінити права на цю пам’ять, однак для цього знадобиться більше коду, ніж просто копіювати його. Альтернативною стратегією було б зміна прав із зовнішньої програми (але про це пізніше).
Код оболонки, який ми передаємо 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 байт
Ідея змінити програму зовнішньо, дає нам можливе рішення про переміщення виводчика до зовнішньої програми. Якщо наша фактична програма (подання) - це лише фіктивна програма, і програма витікання виділить деяку пам’ять у нашій цільовій програмі. Зараз я не був впевнений, чи буде це підпадати під правила цього виклику, але все-таки поділитися ним.
Отже, якщо ми маємо використовувати mach_vm_allocate у зовнішній програмі з цільовим завданням для нашої викликової програми, це може означати, що нашій програмі-виклику потрібно буде лише щось, що відповідає:
main=65259;
Якщо цей shellcode - це просто короткий стрибок до себе (нескінченний стрибок / цикл), тому програма залишається відкритою, і ми можемо посилатися на неї із зовнішньої програми.
Можливе рішення 2: C (Mac OS X x86_64), 8 байт
Досить дивно, коли я дивився на вихід вальгринд, я побачив, що, принаймні, згідно з вальгрингом, руйнів протікає пам'ять. Тож ефективно кожна програма просочує деяку пам’ять. В такому випадку ми насправді можемо просто зробити програму, яка нічого не робить (просто виходить), і це насправді просочується пам'ять.
Джерело:
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