ELF - исправление точки входа с расширенным адресом x86 - PullRequest
1 голос
/ 16 января 2020

Мне удалось пропатчить точку входа в файл ELF и заставить его указать другое место и выполнить фрагмент кода перед возвратом к исходной точке входа. Вот как я пытаюсь вернуться к OEP:

mov rax, 0x4141414141414141  ( 48 b8 41 41 41 41 41 41 41 41 )
jmp rax                      (ff e0)

У меня есть массив с этими кодами операций, которые я исправляю, как только я анализирую заголовок ELF, чтобы получить точку входа:

uint64_t oep = ehdr->e_entry;
memcpy(&opcode[23], &oep, 8);

Но точка входа всегда выглядит примерно так: 0x47fe8d , что делает недействительным остаток массива, поскольку код операции ожидает 8-байтовый адрес без нулей. Я попытался заменить его знаком, расширяющим адрес, например: 0xffffffff47fe8d , но это не сработало. Это нормальное поведение, поскольку адреса x86 расширены нулями .

EDIT : Массив шелл-кода выглядит следующим образом:

 _start:
       xor rax, rax
       xor rax, rax
       xor rsi, rsi
       jmp get_str
 shellcode:
       pop rsi
       mov al, 1
       mov dil, 1
       mov dl, 9
       syscall ; writes a string

       mov rax, 0x4141414141414141 ; patched with the EP
       jmp rax
   get_str:
         call shellcode
         db "strings!", 0xa

 // write syscall + jmp OEP (mov rax, addr, jmp rax). patch at 23
unsigned char shellcode[] = "\x48\x31\xc0\x48\x31\xff\x48\x31\xf6\xeb"
                  "\x16\x5e\xb0\x01\x40\xb7\x01\xb2\x09\x0f"
                  "\x05\x48\xb8\x41\x41\x41\x41\x41\x41\x41"
                  "\xff\xe0\xe8\xe5\xff\xff\xff\x68\x69\x6a"
                  "\x61\x63\x6b\x65\x64\x0a";

Я создал функцию, которая печатает этот массив перед его исправлением. Вот как это выглядит:

\x48\x31\xc0\x48\x31\xff\x48\x31\xf6\xeb\x16\x5e\xb0\x01\x40\xb7\x01\xb2\x09\x0f\x05\x48\xb8\x41\x41\x41\x41\x41\x41\x41\xff\xe0\xe8\xe5\xff\xff\xff\x68\x69\x6a\x61\x63\x6b\x65\x64\x0a

Но после исправления инструкции jmp с 0x47fe8d старшие байты адреса становятся равными нулю:

\x48\x31\xc0\x48\x31\xff\x48\x31\xf6\xeb\x16\x5e\xb0\x01\x40\xb7\x01\xb2\x09\x0f\x05\x48\xb8\x20\x1b\x40

И это для какая-то причина вызывает ошибку сегментации. Я использовал IDA для поиска точки входа пропатченного файла, и вот что я нашел:

LOAD:000000000047FE8D start:                                  ; DATA XREF: LOAD:0000000000400018↑o
LOAD:000000000047FE8D                 xor     rax, rax
LOAD:000000000047FE90                 xor     rdi, rdi
LOAD:000000000047FE93                 xor     rsi, rsi
LOAD:000000000047FE96
LOAD:000000000047FE96 loc_47FE96:                             ; CODE XREF: LOAD:000000000047FEAC↓j
LOAD:000000000047FE96                 jmp     short loc_47FEAE
LOAD:000000000047FE98 ; ---------------------------------------------------------------------------
LOAD:000000000047FE98                 pop     rsi
LOAD:000000000047FE99                 mov     al, 1
LOAD:000000000047FE9B                 mov     dil, 1
LOAD:000000000047FE9E                 mov     dl, 9
LOAD:000000000047FEA0                 syscall                 ; $!
LOAD:000000000047FEA2                 mov     rax, offset _start
LOAD:000000000047FEAC                 loopne  loc_47FE96
LOAD:000000000047FEAE
LOAD:000000000047FEAE loc_47FEAE:                             ; CODE XREF: LOAD:loc_47FE96↑j
LOAD:000000000047FEAE                 in      eax, 0FFh       ; $!
LOAD:000000000047FEAE ; ---------------------------------------------------------------------------
LOAD:000000000047FEB0                 dq 6B63616A6968FFFFh
LOAD:000000000047FEB8                 db 65h, 64h, 0Ah
LOAD:000000000047FEB8 LOAD            ends

