Skip to main content

Binary Exploitation

Your First Overflow (easy)

Source code

/challenge/binary-exploitation-first-overflow-w.c
 #define _GNU_SOURCE 1

#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <assert.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/signal.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/sendfile.h>
#include <sys/prctl.h>
#include <sys/personality.h>
#include <arpa/inet.h>

uint64_t sp_;
uint64_t bp_;
uint64_t sz_;
uint64_t cp_;
uint64_t cv_;
uint64_t si_;
uint64_t rp_;

#define GET_SP(sp) asm volatile ("mov %0, rsp" : "=r"(sp) : : );
#define GET_BP(bp) asm volatile ("mov %0, rbp" : "=r"(bp) : : );
#define GET_CANARY(cn) asm volatile ("mov %0, QWORD PTR [fs:0x28]" : "=r"(cn) : : );
#define GET_FRAME_WORDS(sz_, sp, bp, rp_) GET_SP(sp); GET_BP(bp); sz_ = (bp-sp)/8+2; rp_ = bp+8;
#define FIND_CANARY(cnp, cv, start) \
{ \
cnp = start; \
GET_CANARY(cv); \
while (*(uint64_t *)cnp != cv) cnp = (uint64_t)cnp - 8; \
}

void DUMP_STACK(uint64_t sp, uint64_t n)
{
printf("+---------------------------------+-------------------------+--------------------+\n");
printf("| %31s | %23s | %18s |\n", "Stack location", "Data (bytes)", "Data (LE int)");
printf("+---------------------------------+-------------------------+--------------------+\n");
for (si_ = 0; si_ < n; si_++)
{
printf("| 0x%016lx (rsp+0x%04x) | %02x %02x %02x %02x %02x %02x %02x %02x | 0x%016lx |\n",
sp+8*si_, 8*si_,
*(uint8_t *)(sp+8*si_+0), *(uint8_t *)(sp+8*si_+1), *(uint8_t *)(sp+8*si_+2), *(uint8_t *)(sp+8*si_+3),
*(uint8_t *)(sp+8*si_+4), *(uint8_t *)(sp+8*si_+5), *(uint8_t *)(sp+8*si_+6), *(uint8_t *)(sp+8*si_+7),
*(uint64_t *)(sp+8*si_)
);
}
printf("+---------------------------------+-------------------------+--------------------+\n");
}

void bin_padding()
{
asm volatile (".rept 3705; nop; .endr");
}

void win()
{
static char flag[256];
static int flag_fd;
static int flag_length;

puts("You win! Here is your flag:");
flag_fd = open("/flag", 0);
if (flag_fd < 0)
{
printf("\n ERROR: Failed to open the flag -- %s!\n", strerror(errno));
if (geteuid() != 0)
{
printf(" Your effective user id is not 0!\n");
printf(" You must directly run the suid binary in order to have the correct permissions!\n");
}
exit(-1);
}
flag_length = read(flag_fd, flag, sizeof(flag));
if (flag_length <= 0)
{
printf("\n ERROR: Failed to read the flag -- %s!\n", strerror(errno));
exit(-1);
}
write(1, flag, flag_length);
printf("\n\n");
}

int challenge(int argc, char **argv, char **envp)
{
struct
{
char input[56];
int win_variable;
} data = {0} ;

unsigned long size = 0;

puts("The challenge() function has just been launched!");

GET_FRAME_WORDS(sz_, sp_, bp_, rp_);
puts("Before we do anything, let's take a look at challenge()'s stack frame:");
DUMP_STACK(sp_, sz_);
printf("Our stack pointer points to %p, and our base pointer points to %p.\n", sp_, bp_);
printf("This means that we have (decimal) %d 8-byte words in our stack frame,\n", sz_);
printf("including the saved base pointer and the saved return address, for a\n");
printf("total of %d bytes.\n", sz_ * 8);
printf("The input buffer begins at %p, partway through the stack frame,\n", &data.input);
printf("(\"above\" it in the stack are other local variables used by the function).\n");
printf("Your input will be read into this buffer.\n");
printf("The buffer is %d bytes long, but the program will let you provide an arbitrarily\n", 56);
printf("large input length, and thus overflow the buffer.\n\n");

printf("In this level, there is a \"win\" variable.\n");
printf("By default, the value of this variable is zero.\n");
printf("However, when this variable is non-zero, the flag will be printed.\n");
printf("You can make this variable be non-zero by overflowing the input buffer.\n");
printf("The \"win\" variable is stored at %p, %d bytes after the start of your input buffer.\n\n", &data.win_variable, ((unsigned long) &data.win_variable) - ((unsigned long) &data.input));

puts("We have disabled the following standard memory corruption mitigations for this challenge:");
puts("- the binary is *not* position independent. This means that it will be");
puts("located at the same spot every time it is run, which means that by");
puts("analyzing the binary (using objdump or reading this output), you can");
puts("know the exact value that you need to overwrite the return address with.\n");

FIND_CANARY(cp_, cv_, bp_);

size = 4096;

printf("You have chosen to send %lu bytes of input!\n", size);
printf("This will allow you to write from %p (the start of the input buffer)\n", &data.input);
printf("right up to (but not including) %p (which is %d bytes beyond the end of the buffer).\n", size + (unsigned long) &data.input, size - 56);

printf("Send your payload (up to %lu bytes)!\n", size);
int received = read(0, &data.input, (unsigned long) size);

if (received < 0)
{
printf("ERROR: Failed to read input -- %s!\n", strerror(errno));
exit(1);
}

printf("You sent %d bytes!\n", received);

printf("Let's see what happened with the stack:\n\n");
DUMP_STACK(sp_, sz_);

printf("The program's memory status:\n");
printf("- the input buffer starts at %p\n", &data.input);
printf("- the saved frame pointer (of main) is at %p\n", bp_);
printf("- the saved return address (previously to main) is at %p\n", rp_);
printf("- the saved return address is now pointing to %p.\n", *(unsigned long*)(rp_));
printf("- the canary is stored at %p.\n", cp_);
printf("- the canary value is now %p.\n", *(unsigned long*)(cp_));
printf("- the address of the win variable is %p.\n", &data.win_variable);
printf("- the value of the win variable is 0x%x.\n", data.win_variable);
printf("\n");

if (data.win_variable)
{
win();
}

puts("Goodbye!");

return 0;
}

