【问题标题】:Basic nasm shellcode crashes基本的 nasm shellcode 崩溃
【发布时间】:2016-12-03 14:25:24
【问题描述】:

我正在学习汇编,所以我正在研究 shellcode。我在 nasm 中写了一个简单的“hello world”程序,但是运行时它崩溃了。

; write.asm
[SECTION .text]

global _start

_start:
    jmp short message;

write:          ; takes two arguments pushed onto stack -> text, textlen
    pop edx     ; pop length into edx
    pop ecx     ; pop ptr to text into ecx
    pushad
    mov al, 4
    mov bl, 1
    int 80h    ; syscall
    popad
    ret

exit:           ; push exit_code onto stack
    mov al, 1
    pop ebx     ; pop exit_code into ebx
    int 80h

main:
    pop eax     ; pop ptr to message into eax
    push 7      ; length of string
    push eax    ; push ptr to message
    call write
    xor ebx, ebx; zero out ebx
    push ebx
    call exit
message:
    call main
    db 'Hello!', 10

我编译它:

nasm -f elf write.asm
ld -m elf_i386 -o write write.o

然后得到这个:

Segmentation fault (core dumped)

我尝试用 gdb 调试它,但没有帮助。

【问题讨论】:

  • 不是字符串 Hello!应该以0结尾?因为它是一个字符串?
  • @rcd 因为这是一个 write 调用,我必须提供字符串的长度,不,它不需要以 null 结尾。
  • 在调试器中运行它会显示什么?自从您在write 的开头将其从堆栈中弹出后,您认为ret 指令将返回到哪里?也许您忘记了call 返回地址被压入堆栈?我怀疑由于您弹出了返回地址加上第一个参数,因此您的代码试图返回内存地址 7,当您调用 write 返回时,该地址将位于堆栈的顶部。
  • 在 pop edx 中,我认为您已经从通话中弹出了返回地址?而不是长度?你检查了吗?
  • @Fluffy 我认为如果 esp 指向您期望的值,您应该跟踪它。我认为您无视调用操纵堆栈的方式。如果它确实指向正确的变量,则在调试器中使用 pop 或 push 检查之前。我怀疑它崩溃是因为执行返回到一个无效的地址。

标签: linux assembly nasm system-calls ld


【解决方案1】:

我想向您展示,您如何使用 gdb 找到您的问题:

  1. 启动 gdb 并加载您的程序:>> gdb write
  2. 直接在开始时设置断点:(gdb) b _start。你可以让程序运行到段错误,但如果堆栈搞砸了,很有可能你什么都看不到。
  3. 设置显示以在每一步后自动显示堆栈上的 5 个顶部值:(gdb) display/x {unsigned int[5]}$sp
  4. 查看下一个执行的行:(gdb) display/i $pc
  5. 现在运行:(gdb) run

调试器命中断点,您会看到系统设置的堆栈:

1: /x {unsigned int[5]}$sp = {0x1, 0xffffd284, 0x0, 0xffffd29d, 0xffffd2b2}

到目前为止一切正常,程序以一个参数(顶部的0x1)启动 - 程序的路径(您可以通过(gdb) print (char[10])*(0xffffd284) 看到它)。

  1. 迈出一步(gdb) si,现在我们跳转到message 符号。

(gdb) disas可以看到更多代码:

(gdb) disas
Dump of assembler code for function message:
=> 0x08048083 <+0>: call   0x8048072 <main>
   0x08048088 <+5>: dec    %eax
   0x08048089 <+6>: gs
   0x0804808a <+7>: insb   (%dx),%es:(%edi)
   0x0804808b <+8>: insb   (%dx),%es:(%edi)
   0x0804808c <+9>: outsl  %ds:(%esi),(%dx)
   0x0804808d <+10>:    and    %ecx,(%edx)
End of assembler dump.

如您所见,您的“Hello”字符串被解释为以0x08048088 开头的操作

  1. 再做一步:(gdb) si,进入函数main

现在看看堆栈:

 1: /x {unsigned int[5]}$sp = {0x8048088, 0x1, 0xffffd283, 0x0, 0xffffd29c}

call-指令将返回地址压入堆栈 - 0x08048088 - 您的字符串的地址。好技巧,让我们希望main 永远不会回来......

  1. 让我们快进到write 的电话:(gdb) si 3 并进入(gdb) si

我们看栈,果然,call把返回地址加到栈中了:

1: /x {unsigned int[5]}$sp = {0x804807b, 0x8048088, 0x7, 0x1, 0xffffd283}
  1. 您的程序希望指向字符串的指针和长度位于顶部,但事实并非如此。
  2. 让我们快进到函数write的返回:(gdb) si 7

看看堆栈:

1: /x {unsigned int[5]}$sp = {0x7, 0x1, 0xffffd283, 0x0, 0xffffd29c}

下一个操作 - ret 将从堆栈中弹出 0x7 并尝试在地址 0x7 处执行,这会导致段错误(正如 rcd 所怀疑的那样)。

所以你的问题是你的函数破坏了堆栈。通常,为调用设置堆栈的函数也负责事后清理它。

【讨论】:

  • 谢谢。但是,将其编译为二进制文件并在修复主帖评论者提供的一些内容后运行它不会崩溃。它不能作为 shell 代码工作,但我认为这可能是我将它作为 shell 代码运行的方式的问题。
  • @Fluffy 很难说,你的问题可能是什么 - 但我希望你可以使用调试器来找到问题。这比询问某人需要更多时间 - 但从长远来看,它会得到回报。
猜你喜欢
  • 1970-01-01
  • 2014-10-21
  • 2018-10-10
  • 1970-01-01
  • 2019-02-19
  • 2014-10-03
  • 1970-01-01
  • 2023-04-03
  • 2016-07-30
相关资源
最近更新 更多