Rozwiązanie 1: C (Mac OS X x86_64), 109 bajtów
Źródło dla golf_sol1.c
main[]={142510920,2336753547,3505849471,284148040,2370322315,2314740852,1351437506,1208291319,914962059,195};
Powyższy program należy skompilować z dostępem do wykonania w segmencie __DATA.
clang golf_sol1.c -o golf_sol1 -Xlinker -segprot -Xlinker __DATA -Xlinker rwx -Xlinker rwx
Następnie, aby uruchomić program, uruchom następujące czynności:
./golf_sol1 $(ruby -e 'puts "\xf5\xff\xff\xfe\xff\xff\x44\x82\x57\x7d\xff\x7f"')
Wyniki:
Niestety Valgrind nie szuka pamięci przydzielonej z wywołań systemowych, więc nie mogę pokazać ładnie wykrytego wycieku.
Możemy jednak spojrzeć na vmmap, aby zobaczyć dużą część przydzielonej pamięci (metadane 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
Wyjaśnienie
Myślę więc, że muszę opisać, co się tutaj właściwie dzieje, zanim przejdę do ulepszonego rozwiązania.
Ta główna funkcja nadużywa deklaracji brakującego typu C (więc domyślnie int, bez marnowania znaków podczas pisania), a także jak działają symbole. Linker dba tylko o to, czy nie może znaleźć symbolu, main
do którego należy zadzwonić. Więc tutaj tworzymy główną tablicę liczb całkowitych, które inicjujemy za pomocą naszego kodu powłoki, który zostanie wykonany. Z tego powodu main nie zostanie dodany do segmentu __TEXT, ale raczej do segmentu __DATA, dlatego musimy skompilować program z wykonywalnym segmentem __DATA.
Znaleziony w main kod powłoki jest następujący:
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
Wywołuje to funkcję syscall w celu przydzielenia strony pamięci (syscall mach_vm_allocate używa wewnętrznie). RAX powinien być równy 0x100000a (mówi syscall, której funkcji chcemy), podczas gdy RDI utrzymuje cel alokacji (w naszym przypadku chcemy, aby to był mach_task_self ()), RSI powinien przechowywać adres, aby zapisać wskaźnik do nowo utworzonej pamięci (więc wskazujemy tylko sekcję na stosie), RDX zachowuje rozmiar alokacji (przekazujemy tylko RAX lub 0x100000a, aby zaoszczędzić na bajtach), R10 przechowuje flagi (wskazujemy, że może być przydzielone w dowolnym miejscu).
Teraz nie jest oczywiste, skąd RAX i RDI czerpią swoje wartości. Wiemy, że RAX musi mieć wartość 0x100000a, a RDI musi być wartością zwracaną przez mach_task_self (). Na szczęście mach_task_self () jest w rzeczywistości makrem zmiennej (mach_task_self_), która ma ten sam adres pamięci za każdym razem (jednak powinna się zmienić przy ponownym uruchomieniu). W moim szczególnym przypadku mach_task_self_ znajduje się pod adresem 0x00007fff7d578244. Aby ograniczyć instrukcje, zamiast tego przekażemy te dane z argv. Dlatego uruchamiamy program z tym wyrażeniem$(ruby -e 'puts "\xf5\xff\xff\xfe\xff\xff\x44\x82\x57\x7d\xff\x7f"')
za pierwszy argument. Łańcuch to dwie połączone wartości, w których wartość RAX (0x100000a) wynosi tylko 32 bity i zastosowano do niej dopełnienie jednego (więc nie ma bajtów zerowych; po prostu NIE mamy wartości, aby uzyskać oryginał), następna wartość to RDI (0x00007fff7d578244), który został przesunięty w lewo z 2 dodatkowymi śmieciami dodanymi na końcu (ponownie, aby wykluczyć bajty puste, po prostu przesuwamy go z powrotem w prawo, aby przywrócić go do pierwotnego).
Po syscall piszemy do naszej nowo przydzielonej pamięci. Powodem tego jest to, że pamięć przydzielana za pomocą mach_vm_allocate (lub tego syscall) to w rzeczywistości strony VM i nie są automatycznie przenoszone do pamięci. Są one raczej zarezerwowane, dopóki dane nie zostaną do nich zapisane, a następnie strony te zostaną zmapowane w pamięci. Nie byłem pewien, czy spełniłby wymagania, gdyby był zarezerwowany.
W kolejnym rozwiązaniu wykorzystamy fakt, że nasz kod powłoki nie ma zerowych bajtów, więc możemy przenieść go poza kod naszego programu, aby zmniejszyć rozmiar.
Rozwiązanie 2: C (Mac OS X x86_64), 44 bajty
Źródło dla golf_sol2.c
main[]={141986632,10937,1032669184,2,42227};
Powyższy program należy skompilować z dostępem do wykonania w segmencie __DATA.
clang golf_sol2.c -o golf_sol2 -Xlinker -segprot -Xlinker __DATA -Xlinker rwx -Xlinker rwx
Następnie, aby uruchomić program, uruchom następujące czynności:
./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"')
Wynik powinien być taki sam jak poprzednio, ponieważ dokonujemy alokacji o tym samym rozmiarze.
Wyjaśnienie
Stosuje tę samą koncepcję, co rozwiązanie 1, z tym wyjątkiem, że przenieśliśmy część naszego wycieku kodu poza program.
Kod powłoki znaleziony w main jest teraz następujący:
movq 8(%rsi), %rsi
movl $42, %ecx
leaq 2(%rip), %rdi
rep movsb (%rsi), (%rdi)
To w zasadzie kopiuje kod powłoki, który przekazujemy w argv po tym kodzie (więc po skopiowaniu go uruchomi wstawiony kod powłoki). Na naszą korzyść działa to, że segment __DATA będzie miał co najmniej rozmiar strony, więc nawet jeśli nasz kod nie jest tak duży, nadal możemy „bezpiecznie” pisać więcej. Minusem jest tutaj idealne rozwiązanie, nawet nie potrzebuje kopii, zamiast tego po prostu wywołuje i wykonuje kod powłoki bezpośrednio w argv. Ale niestety ta pamięć nie ma uprawnień do wykonywania. Moglibyśmy zmienić prawa do tej pamięci, jednak wymagałoby to więcej kodu niż zwykłe kopiowanie. Alternatywną strategią byłaby zmiana praw z programu zewnętrznego (ale o tym później).
Kod powłoki, który przekazujemy argv, jest następujący:
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
Jest to bardzo podobne do naszego poprzedniego kodu, z tą różnicą, że uwzględniamy bezpośrednio wartości EAX i RDI.
Możliwe rozwiązanie 1: C (Mac OS X x86_64), 11 bajtów
Pomysł zmodyfikowania programu zewnętrznie daje nam możliwe rozwiązanie przeniesienia przecieku do programu zewnętrznego. Tam, gdzie nasz rzeczywisty program (przesłanie) jest tylko programem zastępczym, a program nieszczelny przydzieli część pamięci w naszym programie docelowym. Teraz nie byłem pewien, czy będzie to zgodne z zasadami tego wyzwania, ale mimo to podzielam się nim.
Więc jeśli użyjemy mach_vm_allocate w zewnętrznym programie z celem ustawionym na nasz program wyzwań, może to oznaczać, że nasz program wyzwań musiałby być czymś w rodzaju:
main=65259;
Tam, gdzie ten kod powłoki to po prostu krótki skok do siebie (nieskończony skok / pętla), więc program pozostaje otwarty i możemy odwoływać się do niego z zewnętrznego programu.
Możliwe rozwiązanie 2: C (Mac OS X x86_64), 8 bajtów
Co zabawne, kiedy patrzyłem na produkcję valgrind, zobaczyłem, że przynajmniej według valgrind, słaba pamięć przecieków. Tak skutecznie każdy program przecieka trochę pamięci. Mając to na uwadze, moglibyśmy po prostu stworzyć program, który nic nie robi (po prostu wychodzi) i faktycznie wycieknie pamięć.
Źródło:
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