int main(int argc, char **argv, char **envp)
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);

char crash_resistance[0x1000];

challenge(argc, argv, envp);

}

On running the challenge, we get the following:

hacker@binary-exploitation~your-first-overflow-easy:/$ /challenge/binary-exploitation-first-overflow-w
The challenge() function has just been launched!
Before we do anything, let's take a look at challenge()'s stack frame:
+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffd92a28580 (rsp+0x0000) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a28588 (rsp+0x0008) | 48 97 a2 92 fd 7f 00 00 | 0x00007ffd92a29748 |
| 0x00007ffd92a28590 (rsp+0x0010) | 38 97 a2 92 fd 7f 00 00 | 0x00007ffd92a29738 |
| 0x00007ffd92a28598 (rsp+0x0018) | 25 a5 b4 bf 01 00 00 00 | 0x00000001bfb4a525 |
| 0x00007ffd92a285a0 (rsp+0x0020) | 00 10 00 00 00 00 00 00 | 0x0000000000001000 |
| 0x00007ffd92a285a8 (rsp+0x0028) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a285b0 (rsp+0x0030) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a285b8 (rsp+0x0038) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a285c0 (rsp+0x0040) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a285c8 (rsp+0x0048) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a285d0 (rsp+0x0050) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a285d8 (rsp+0x0058) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a285e0 (rsp+0x0060) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a285e8 (rsp+0x0068) | 00 00 00 00 fd 7f 00 00 | 0x00007ffd00000000 |
| 0x00007ffd92a285f0 (rsp+0x0070) | b0 11 40 00 00 00 00 00 | 0x00000000004011b0 |
| 0x00007ffd92a285f8 (rsp+0x0078) | 00 13 c2 3e ea 86 82 2f | 0x2f8286ea3ec21300 |
| 0x00007ffd92a28600 (rsp+0x0080) | 40 96 a2 92 fd 7f 00 00 | 0x00007ffd92a29640 |
| 0x00007ffd92a28608 (rsp+0x0088) | 50 29 40 00 00 00 00 00 | 0x0000000000402950 |
+---------------------------------+-------------------------+--------------------+
Our stack pointer points to 0x7ffd92a28580, and our base pointer points to 0x7ffd92a28600.
This means that we have (decimal) 18 8-byte words in our stack frame,
including the saved base pointer and the saved return address, for a
total of 144 bytes.
The input buffer begins at 0x7ffd92a285b0, partway through the stack frame,
("above" it in the stack are other local variables used by the function).
Your input will be read into this buffer.
The buffer is 56 bytes long, but the program will let you provide an arbitrarily
large input length, and thus overflow the buffer.

In this level, there is a "win" variable.
By default, the value of this variable is zero.
However, when this variable is non-zero, the flag will be printed.
You can make this variable be non-zero by overflowing the input buffer.
The "win" variable is stored at 0x7ffd92a285e8, 56 bytes after the start of your input buffer.

We have disabled the following standard memory corruption mitigations for this challenge:
- the binary is *not* position independent. This means that it will be
located at the same spot every time it is run, which means that by
analyzing the binary (using objdump or reading this output), you can
know the exact value that you need to overwrite the return address with.

You have chosen to send 4096 bytes of input!
This will allow you to write from 0x7ffd92a285b0 (the start of the input buffer)
right up to (but not including) 0x7ffd92a295b0 (which is 4040 bytes beyond the end of the buffer).
Send your payload (up to 4096 bytes)!