Итак, несмотря на то, что IDA неправильно кодирует инструкцию в 000000000047FEA C, кажется, что файл был успешно исправлен, символ _start ведет по следующему пути:

public _start
_start proc near
endbr64
xor     ebp, ebp
mov     r9, rdx         ; rtld_fini
pop     rsi             ; argc
mov     rdx, rsp        ; ubp_av
and     rsp, 0FFFFFFFFFFFFFFF0h
push    rax
push    rsp             ; stack_end
mov     r8, offset __libc_csu_fini ; fini
mov     rcx, offset __libc_csu_init ; init
mov     rdi, offset main ; main
db      67h
call    __libc_start_main
hlt
_start endp

В результате вызывается исходная основная функция, все кажется в порядке.

При дальнейшем осмотре я обнаружил, что инструкция по 000000000047FEAE является виновником, хотя я не совсем понимаю, почему. Это инструкция call , которую я использовал для вывода sh адреса строки в стек.

Почему я получаю ошибку сегментации?

1 Ответ

1 голос
/ 19 января 2020

IDA не расшифровывает это неправильно, неверная версия вашего машинного кода в шестнадцатеричной строке ; один \x41 короткий байт, поэтому mov r64, imm64 использует следующий FF байт как часть его непосредственного значения вместо кода операции для jmp. Вот почему он декодируется в 0e e8 loopne`.

Я заметил это, скопировав / вставив массив C в .c и скомпилировав его в .o. Затем я разобрал его с помощью objdump -D -rwC -Mintel foo.o, чтобы objdump разобрал раздел .data. Это согласуется с IDA, доказывая, что IDA была права, и вы допустили ошибку во всех своих действиях, чтобы преобразовать выходные данные NASM в шестнадцатеричную строку. (ИДК, почему вы пытаетесь это сделать, вместо того, чтобы просто связываться с выходом NASM .o, чтобы сначала проверить его обычным способом, или то, что он должен делать с изменением двоичного файла ELF.)

 // write syscall + jmp OEP (mov rax, addr, jmp rax). patch at 23
unsigned char shellcode[] = "\x48\x31\xc0\x48\x31\xff\x48\x31\xf6\xeb"
                  "\x16\x5e\xb0\x01\x40\xb7\x01\xb2\x09\x0f"
                  "\x05\x48\xb8\x41\x41\x41\x41\x41\x41\x41"  // this is only 7 x41 bytes
                  "\xff\xe0\xe8\xe5\xff\xff\xff\x68\x69\x6a"
                  "\x61\x63\x6b\x65\x64\x0a";

objdump -D показывает 48 b8 41 41 41 41 41 41 41 ff movabs rax,0xff41414141414141 - самый старший байт вашего mov imm64 - это FF, который должен быть опкодом jmp. Ваша строка C имеет только 7 \x41 байт.

Вы также должны увидеть то же самое, если разберетесь в GDB по команде, которая вызвала ошибку; это, вероятно, привилегированная инструкция in.


Создание значений, содержащих 0 в регистрах с шеллкодом

Эта часть проста. XOR или ADD - некоторая константа, такая как -1 или 0x80, которая делает каждый байт ненулевым, затем НЕ, немедленным или второстепенным. Или pad с небольшим мусором и смещением вправо.

Например, чтобы создать 3-байтовый 0x47fe8d в регистре, вы можете сделать

   mov eax, 0x47fe8d61       ; (0x47fe8d << 8) + 'a'
   shr eax, 8

Записать 32-битный регистр неявно с нулевым расширением до 64 бит, так что получается
RAX = 0 0 0 0 0 47 fe 8d = 0x47fe8d.

или

    mov eax, ~0x47fe8d          ; none of the bytes are FF -> none of ~x are 0
    not eax                     ; still leaving the upper 32 bits zeroed
...