Решение 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
для вызова. Итак, здесь мы делаем main массив int, который мы инициализируем нашим шелл-кодом, который будет выполнен. Из-за этого main будет добавляться не к сегменту __TEXT, а к сегменту __DATA, поэтому нам нужно скомпилировать программу с исполняемым сегментом __DATA.
Обнаружен шелл-код в 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 (сообщает системному вызову, какую функцию мы хотим), в то время как 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 (или этого системного вызова), на самом деле является страницами виртуальной машины и не выгружается в память автоматически. Скорее они зарезервированы до тех пор, пока им не будут записаны данные, а затем эти страницы будут отображены в памяти. Не был уверен, что он будет соответствовать требованиям, если он будет только зарезервирован.
Для следующего решения мы воспользуемся преимуществом того факта, что наш шелл-код не имеет нулевых байтов и поэтому может переместить его за пределы кода нашей программы, чтобы уменьшить размер.
Решение 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, за исключением того, что мы переместили часть нашего утекшего кода за пределы программы.
Найденный в main шелл-код теперь выглядит следующим образом:
movq 8(%rsi), %rsi
movl $42, %ecx
leaq 2(%rip), %rdi
rep movsb (%rsi), (%rdi)
Это в основном копирует шелл-код, который мы передаем в argv, чтобы следовать за этим кодом (поэтому после того, как он скопировал его, он запустит вставленный шелл-код). Что работает в нашу пользу, так это то, что сегмент __DATA будет иметь размер как минимум страницы, поэтому даже если наш код не такой большой, мы все равно можем «безопасно» писать больше. Недостатком является идеальное решение здесь, даже не нужно копировать, вместо этого он просто вызовет и выполнит шелл-код напрямую в 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;
Где этот шеллкод - просто короткий переход к самому себе (бесконечный переход / цикл), так что программа остается открытой, и мы можем ссылаться на нее из внешней программы.
Возможное решение 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