crackmes.one (Example 2)

Setup

Data

  • URL: BitFriends’s simple overflow
  • Language: C/C++
  • Platform: Unix/Linux (ELF64)
  • Description: “This is a very simple crackme. The goal is to get “you are logged in as admin”. Patching is not allowed. The solution should contain an exact description, how this works. Have fun!”

Output

$ ./a.out
enter password: pass
uid: 1
you are logged in as user

Analysis

Tools
Cutter/Radare2

Disassembly (Origin, Commented)

151: login ();
; var char *s @ rbp-0x10
; var void *var_8h @ rbp-0x8
; Setup stack for function
0x00001169      push    rbp
0x0000116a      mov     rbp, rsp
0x0000116d      sub     rsp, 0x10
; Printf("enter password: ")
0x00001171      lea     rdi, str.enter_password: ; 0x2004 ; const char *format
0x00001178      mov     eax, 0
0x0000117d      call    printf     ; sym.imp.printf ; int printf(const char *format)
; Malloc of chunk1 (password)
0x00001182      mov     edi, 0x10  ; size_t size
0x00001187      call    malloc     ; sym.imp.malloc ;  void *malloc(size_t size)
0x0000118c      mov     qword [s], rax
; Malloc of chunk2 (uid)
0x00001190      mov     edi, 8     ; size_t size
0x00001195      call    malloc     ; sym.imp.malloc ;  void *malloc(size_t size)
0x0000119a      mov     qword [var_8h], rax
; *chunk2 = 1
0x0000119e      mov     rax, qword [var_8h]
0x000011a2      mov     dword [rax], 1
; Fgets(chunk1, 0xA0, stdin)
0x000011a8      mov     rdx, qword [stdin] ; obj.stdin__GLIBC_2.2.5
                                   ; 0x4050 ; FILE *stream
0x000011af      mov     rax, qword [s]
0x000011b3      mov     esi, 0xa0  ; int size
0x000011b8      mov     rdi, rax   ; char *s
0x000011bb      call    fgets      ; sym.imp.fgets ; char *fgets(char *s, int size, FILE *stream)
; Printf("uid: %d\n", *chunk2)
0x000011c0      mov     rax, qword [var_8h]
0x000011c4      mov     eax, dword [rax]
0x000011c6      mov     esi, eax
0x000011c8      lea     rdi, str.uid:__d ; 0x2015 ; const char *format
0x000011cf      mov     eax, 0
0x000011d4      call    printf     ; sym.imp.printf ; int printf(const char *format)
; If (*chunk2 == 0)
0x000011d9      mov     rax, qword [var_8h]
0x000011dd      mov     eax, dword [rax]
0x000011df      test    eax, eax
0x000011e1      jne     0x11f1
; Then puts("you are logged in as admin")
0x000011e3      lea     rdi, str.you_are_logged_in_as_admin ; 0x201e ; const char *s
0x000011ea      call    puts       ; sym.imp.puts ; int puts(const char *s)
0x000011ef      jmp     0x11fd
; Else puts("you are logged in as user")
0x000011f1      lea     rdi, str.you_are_logged_in_as_user ; 0x2039 ; const char *s
0x000011f8      call    puts       ; sym.imp.puts ; int puts(const char *s)
0x000011fd      nop
0x000011fe      leave
0x000011ff      ret

Decompiled (Commented)

void login(void)
{
    undefined8 uVar1;
    int32_t *piVar2;
    char *s;
    void *var_8h;
    
    printf("enter password: ");
    uVar1 = malloc(0x10);
    piVar2 = (int32_t *)malloc(8);
    *piVar2 = 1;
    fgets(uVar1, 0xa0, _reloc.stdin);
    printf("uid: %d\n", *piVar2);
    if (*piVar2 == 0) {
        puts("you are logged in as admin");
    } else {
        puts("you are logged in as user");
    }
    return;
}

Conclusion
To become a admin the uid (chunk2) has to be zero. A buffer overflow attack is possible cause the fgets reads more (0xA0) then the size of the password buffer (0x10). The important thing to know is that the chunk size on the heap for a x64 machine is 32 (0x20) bytes. For both password and uid one chunk is enough. It’s not garantueed, but most likely, that these two chunks are right behind one another. By the way the missing frees leading to memory leaks.

The memory layout on the heap most likely looks like this:

... | password (chunk1, 0x20 bytes) | uid (chunk2, 0x20 bytes) | ...

In my test example (entered ‘pass’) it would look like this:

... | x70x61x73x73x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00 | 
      x01x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00 | ...

Solution

Overflow
Insert a string that contains 32 (0x20) bytes of anything to fill the password (chunk1) and then a 0x00 to replace the 0x01 of the uid (chunk2). Let’s use a string with 31 (0x21) times the value of 0x00.

Output

$ python -c 'print("\x00" * 31)' | ./a.out
enter password: uid: 0
you are logged in as admin