【pwn.college】ShellCode课程视频记录和分析

shellcoding tips and tricks

以下逐句记录该课程视频的内容

You've managed to write some assembly. Maybe you've even gotten some shellcode running inside of some of the Pone College challenges. In this video, I'm gonna take a look at various techniques you can use to both build your shellcode and debug it to hopefully show you a better way to develop shellcode than what you're currently doing.

这节课的目的是介绍构建和调试 shellcode 时可以使用的各种技巧,帮助你改进当前开发 shellcode 的方式,让你能更高效地编写、测试和调试 shellcode。


start from kind of the simplest techniques and then move to kind of more advanced ones. And hopefully along the way, you find something that you didn't know you could do.

我们会从最简单的技术开始,然后逐渐过渡到更高级一些的技术。希望在这个过程中,你能发现一些你原本不知道自己也能做到的事情。

 

So the kind of first thing that's worth talking about is just the most basic thing that we could possibly do. And that is by calling all of the commands necessary to go from an assembly file to shell code that we could pass into a binary.

那么,首先值得讨论的,就是我们能做的最基本的事情,那就是:调用所有必要的命令,把一个汇编文件变成可以传给二进制程序的 shellcode,并得到能正常工作的 shellcode。

image

课程会由浅入深,先讲最基础的 shellcode 构建流程——从汇编文件出发,通过一系列命令生成可注入、可传给目标二进制程序并且能运行的 shellcode。

 

image

Over here on the right hand side, I'm going to edit a file. We're going to call it shellcode.s. We're going to start with some kind of generic boilerplate here. We're going to say Intel syntax, no prefix. We're going to say global, not global, global underscore start. We're then going to have our start label. It's going to be a very simple binary that we're going to build or simple shellcode. What we're going to do is we're going to call exits. We're going to say move RAX 60. See if I can type. Move RAX 60. We're going to move RDI 1337. And then we're going to perform a syscall. In case you're unaware, 60 is the syscall number for performing an exit syscall. The the syscall number goes in RAX. RDI is the register for the first argument to a syscall. And then the syscall happens by executing the syscall instruction. So these three instructions will execute syscall with the first argument being 1337, which should call exit and show us 1337 on, or at least an interpretation of 1337.

在右侧这里,我要编辑一个文件。我们把它叫做 shellcode。我们先写一些通用模板代码。我们会写 Intel 语法、no prefix,也就是 .intel_syntax noprefix。我们会写 global _start。然后定义 _start 标签。我们要构建的是一个非常简单的二进制程序,或者说简单的 shellcode。我们要做的是调用 exit。我们会写 mov rax, 60。看看我能不能打出来。mov rax, 60。我们会写 mov rdi, 1337。然后执行 syscall。以防你不知道,60 是执行 exit 系统调用的系统调用号。系统调用号放在 RAX 中。RDI 是系统调用第一个参数所在的寄存器。然后通过执行 syscall 指令来触发系统调用。所以这三条指令会执行系统调用,第一个参数是 1337,这应该会调用 exit,并在退出时显示 1337,或者至少是对 1337 的一种解释。

 

image

 So now that we have that, we've written shellcode.s, we're going to assemble it into an object file. We do that with AS. We're going to specify our output is going to be shellcode.o. .o is just a convention. We could really call it whatever we want. And the file that we are using as input is shellcode.s. If we take a look at shellcode.o, what we see here is this is an L file, but it doesn't say it's executable. And if we take a look at the permissions of shellcode.o, it doesn't have the executable bit set. Now, one thing you might think at face value is, all right, well, let's just make this thing executable, right? I'm smarter than the computer. And if we do that, yeah, this has the executable bit set. But when we try and execute it, we get an error. And that's because this isn't an executable elf, right? We tried to execute it, but it doesn't have the executable format. It's not an executable elf. And so we don't actually get anything done. Now, we can make it an executable elf by linking it. We do that with the LD command. So we can say LD-O. We're going to call it shellcode-elf. That is what our output's going to be. And what we want to link is shellcode.o. We take a quick look here at what is shellcode-elf. This is, in fact, an elf file, but now it is an executable elf file. What this means is that I can run it. Well, that is a pretty exciting executable file, right? All it does is call exit. But how do we know that it's actually calling exit with 1337?

