Выполнение динамического кода C: ссылки на память

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");

Затем я передаю код для выполнения через файл mmaped

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), поэтому он не будет указывать на мой символ и выйдет из строя.

Есть ли способ решить две мои проблемы?

Кроме того, у меня были проблемы с поиском хорошей документации, связанной с моей проблемой (разрешение низкоуровневой памяти), если бы вы могли мне ее дать, это действительно помогло бы мне.


Изменить: поскольку я запускаю код в ядре, я не могу просто скомпилировать код с помощью -fPIC или -fpie что не разрешено gcc (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 

Таким образом, теперь я могу получить доступ к другим переменным в моем скрипте...

Таким образом, после связывания я могу успешно получить доступ к моей переменной, встроенной в буфер.

40101e: 48 8d 3d e6 0f 00 00    lea    0xfe6(%rip),%rdi        # 40200b

Тем не менее, остается одна проблема:

Символ, к которому я хочу получить доступ (printk) и мой исполняемый буфер находятся в разных адресных пространствах, например:

printk=0xffffffff810e73de:
Executable_buffer=0xffffc9000099d000

Но в моем callq к 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 функционировать как (грязный) взлом.

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));

Затем, когда я узнаю адрес своего исполняемого буфера и адрес символов ядра (путем синтаксического анализа /proc/kallsyms), Я могу исправить свой двоичный файл, используя ldвариант --defsym symbol=relative_address где relative_address = symbol_address - buffer_offset.

Несмотря на то, что этот подход крайне грязный, он действительно работает.

Но мне нужно повторно связывать свой двоичный файл каждый раз, когда я его выполняю, поскольку буфер может (и будет) размещаться по другому адресу. Я думаю, что для решения этой проблемы лучше всего было бы создать исполняемый файл как исполняемый файл, не зависящий от реальной позиции, чтобы я мог просто исправить глобальную таблицу смещений и не повторно связывать модуль полностью.

Но с предоставленными там параметрами я получил относительный адрес копирования, но не получил / plt. Поэтому я хотел бы найти способ построить свой модуль как настоящий PIE.

Этот пост становится огромным, беспорядочным, и мы отклоняемся от исходного вопроса. Таким образом, я открыл новый упрощенный пост там. Если я получу интересные ответы, я отредактирую этот пост, чтобы объяснить их.


Примечание: для простоты тесты безопасности здесь не отображаются.

Примечание 2: я прекрасно понимаю, что мой PoC очень необычен и может быть плохой практикой, но я все равно хотел бы это сделать.

0 ответов

Другие вопросы по тегам