2

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_addressrelative_address = symbol_address - buffer_offset

尽管非常肮脏,但这种方法确实有效。

但是我每次执行它时都需要重新链接我的二进制文件,因为缓冲区可能(并且将)分配在不同的地址。为了解决这个问题,我认为最好的方法是将我的可执行文件构建为一个真正的位置独立的可执行文件,这样我就可以修补全局偏移表而不是完全重新链接模块。

但是通过那里提供的选项,我得到了一个相对于 rip 的地址,但没有得到/plt。所以我想找到一种方法将我的模块构建为一个合适的 PIE。

这篇文章变得庞大而混乱,我们偏离了最初的问题。因此,我在那里打开了一个新的简化帖子。如果我得到有趣的答案,我会编辑这篇文章来解释它们。


注意:为简单起见,此处未显示安全测试

注 2:我非常清楚我的 PoC 非常不寻常,可能是一种不好的做法,但我还是想这样做。

4

0 回答 0