Выполнение динамического кода 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 очень необычен и может быть плохой практикой, но я все равно хотел бы это сделать.