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