Welcome to Rainfall, a collection of walkthroughs and solutions for binary exploitation challenges and deep dives into reverse engineering on i386 systems. In this project, we explore various techniques such as buffer overflows, shellcode injection, format string exploits for printf, heap and stack injection, Global Offset Table (GOT) hijacking, and more.
Rainfall consists of 10 binaries that have been carefully selected and analyzed. Each binary presents unique challenges, allowing you to explore different aspects of binary exploitation and reverse engineering.
- Binary Exploitation: Learn how to exploit vulnerabilities in binary executables.
- Reverse Engineering: Deep dive into understanding the inner workings of compiled programs.
- Multiple Techniques: Explore a variety of techniques including buffer overflows, shellcode injection, format string exploits, and more.
- Practical Examples: Real-world scenarios to apply your knowledge and skills.
Each binary comes with a detailed walkthrough and solution, guiding you through the process of analyzing, reverse engineering, and exploiting the vulnerabilities present in the executable.
To get started with Rainfall, clone this repository and navigate to the specific level you want to analyze. Each level directory contains the following:
- binary: The executable binary file.
- flag: The obtained flag.
- source.c: Optional, contains the original C source code if available.
- README.md: Detailed walkthrough and solution for the binary.
- resources: The tools used to exploit the binary.
To follow along with the walkthroughs, you'll need:
- Basic understanding of C programming language.
- Familiarity with Linux environments.
- Knowledge of assembly language (x86/i386 architecture).
Level 0
Objective: Binary analysis to find out which number we have to input (423).README.md
int main(int argc, const char **argv, const char **envp)
{
if (atoi(argv[1]) != 423)
{
// Write "No !" to stderr
fwrite("No !\n", sizeof(char), 5, stderr);
}
else
{
// Execute /bin/sh
char *shell_cmd = strdup("/bin/sh");
setresgid(getegid(), getegid(), getegid());
setresuid(geteuid(), geteuid(), geteuid());
execv(shell_cmd, argv);
}
return (0);
}
Level 1
Objective: Buffer overflow on gets function, to overwritte the EIP reg to point to the run function.README.md Walkthrough_pwntools.md
void run() {
FILE *stdout_ptr = stdout;
// Print the message "Good... Wait what?\n" to the standard output
fwrite("Good... Wait what?\n", sizeof(char), 17, stdout_ptr);
// Execute the "/bin/sh" shell command
system("/bin/sh");
}
int main(int argc, const char **argv, const char **envp)
{
char buffer[76]; // Buffer to hold user input
gets(buffer); // Reading input from the user
return 0;
}
Level 2
Objective: Buffer overflow on gets function, to inject shellcode into the Heap, and execute it overwriting the EIP reg to point to the code on the Heap.README.md
int p()
{
char buffer[64]; // ebp+0x4C - ebp+0xC
int arg;
int eax;
int edx;
fflush(stdout); // Flush stdout buffer
gets(buffer); // Again, possible buffer overflow
memcpy(eax, &buffer[80], 4); // Copy EIP (return address) from buffer[80] to eax
arg = &buffer[64]; // Set arg to point to the end of buffer
memcpy(arg, eax, 4); // Copy 4 bytes from eax to arg
memcpy(eax, arg, 4); // Copy 4 bytes from arg to eax
if ( (eax & 0xB0000000) == 0xB0000000 )
{
printf("(%p)\n", arg);
exit(1);
}
puts(buffer);
return (strdup(buffer));
}
int main(int argc, const char **argv, const char **envp)
{
return (p());
}
Level 3
Objective: Format string attack on printf function, to inject the number 64 into the global variable m.README.md
int m = 0;
int v() {
int result; // EAX
char buffer[520]; // [esp+10h] [ebp-208h] BYREF
fgets(buffer, 512, stdin); // Read up to 512 characters from stdin
printf(buffer); // Print the buffer
result = m;
if (m == 64) { // @
fwrite("Wait what?!\n", 1, 12, stdout);
return (system("/bin/sh"));
}
return (result);
}
int main() {
return (v());
}
Level 4
Objective: Format string attack on printf function, to inject the number 16930116 into the global variable m.README.md
int m = 0;
int p(int buffer) {
return (printf(buffer));
}
int n()
{
int eax; // EAX
char v1[520]; // [esp+10h] [ebp-208h] BYREF
fgets(v1, 512, stdin);
p(v1);
eax = m;
if ( m == 16930116 )
return system("/bin/cat /home/user/level5/.pass");
return eax;
}
int main() {
int eax;
n();
return (eax);
}
Level 5
Objective: Format string attack on printf function, to hijack the Global Offset Table replacing there the exit address for the o() function address to redirect the code execution.README.md
int n()
{
char v4[520]; // [esp+10h] [ebp-208h] BYREF
fgets(v4, 512, stdin);
printf(v4);
exit(1);
}
int o()
{
system("/bin/sh");
_exit(1);
}
int main() {
return (n());
}
Level 6
Objective: Buffer overflow on strcpy function, to overwrite the EIP (which was going to execute m()) to make it execute n() instead.README.md
int n()
{
return (system("/bin/cat /home/user/level7/.pass"));
}
int m()
{
return (puts("Nope"));
}
int main(int argc, const char **argv, const char **envp)
{
int (**v4)(void); // [esp+18h] [ebp-8h]
int v5; // [esp+1Ch] [ebp-4h]
v5 = malloc(64);
v4 = (int (**)(void))malloc(4);
*v4 = m;
strcpy(v5, argv[1]);
return ((*v4)());
}
Level 7
Objective: Buffer overflow on the 2 strcpy functions, to hijack the Global Offset Table replacing there the puts address for the m() function address to redirect the code execution and print the .pass content.README.md
char *c = NULL;
int m()
{
int eax;
eax = time(0);
return printf("%s - %d\n", c, eax);
}
int main(int argc, const char **argv, const char **envp)
{
int eax; // eax
_DWORD *v5; // [esp+18h] [ebp-8h] argv[2]
_DWORD *v6; // [esp+1Ch] [ebp-4h] argv[1]
v6 = (_DWORD *)malloc(8);
*v6 = 1;
v6[1] = malloc(8);
v5 = (_DWORD *)malloc(8);
*v5 = 2;
v5[1] = malloc(8);
strcpy(v6[1], argv[1]); // Vulnerable for buffer overflow
strcpy(v5[1], argv[2]); // Vulnerable for buffer overflow
eax = fopen("/home/user/level8/.pass", "r");
fgets(&c, 68, eax); // 68 is the lenght of the flag from .pass
// c has now the flag
puts("~~"); // Call m() instead of puts()
return 0;
}
Level 8
Objective: play with the options of the program to write at the 32th byte of the auth global variable.README.md
char *auth = NULL;
char *service = NULL;
int main(int argc, const char **argv, const char **envp)
{
char *input;
while ( 1 )
{
printf("%p, %p \n", auth, service);
if ( !fgets(input, 128, stdin) )
break;
if ( !memcmp(input, "auth ", 5u) )
{
auth = malloc(4);
*auth = 0;
if ( strlen(input + 5) <= 30 )
strcpy(auth, input + 5);
}
if ( !memcmp(input, "reset", 5u) )
free(auth);
if ( !memcmp(input, "service", 6u) )
service = strdup(input + 7);
if ( !memcmp(input, "login", 5u) )
{
if ( auth[32] )
system("/bin/sh");
else
fwrite("Password:\n", 1, 10, stdout);
}
}
return 0;
}
Level 9
Objective: Buffer overflow on c++ object with memcpy to overwrite the vtable of a second object on the heap.README.md
class N {
public:
char buffer[100];
int value;
N(int value) : value(value) {}
void setAnnotation(char* annotation) {
int len = strlen(annotation);
std::memcpy(this->buffer, annotation, len);
}
int operator+(const N &right) const {
return (this->value + right.value);
}
int operator-(const N &right) const {
return (this->value - right.value);
}
};
int main(int argc, char* argv[]) {
if (argc < 2) {
exit(1);
}
N* instance1 = new N(5);
N* instance2 = new N(6);
instance1->setAnnotation(argv[1]);
return (*instance2 + *instance1);
}
Rainfall is intended for educational purposes only. By using this project, you agree that the authors are not responsible for any misuse or illegal activities.
This project is licensed under the GNU General Public License - see the LICENSE file for details.