tl; dr:我正在尝试从另一个片段中动态执行一些代码。但是我坚持处理内存引用(例如mov 40200b, %rdi
):我可以修补我的代码或运行代码的片段以便0x40200b
正确解析(作为200b
代码的偏移量)吗?
为了生成要动态执行的代码,我从一个(内核)对象开始,并使用 ld 解析引用。
#!/usr/bin/python
import os, subprocess
if os.geteuid() != 0:
print('Run this as root')
exit(-1)
with open("/proc/kallsyms","r") as f:
out=f.read()
sym= subprocess.Popen( ['nm', 'ebbchar.ko', '-u' ,'--demangle', '-fposix'],stdout=subprocess.PIPE)
v=''
for sym in sym.stdout:
s = " "+ sym.split()[0]+ "\n"
off = out.find(s)
v += "--defsym "+s.strip() + "=0x" +out[off-18:off -2]+" "
print(v)
os.system("ld ebbchar.ko "+ v +"-o ebbchar.bin");
然后我通过一个映射文件传输要执行的代码
int fd = open(argv[1], O_RDWR | O_SYNC);
address1 = mmap(NULL, page_size, PROT_WRITE|PROT_READ , MAP_SHARED, fd, 0);
int in=open(argv[2],O_RDONLY);
sz= read(in, buf+8,BUFFER_SIZE-8);
uint64_t entrypoint=atol(argv[3]);
*((uint64_t*)buf)=entrypoint;
write(fd, buf, min(sz+8, (size_t) BUFFER_SIZE));
我用这段代码动态地执行代码
struct mmap_info *info;
copy_from_user((void*)(&info->offset),buf,8);
copy_from_user(info->data, buf+8, sz-8);
unsigned long (*func)(void) func= (void*) (info->data + info->offset);
int ret= func();
这种方法适用于不访问内存的代码,例如"\x55\x48\x89\xe5\xc7\x45\xf8\x02\x00\x00\x00\xc7\x45\xfc\x03\x00\x00\x00\x8b\x55\xf8\x8b\x45\xfc\x01\xd0\x5d\xc3"
当涉及内存时我遇到问题。
请参见下面的示例。
假设我不想动态执行函数 vm_close。Objdump -d -S
返回:
0000000000401017 <vm_close>:
{
401017: e8 e4 07 40 81 callq ffffffff81801800 <__fentry__>
printk(KERN_INFO "vm_close");
40101c: 48 c7 c7 0b 20 40 00 mov $0x40200b,%rdi
401023: e9 b6 63 ce 80 jmpq ffffffff810e73de <printk>
在执行时,我的函数指针指向正确的代码:
(gdb) x/12x $rip
0xffffc90000c0601c: 0x48 0xc7 0xc7 0x0b 0x20 0x40 0x00 0xe9
0xffffc90000c06024: 0xb6 0x63 0xce 0x80
(gdb) x/2i $rip
=> 0xffffc90000c0601c: mov $0x40200b,%rdi
0xffffc90000c06023: jmpq 0xffffc8ff818ec3de
但是,此代码将失败,因为:
1)在我的上下文中 $0x40200b 指向物理地址$0x40200b
,而不是offset 200b
从代码的开头。
2)我不明白为什么,但那里显示的地址实际上与正确的地址不同(0xffffc8ff818ec3de!= ffffffff810e73de)所以它不会指向我的符号并且会崩溃。
有没有办法解决我的两个问题?
另外,我很难找到与我的问题(低级内存分辨率)相关的好文档,如果你能给我一些,那真的对我有帮助。
编辑:由于我在内核中运行代码,我不能简单地用 gcc ( )-fPIC
或-fpie
不允许的代码编译代码cc1: error: code model kernel does not support PIC mode
编辑 24/09:
根据@Peter Cordes 的评论,我重新编译它添加mcmodel=small -fpie -mno-red-zone -mnosse
到 Makefile ( /lib/modules/$(uname -r)fixed/build/Makefile
) 这比原始版本更好,因为在链接之前生成的代码现在是:
0000000000000018 <vm_close>:
{
18: ff 15 00 00 00 00 callq *0x0(%rip) # 1e <vm_close+0x6>
printk(KERN_INFO "vm_close");
1e: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 25 <vm_close+0xd>
25: e8 00 00 00 00 callq 2a <vm_close+0x12>
}
2a: c3 retq So thanks to rip-relative addressing
因此,我现在可以访问脚本中的其他变量了……</p>
因此,链接后,我可以成功访问嵌入在缓冲区中的变量。
40101e: 48 8d 3d e6 0f 00 00 lea 0xfe6(%rip),%rdi # 40200b
尽管如此,仍然存在一个问题:
我要访问的符号 ( printk
) 和我的可执行缓冲区位于不同的地址空间中,例如:
printk=0xffffffff810e73de:
Executable_buffer=0xffffc9000099d000
但是在我callq
的 to 中printk
,我只有 32 位来写入要调用的地址作为偏移量,$rip
因为内核中没有.got
部分。这意味着 printk必须位于[$rip-2GO, $rip+2GO]
. 但情况并非如此。
我是否有办法访问 printk 地址,尽管它们距离我的缓冲区超过 2GO(我尝试使用mcmodel=medium
但我没有看到生成的代码有任何区别),例如通过修改 gcc 选项以便二进制文件居然有.got
节?
或者是否有一种可靠的方法可以强制在(我目前使用)中分配我的可执行文件和可能对 kmalloc 而言太大的缓冲区[0xffffffff00000000 ; 0xffffffffffffffff] range?
__vmalloc(BUFFER_SIZE, GFP_KERNEL, PAGE_KERNEL_EXEC);
编辑 27/09:[0xffffffff00000000 ; 0xffffffffffffffff]
我使用非导出 __vmalloc_node_range
函数作为(脏)hack
成功地在范围内分配了我的缓冲区。
IMPORTED(__vmalloc_node_range)(BUFFER_SIZE, MODULE_ALIGN,
MODULES_VADDR + get_module_load_offset(),
MODULES_END, GFP_KERNEL,
PAGE_KERNEL_EXEC, 0, NUMA_NO_NODE,
__builtin_return_address(0));
然后,当我知道我的可执行缓冲区的地址和内核符号的地址(通过解析)时,我可以使用's option where/proc/kallsyms
修补我的二进制文件。ld
--defsym symbol=relative_address
relative_address = symbol_address - buffer_offset
尽管非常肮脏,但这种方法确实有效。
但是我每次执行它时都需要重新链接我的二进制文件,因为缓冲区可能(并且将)分配在不同的地址。为了解决这个问题,我认为最好的方法是将我的可执行文件构建为一个真正的位置独立的可执行文件,这样我就可以修补全局偏移表而不是完全重新链接模块。
但是通过那里提供的选项,我得到了一个相对于 rip 的地址,但没有得到/plt。所以我想找到一种方法将我的模块构建为一个合适的 PIE。
这篇文章变得庞大而混乱,我们偏离了最初的问题。因此,我在那里打开了一个新的简化帖子。如果我得到有趣的答案,我会编辑这篇文章来解释它们。
注意:为简单起见,此处未显示安全测试
注 2:我非常清楚我的 PoC 非常不寻常,可能是一种不好的做法,但我还是想这样做。