W tym poście chciałbym opisać rozwiązanie zadania Forky z działu Reverse Engineering picoCTF.
Do zadania dołączony jest skompilowany kod. W celu dekompilacji można posłużyć się narzędziem jak cutter lub decompiler online.
Poniżej fragment decompilacji w assemblerze z programu cutter:
- doNothing(void *arg_4h);
- ; var int32_t var_8h @ stack - 0x8
- ; arg void *arg_4h @ stack + 0x4
- 0x0000054d push ebp
- 0x0000054e mov ebp, esp
- 0x00000550 sub esp, 0x10
- 0x00000553 call __x86.get_pc_thunk.ax ; sym.__x86.get_pc_thunk.ax
- 0x00000558 add eax, 0x1a7c
- 0x0000055d mov eax, dword [arg_4h]
- 0x00000560 mov dword [var_8h], eax
- 0x00000563 nop
- 0x00000564 leave
- 0x00000565 ret
- int main(int argc, char **argv, char **envp);
- ; var int prot @ stack - 0x1c
- ; var int flags @ stack - 0x18
- ; var void *var_14h @ stack - 0x14
- ; var int32_t var_10h @ stack - 0x10
- ; arg int argc @ stack + 0x4
- 0x00000566 lea ecx, [argc]
- 0x0000056a and esp, 0xfffffff0
- 0x0000056d push dword [ecx - 4]
- 0x00000570 push ebp
- 0x00000571 mov ebp, esp
- 0x00000573 push ebx
- 0x00000574 push ecx
- 0x00000575 sub esp, 0x10
- 0x00000578 call __x86.get_pc_thunk.bx ; sym.__x86.get_pc_thunk.bx
- 0x0000057d add ebx, 0x1a57
- 0x00000583 mov dword [prot], 3
- 0x0000058a mov dword [flags], 0x21 ; '!'
- 0x00000591 sub esp, 8
- 0x00000594 push 0 ; size_t offset
- 0x00000596 push 0xffffffffffffffff ; int fd
- 0x00000598 push dword [flags] ; int flags
- 0x0000059b push dword [prot] ; int prot
- 0x0000059e push 4 ; size_t length
- 0x000005a0 push 0 ; void *addr
- 0x000005a2 call mmap ; sym.imp.mmap ; void *mmap(void *addr, size_t length, int prot, int flags, int fd, size_t offset)
- 0x000005a7 add esp, 0x20
- 0x000005aa mov dword [var_14h], eax
- 0x000005ad mov eax, dword [var_14h]
- 0x000005b0 mov dword [eax], 0x3b9aca00
- 0x000005b6 call fork ; sym.imp.fork
- 0x000005bb call fork ; sym.imp.fork
- 0x000005c0 call fork ; sym.imp.fork
- 0x000005c5 call fork ; sym.imp.fork
- 0x000005ca mov eax, dword [var_14h]
- 0x000005cd mov eax, dword [eax]
- 0x000005cf lea edx, [eax + 0x499602d2]
- 0x000005d5 mov eax, dword [var_14h]
- 0x000005d8 mov dword [eax], edx
- 0x000005da mov eax, dword [var_14h]
- 0x000005dd mov eax, dword [eax]
- 0x000005df sub esp, 0xc
- 0x000005e2 push eax ; int32_t arg_4h
- 0x000005e3 call doNothing ; sym.doNothing
- 0x000005e8 add esp, 0x10
- 0x000005eb mov eax, 0
- 0x000005f0 lea esp, [var_10h]
- 0x000005f3 pop ecx
- 0x000005f4 pop ebx
- 0x000005f5 pop ebp
- 0x000005f6 lea esp, [ecx - 4]
- 0x000005f9 ret
W assemblerze dosyć ciężko to przeanalizować. Natomiast widać, że czterokrotnie następuje wywołanie funckcji fork (stworzenie nowych procesów). Po stworzeniu tych procesów przeprowadzane są operacje na zmiennych i dopiero na samym końcu wywoływana jest funkcja doNothing.
Zdekompilowany fragment kodu narzędziem online. W trochę bardziej czytelnej formie:
- undefined4 main(undefined1 param_1)
- {
- int *piVar1;
- piVar1 = (int *)mmap((void *)0x0,4,3,0x21,-1,0);
- *piVar1 = 1000000000;
- fork();
- fork();
- fork();
- fork();
- *piVar1 = *piVar1 + 0x499602d2;
- doNothing(*piVar1);
- return 0;
- }
- void doNothing(undefined4 param_1)
- {
- __x86.get_pc_thunk.ax();
- return;
- }
W zadaniu należy znaleść wartość wprowadzoną do zmiennej doNothing(). Teoretycznie powinno być 1000000000 + 0x499602D2. Natomiast pojawia się cztery wywołania funkcji fork(). Funkcja fork powoduje powstanie kolejnego egzemplarza procesu. Zmienia się tylko id. W związku z tym, że fork jest wywołane czterokrotnie to końcowa ilość procesów wynosi 2^4 = 16.
Działa to tak:
Na początku, po uruchomieniu programu, istnieje jeden proces. Pierwsze wywołanie
fork()
powoduje utworzenie nowego procesu, co daje łącznie 2 procesy. Drugie wywołanie fork()
sprawia, że każdy z tych dwóch procesów tworzy kolejny, zwiększając ich liczbę do 4. Trzecie wywołanie fork()
ponownie podwaja liczbę procesów, co skutkuje 8 aktywnymi procesami. Czwarte wywołanie fork()
powoduje, że każdy z 8 istniejących procesów tworzy nowy, co daje łącznie 16 procesów.Czyli nastąpi szesnastokrotne dodanie wartości 0x499602D2 do wartości 1000000000.
Do obliczeń można wykorzystać program w C:
- #include <stdio.h>
- #include <stdint.h>
- int main()
- {
- int32_t val = (1000000000 + (16 * 1234567890));
- printf("wartosc %d", val);
- return 0;
- }
Otrzymuje wynik -721750240, ponieważ następuje przypisanie wartości do zmiennej int32. Jest on zgodny z oczekiwaną wartością jako flaga.