【发布时间】:2020-01-16 20:24:43
【问题描述】:
我已经对 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 地址是 zero-extended。
编辑:shellcode 数组如下所示:
_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
但是在用 0x47fe8d 修补 jmp 指令后,地址的高字节变为零:
\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 错误地对 000000000047FEAC 处的指令进行了编码,但似乎文件已成功修补,_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
这最终调用了原始的 main 函数,一切似乎都井井有条。
经过进一步检查,我发现 000000000047FEAE 处的指令是罪魁祸首,虽然我不太明白为什么。 这是我用来将字符串地址压入堆栈的call指令。
为什么会出现分段错误?
【问题讨论】:
-
ELF 入口点是 64 位地址,不使用 32 位地址大小前缀截断 + 零扩展回 64 位。你说的是shellcode吗?如果您需要在包含零的寄存器中创建一个值,您可以使用
mov rax, (-1) ^ 0x47fe8d之类的东西来实现;not rax:即使用不包含零的立即数常量,然后异或或减去某个值以获得寄存器中所需的值。 -
@PeterCordes 是的,彼得,我已经用更多细节更新了这个问题。
-
究竟是什么导致了段错误?运行 shellcode 的程序,还是修补过的二进制文件?使用调试器查看哪些指令错误。那些十六进制字符串的代码块,如果不包括反汇编,对大多数人来说基本上是没用的。
-
@PeterCordes 根据 gdb 它在 000000000047FEAE 崩溃,这是我使用 call 指令将字符串放入堆栈时。出于某种原因IDA 解码错误。
-
IDA 没有解码错误,你的十六进制字符串是错误的。如果我将该 C 数组编译成
.o并用objdump -D -rwC -Mintel foo.o反汇编它以让 objdump 反汇编.data部分,这就是它对我的反汇编方式。如果您在 GDB 中按照出错的指令进行反汇编,您也应该会看到同样的情况;它可能是特权的in指令。 (为什么这甚至是一个 char 数组中的 shellcode,而不仅仅是一个用 asm 编写的函数,组装成.o,然后链接到您的程序中?)