现在我们已经有了这些,我们写好了 shellcode.s,接下来要把它汇编成一个目标文件。我们用 as 来完成这件事。我们指定输出文件为 shellcode.o。.o 只是一个约定俗成的扩展名。其实我们想叫它什么都可以。而我们用作输入的文件是 shellcode.s。如果我们查看 shellcode.o,会看到它是一个 ELF 文件,但它并没有说自己可执行(LSB后面是reloactable,而不是下面几行出现的executable)。如果我们查看 shellcode.o 的权限,会发现它没有设置可执行位。现在,你表面上可能会想:好吧,那我们直接把它变成可执行的不就行了吗?我比计算机聪明。如果我们这么做,是的,它确实有了可执行位。但当我们尝试执行它时,会得到一个错误。这是因为这并不是一个可执行的 ELF 文件,对吧?我们尝试执行它,但它并不具备可执行格式。它不是可执行的 ELF。所以我们实际上什么也做不了。现在,我们可以通过链接把它变成一个可执行的 ELF。我们用 ld 命令来完成。我们可以写 ld -o。我们把输出叫做 shellcode-elf。这就是我们的输出文件。而我们要链接的是 shellcode.o。我们快速看一下 shellcode-elf 是什么。它确实是一个 ELF 文件,但现在它是一个可执行的 ELF 文件。这意味着我可以运行它。哇,这真是个令人兴奋的可执行文件,对吧?它所做的只是调用 exit。但我们怎么知道它实际上是用 1337 来调用 exit 呢?

 

image

How do I debug the assembly and make sure this is doing what I think right now at this stage if this is how I'm going to try and build my shellcode? What I would probably want to do is I'd want to run strace. Strace is a command that shows us the syscalls that are executed inside of the program that we specify. And so if I run strace on shellcode-elf, what we see here is shellcode-elf is executed. It then calls exit with 1337 and the program exits. Not very exciting, but it is doing what I think it should be doing.

 如果这就是我尝试构建 shellcode 的方式,那么在这个阶段,我该如何调试汇编,并确保它正在做我认为它该做的事?我可能想做的事情是运行 stracestrace 是一个命令,它向我们展示我们指定的程序内部所执行的系统调用。所以如果我对 shellcode-elf 运行 strace,我们在这里看到的是 shellcode-elf 被执行。然后它用 1337 调用 exit,程序退出。虽然不太令人兴奋,但它确实在做我认为它应该做的事。

对命令输出的解释:

  • execve(...) 表示程序被加载执行。

  • exit(1337) 表示程序执行了退出系统调用,参数是 1337

  • +++ exited with 57 +++ 是因为 Linux 退出码只保留低 8 位:1337 & 0xff = 57

 

image

 Well, I still haven't made shellcode. What I've done is I've gone from my assembly file to an object file to an ELF that's executable. I still haven't actually made shellcode. I need to pull out the text section from this ELF because an ELF contains a whole lot more than just the executable instructions that we care about. So this shellcode-ELF is 4,696 bytes. I guarantee you most of that is not these three assembly instructions. If I want to just pull out those assembly instructions, what I can do is I can use object copy. We can say object copy, dump section. dump section. The section I'm interested in is the text section. That's where the executable instructions are located. I want to copy the text section into a file which we will call shellcode. and we're going to pull the text section out of shellcode-elf. Now if we take a look at shellcode, it thinks it's zlib compressed data. It turns out that these are just raw bytes. And so the file command isn't going to be able to know what the heck this thing is. Right? So how do I take a look at my shellcode? Well, I could try and open it up in like Vim, but this looks like a whole bunch of, that's a giant mess. That doesn't mean anything to me. So I'm going to try and open it up. So I'm going to try and understand the bytes that are in my shellcode. I need to use something like OD. And this shows me the values of my shellcode, but that is also not a very useful format. I probably want to look at this in hexadecimal. And so we could use something like hexadump. And now this is a same interpretation, this is a sane interpretation of the bytes that are my shellcode. 

不过,我仍然还没有制作出 shellcode。我所做的是从汇编文件到目标文件,再到一个可执行的 ELF。我实际上还没有制作出 shellcode。我需要从这个 ELF 中提取出 text 段,因为 ELF 包含的内容远不止我们关心的可执行指令。所以这个 shellcode-ELF 有 4,696 字节。我保证其中大部分都不是这三条汇编指令。如果我只想提取出这些汇编指令,我可以用 objcopy。我们可以写 objcopy --dump-section。我感兴趣的段是 text 段。可执行指令就在那里。我想把 text 段复制到一个文件中,我们把它叫做 shellcode。然后我们从 shellcode-elf 中提取 text 段。现在如果我们看一下 shellcodefile 命令会认为它是 zlib 压缩数据。事实证明这些只是原始字节。所以 file 命令无法知道这到底是什么东西,对吧?那么我该如何查看我的 shellcode 呢?我可以尝试用 Vim 打开它,但看起来就是一大堆乱七八糟的东西。对我来说没有任何意义。所以我要尝试打开它。我要尝试理解我的 shellcode 中的字节。我需要使用类似 od 的东西。这会显示我的 shellcode 的值,但格式也不太有用。我可能想用十六进制查看它。所以我们可以用类似 hexdump 的东西。现在这就是一种合理的解释了,这是对我的 shellcode 字节的一种合理呈现。

