如何在 GCC (x86_64) 中使用内联汇编进行相对跳转/调用
Posted
技术标签:
【中文标题】如何在 GCC (x86_64) 中使用内联汇编进行相对跳转/调用【英文标题】:how to do a relative jump/call with inline assembly in GCC (x86_64) 【发布时间】:2015-03-16 20:11:15 【问题描述】:我正在编写一个从头开始生成 shell 的漏洞利用程序。 (即用于缓冲区溢出)。我面临的问题之一是让 jmp 语句起作用。我的理解是jmp指令是相对于ip的。但是,当我尝试在内联汇编中运行以下命令时,我会跳转到绝对地址。
jmp 0x28 #in inline GCC will jump to address 0x28 not 0x28 relative to the ip
我解决这个问题的一种方法是使用 IP 作为指令的一部分,如下所示:
jmp *0x28(%rip) #will jump to address 0x28 relative to the ip
但是,当我这样做时,我在 jmp 上遇到了分段错误
整个汇编代码如下:
void main()
__asm__(
"jmp *0x28(%rip) \n"
"popq %rax \n"
"movw $0x0, 0x0(%rax) #add null termination \n"
"movq %rax,0x8(%rax) #set up argv in memory \n"
"movq $0, 0x10(%rax) \n"
"mov $0x0, %edx #set up arg 3 \n"
"mov %rax, %rsi \n"
"add $0x8, %rsi \n"
"mov %rax,%rdi \n"
"mov $0x3b,%eax \n"
"syscall \n"
"call *-0x2e(%rip) \n"
".string \"/bin/sh\""
);
GDB 的反汇编输出是:
Dump of assembler code for function main:
0x00000000004004ac <+0>: push %rbp
0x00000000004004ad <+1>: mov %rsp,%rbp
0x00000000004004b0 <+4>: jmpq *0x28(%rip) # 0x4004de <main+50>
0x00000000004004b6 <+10>: pop %rax
0x00000000004004b7 <+11>: movw $0x0,(%rax)
0x00000000004004bc <+16>: mov %rax,0x8(%rax)
0x00000000004004c0 <+20>: movq $0x0,0x10(%rax)
0x00000000004004c8 <+28>: mov $0x0,%edx
0x00000000004004cd <+33>: mov %rax,%rsi
0x00000000004004d0 <+36>: add $0x8,%rsi
0x00000000004004d4 <+40>: mov %rax,%rdi
0x00000000004004d7 <+43>: mov $0x3b,%eax
0x00000000004004dc <+48>: syscall
0x00000000004004de <+50>: callq *-0x2e(%rip) # 0x4004b6 <main+10>
0x00000000004004e4 <+56>: (bad)
0x00000000004004e5 <+57>: (bad)
0x00000000004004e6 <+58>: imul $0x5d006873,0x2f(%rsi),%ebp
0x00000000004004ed <+65>: retq
End of assembler dump.
我在第一条指令jmp *0x28(%rip)
上得到一个段错误,尽管 GDB 说它会转到正确的地址。
有趣的是,如果我在 call *-0x2e(%rip)
之前放置一个标签,然后 jmp 就可以了。地址将是绝对的,不会产生 jmp 处的分段错误。
使用标签的C代码:
void main()
__asm__(
"jmp my_hack \n"
"popq %rax \n"
"movw $0x0, 0x0(%rax) #add null termination \n"
"movq %rax,0x8(%rax) #set up argv in memory \n"
"movq $0, 0x10(%rax) \n"
"mov $0x0, %edx #set up arg 3 \n"
"mov %rax, %rsi \n"
"add $0x8, %rsi \n"
"mov %rax,%rdi \n"
"mov $0x3b,%eax \n"
"syscall \n"
"my_hack: \n"
"call *-0x2e(%rip) \n"
".string \"/bin/sh\""
);
产生的反汇编
Dump of assembler code for function main:
0x00000000004004ac <+0>: push %rbp
0x00000000004004ad <+1>: mov %rsp,%rbp
0x00000000004004b0 <+4>: jmp 0x4004da <main+46>
0x00000000004004b2 <+6>: pop %rax
0x00000000004004b3 <+7>: movw $0x0,(%rax)
0x00000000004004b8 <+12>: mov %rax,0x8(%rax)
0x00000000004004bc <+16>: movq $0x0,0x10(%rax)
0x00000000004004c4 <+24>: mov $0x0,%edx
0x00000000004004c9 <+29>: mov %rax,%rsi
0x00000000004004cc <+32>: add $0x8,%rsi
0x00000000004004d0 <+36>: mov %rax,%rdi
0x00000000004004d3 <+39>: mov $0x3b,%eax
0x00000000004004d8 <+44>: syscall
0x00000000004004da <+46>: callq *-0x2e(%rip) # 0x4004b2 <main+6>
0x00000000004004e0 <+52>: (bad)
0x00000000004004e1 <+53>: (bad)
0x00000000004004e2 <+54>: imul $0x5d006873,0x2f(%rsi),%ebp
0x00000000004004e9 <+61>: retq
End of assembler dump.
上述反汇编中使用标签的跳转不会产生分段错误。在0x00000000004004da
执行的调用将。
谁能解释为什么在 jmp 中使用 rip 会导致分段错误?
如何使用 GCC 内联汇编完成相对跳转/调用?我不知道如何检查汇编器,但我很确定我正在使用 GAS(在他们的 wiki 上它说它是默认的 GCC 汇编器)。在相关问题中有建议使用 jmp .+0x28
等语法,但这将导致绝对跳转而不是相对跳转。
【问题讨论】:
【参考方案1】:当您将jmp
和call
指向标签时,您使用的是相对地址而不是绝对地址。您在 GDB 中看到的反汇编可能具有欺骗性,请尝试 objdump -D <ELF file>
并查找 main
段。
这是objdump
告诉我们您的第一个示例的内容。
00000000004004b4 <main>:
4004b4: 55 push %rbp
4004b5: 48 89 e5 mov %rsp,%rbp
4004b8: ff 25 28 00 00 00 jmpq *0x28(%rip) # 4004e6 <main+0x32>
4004be: 58 pop %rax
4004bf: 66 c7 00 00 00 movw $0x0,(%rax)
4004c4: 48 89 40 08 mov %rax,0x8(%rax)
4004c8: 48 c7 40 10 00 00 00 movq $0x0,0x10(%rax)
4004cf: 00
4004d0: ba 00 00 00 00 mov $0x0,%edx
4004d5: 48 89 c6 mov %rax,%rsi
4004d8: 48 83 c6 08 add $0x8,%rsi
4004dc: 48 89 c7 mov %rax,%rdi
4004df: b8 3b 00 00 00 mov $0x3b,%eax
4004e4: 0f 05 syscall
4004e6: ff 15 d2 ff ff ff callq *-0x2e(%rip) # 4004be <main+0xa>
4004ec: 2f (bad)
4004ed: 62 (bad)
4004ee: 69 6e 2f 73 68 00 5d imul $0x5d006873,0x2f(%rsi),%ebp
4004f5: c3 retq
0x4004b8
上的 jmp
可能不是您想要的。它跳转到内存位置0x4004e6
引用的地址;尝试在0x622fffffffd215ff
执行指令可能会引发页面错误。同样,0x4004e6
处的 call
实际上正在将程序计数器移动到 0x66580000002825ff
,从而导致另一个可能的段错误。
我稍微修改了你的第二个例子
void main()
__asm__(
"jmp my_hack \n"
"my_hack2:\n"
"popq %rax \n"
"movw $0x0, 0x0(%rax) #add null termination \n"
"movq %rax,0x8(%rax) #set up argv in memory \n"
"movq $0, 0x10(%rax) \n"
"mov $0x0, %edx #set up arg 3 \n"
"mov %rax, %rsi \n"
"add $0x8, %rsi \n"
"mov %rax,%rdi \n"
"mov $0x3b,%eax \n"
"syscall \n"
"my_hack: \n"
"call my_hack2 \n"
".string \"/bin/sh\""
);
...以及来自objdump
的反汇编
00000000004004b4 <main>:
4004b4: 55 push %rbp
4004b5: 48 89 e5 mov %rsp,%rbp
4004b8: eb 28 jmp 4004e2 <my_hack>
00000000004004ba <my_hack2>:
4004ba: 58 pop %rax
4004bb: 66 c7 00 00 00 movw $0x0,(%rax)
4004c0: 48 89 40 08 mov %rax,0x8(%rax)
4004c4: 48 c7 40 10 00 00 00 movq $0x0,0x10(%rax)
4004cb: 00
4004cc: ba 00 00 00 00 mov $0x0,%edx
4004d1: 48 89 c6 mov %rax,%rsi
4004d4: 48 83 c6 08 add $0x8,%rsi
4004d8: 48 89 c7 mov %rax,%rdi
4004db: b8 3b 00 00 00 mov $0x3b,%eax
4004e0: 0f 05 syscall
00000000004004e2 <my_hack>:
4004e2: e8 d3 ff ff ff callq 4004ba <my_hack2>
4004e7: 2f (bad)
4004e8: 62 (bad)
4004e9: 69 6e 2f 73 68 00 5d imul $0x5d006873,0x2f(%rsi),%ebp
4004f0: c3 retq
即使您不知道jmp
和call
的指令编码,也希望很明显,汇编程序为0x4004b8
和0x4004e2
处的指令生成了相对地址。
您的程序仍然存在段错误,但希望这可以帮助您找出原因。
【讨论】:
【参考方案2】:我认为你的间接性有点太多了。试试
jmp 0x28(%rip)
在汇编程序中,它会(大约)写成
jmp $+0x28
我写了大约,因为汇编指令是相对于指令的起始地址的。但是rip
在执行时会递增到下一条指令。所以要获得类似的效果
jmp $+0x24 # maybe 0x23, maybe 0x25 depending on the instruction length
【讨论】:
在 GAS 语法中,jmp . + 0x22
组装成 eb 20
。 dot 是 NASM $
的 GAS 等价物。 jmp 0x28(%rip)
仍然有太多间接性:gas 警告:Warning: indirect jmp without '*'
并组装 ff 25 28 00 00 00 jmp QWORD PTR [rip+0x28]
当然好的解决方案是使用本地标签,甚至编号标签,如0: ...
/call 0b
(寻找第一个0:
标签倒退1b and 1f in GNU assembly。或者使用.Lfoo%=:
在 inline-asm 块中获取唯一标签。以上是关于如何在 GCC (x86_64) 中使用内联汇编进行相对跳转/调用的主要内容,如果未能解决你的问题,请参考以下文章