对命令行输出的解释:

  • file shellcode 可能误判为 zlib 压缩数据,因为原始机器码字节碰巧符合某种特征。

  • od 默认以八进制显示,不太直观。

  • hexdump -C 或 xxd 以十六进制加 ASCII 显示,更适合查看 shellcode 字节。

 

image

And we see here that my shellcode, this is 16, because this is a hexadecimal counting from here to here. We can confirm that by looking at shellcode with LS. We see our shellcode is actually 16 bytes, and these are those 16 bytes, or at least the hexadecimal representation. And so these 16 machine code bytes correspond to these three assembly instructions.

我们在这里看到,我的 shellcode 是 16 字节,因为这是从这儿到那儿的十六进制计数。我们可以用 ls 查看 shellcode 来确认这一点。我们看到我们的 shellcode 实际上是 16 字节,这些就是那 16 个字节,或者至少是它们的十六进制表示。所以这 16 个机器码字节对应这三条汇编指令。

image

But we saw that that took many commands. And so if I, for instance, didn't want to have null bytes in my shell code, well, I see that there are null bytes here. So maybe I need to go over here and change some instructions. But then for me to get back to checking this, I have to run AS again. I have to run LD again. I have to run object copy again. And then I have to run hex dump again. That's a whole lot of commands I need to execute to get information from what I'm working on to what I care about. You can certainly do that, but it would be a pretty inefficient workflow. So we can go one step improved and we can call GCC directly. GCC is commonly referred to as the compiler. It's actually a collection of compiler tools. And so if we call GCC with these arguments here, it is functionally going to be the same thing as if I call AS and LD. We can just do it in one go. So let's remove our shellcode-elf and let's try and create shellcode-elf using GCC. 

但我们看到,那需要很多条命令。所以,比如说,如果我不想让 shellcode 里出现空字节,那么我看到这里确实有空字节。所以也许我需要到这里来,修改一些指令。但这样一来,为了重新检查它,我又得再运行一次 as。我又得再运行一次 ld。我又得再运行一次 objcopy。然后我又得再运行一次 hexdump。为了从我正在处理的东西中得到我真正关心的信息,我需要执行一大堆命令。你当然可以这么做,但这会是一种相当低效的工作流程。所以我们可以改进一步,直接调用 gccgcc 通常被称为编译器。它实际上是一组编译器工具的集合。所以如果我们用这里这些参数调用 gcc,它在功能上就等同于我调用 as 和 ld。我们可以一步完成。所以让我们删掉 shellcode-elf,然后试着用 gcc 来创建 shellcode-elf

 

image

 let's try and create shellcode -elf using GCC. So we'll say GCC, no standard lib, static, my output is going to be shellcode -elf, my input is shellcode -s. Take a look again. Shellcode dash elf is an executable elf. We can run it. If we strace it, it behaves as expected. It's calling exit 1337. I can call that same object copy command to pull the shellcode out, and we would end up with pretty much the exact same thing. Now, that is still a whole lot of work. Because we're doing all of this at the command line level, we're running all of these commands. You could write a shell script to automate some of it, but it's still not a very quick workflow.

让我们试着用 GCC 来创建 shellcode-elf。所以我们会写:gcc -nostdlib -static -o shellcode-elf shellcode.s。输出是 shellcode-elf,输入是 shellcode.s。再来看一下。shellcode-elf 是一个可执行的 ELF 文件。我们可以运行它。如果我们用 strace 跟踪它,它的行为符合预期。它调用了 exit(1337)。我可以调用同样的 objcopy 命令把 shellcode 提取出来,最终得到的东西基本完全一样。不过,这仍然是相当多的工作。因为我们是在命令行层面做所有这些事情,要运行所有这些命令。你可以写一个 shell 脚本来自动化其中一部分,但这仍然不是一个很快的工作流程。

 

image

 What I would encourage people to do is starting at this third option here, and that is use Python and PwnTools to do a lot of the heavy lifting for you.

我鼓励大家从这个第三个选项开始,那就是使用 Python 和 pwntools 来帮你完成大量繁重的工作。

 

 

 

 

 

 

 

 

posted on 2026-09-17 10:20  d0ubleU0x00  阅读(4)  评论(0)    收藏  举报

导航