ShellCode 通常是指一个原始的可执行代码的有效载荷,ShellCode 这个名字来源于攻击者通常会使用这段代码来获得被攻陷系统上的交互 Shell 的访问权限,而现在通常用于描述一段自包含的独立的可执行代码片段。

ShellCode 通常会与漏洞利用并肩使用,或是被恶意代码用于执行进程代码的注入,漏洞利用等,通常情况下 ShellCode 代码无法独立运行,必须依赖于父进程或是 Windows 文件加载器才能够被运行,本章将通过一个简单的弹窗(MessageBox)来实现 ShellCode 有效载荷的提取。

在编写ShellCode之前,我们需要查找一个函数地址,这里要调用 MessageBox() 这个API函数,所以说首先需要获取该函数的地址,这个函数默认放在了 User32.dll 库中,你可以通过编写一个小程序来获取:

如下代码如果在VC6.0环境下是可以正常编译通过的,但如果在VS2010之后则需要修改字符集

VS2013版本:需要修改 解决方案 -> 属性 -> 配置属性 -> 常规 -> 字符集(使用多字节字符集)

#include <windows.h>
#include <iostream>
typedef void(*MYPROC)(LPTSTR); int main()
{
HINSTANCE LibAddr;
MYPROC ProcAddr;
LibAddr = LoadLibrary("user32"); // 获取User.dll基地址
printf("动态库基地址 = 0x%x\n", LibAddr);
ProcAddr = (MYPROC)GetProcAddress(LibAddr, "MessageBoxA"); // 获取MessageBox基地址
printf("函数相对地址 = 0x%x", ProcAddr);
getchar();
return 0;
}

上方的代码经过编译运行以后会得到两个返回结果,由结果可知,User32.dll 的基地址是 0x76320000 而 MessageBox 在当前系统中的地址为 0x763a1f70 ,当然这两个地址在不同版本的Windows系统中,应该是不同的,所以我这里的地址和你的应该也是不同的。

在获取到 MessageBox 的内存地址以后,我们接着需要获取一个 ExitProecess() 函数的地址,这个API函数的作用是让程序正常退出,这是因为我们注入代码以后,原始的堆栈地址会被破坏,堆栈失衡后会导致程序崩溃,所以为了稳妥起见我们还是添加一行正常退出为好。

ExitProcess 函数位于 Kernel32.dll 这个动态链接库里面,我们只需要对上方的获取代码稍加修改,然后编译运行就可得到相应的结果,这里我获取到的地址是 0x75874b80


既然获取到了相应的内存地址,那么接下来就需要通过汇编来编写可执行代码片段了,在编写这段代码之前,先来了解一下汇编语言的调用约定,在汇编语言中,要想调用某个函数,需要使用CALL语句,而在CALL语句的后面,要跟上该函数在系统中的地址,前面我们已经获取到了相应的内存地址了,所以在这里就可以通过CALL相应的地址来调用相应的函数。

在实际的编程中,一般还是先将地址赋给eax寄存器,然后再CALL相应的寄存器实现调用,比如现在有一个函数 lyshark(a,b,c,d),我们想调用它,那么它的汇编代码就应该编写为:

    push d
push c
push b
push a
mov eax,AddressOflyshark // 获取偏移地址
call eax

根据上方的调用方式,我们可以在VS中利用内联汇编来调用ExitProcess() 这个函数了。

    xor ebx, ebx
push ebx
mov eax, 0x75874b80
call eax

接着编写 MessageBox() 这个函数调用。与上个函数不同的是,这个API函数包含有四个参数,当然第一和第四个参数,我们可以赋给0值,但是中间两个参数都包含有较长的字符串,这个该如何解决呢?我们不妨先把所需要用到的字符串转换为ASCII码值,转换的网站这里推荐:https://www.sojson.com/ascii.html

MsgBox标题:alert -->  \x61\x6c\x65\x72\x74\x21
MsgBox内容:hello lyshark --> \x68\x65\x6c\x6c\x6f\x20\x6c\x79\x73\x68\x61\x72\x6b

由于我们使用的是32位汇编,所以上方的字符串需要做一定的处理,我们分别将每四个字符为一组,进行分组,将不满四个字符的,以空格(x20)进行填充:

alert
-------------------------------------------------------------
\x61\x6c\x65\x72
\x74\x21\x20\x20 hello lyshark
-------------------------------------------------------------
\x68\x65\x6c\x6c
\x6f\x20\x6c\x79
\x73\x68\x61\x72
\x6b\x20\x20\x20

上方的空位置之所以需要以 x20 进行填充,而不是 x00 进行填充,是因为 strcpy 这个字符串拷贝函数,默认只要一遇到 x00 就会认为我们的字符串结束了,就不会再拷贝x00后的内容了,所以这里就不能使用 x00 进行填充了,这里要特别留意一下。

接着我们需要将这两段字符串分别压入堆栈存储,这里需要注意,由于我们的计算机是小端序排列的,因此字符的入栈顺序是从后往前不断进栈的,上面的字符串压栈参数应该写为:

alert
-------------------------------------------------------------
push 0x20202174
push 0x72656c61 hello lyshark
-------------------------------------------------------------
push 0x2020206b
push 0x72616873
push 0x796c206f
push 0x6c6c6568

那么下面问题来了,我们如何获取这两个字符串的地址,从而让其成为MessageBox()的参数呢?其实这个问题也不难,我们可以利用esp指针,因为它始终指向的是栈顶的位置,我们将字符压栈后,栈顶位置就是我们所压入的字符的位置,于是在每次字符压栈后,可以加入如下指令:

    xor ebx,ebx                    // 清空寄存器
push 0x20202174 // 字符串 alert
push 0x72656c61
mov eax,esp // 获取第一个字符串的地址 push ebx // 压入00,为了将两个字符串分开。 push 0x2020206b // 字符串 hello lyshark
push 0x72616873
push 0x796c206f
push 0x6c6c6568
mov ecx,esp // 获取第二个字符串的地址

上方代码完成压栈以后,接下来我们就可以调用MessageBox函数了,其调用代码如下。

push ebx                               // push 0
push eax // push "alert"
push ecx // push "hello lyshark !"
push ebx // push 0
mov eax,0x763a1f70 // 将MessageBox地址赋值给EAX
call eax // 调用 MessageBox

最终代码如下

int main()
{
_asm{
sub esp, 0x50 // 抬高栈顶,防止冲突
xor ebx, ebx // 清空ebx
push ebx
push 0x20202174
push 0x72656c61 // 字符串 "alert"
mov eax, esp // 获取栈顶
push ebx // 填充00 截断字符串 push 0x2020206b
push 0x72616873
push 0x796c206f
push 0x6c6c6568 // 字符串 hello lyshark
mov ecx, esp // 获取第二个字符串的地址 push ebx
push eax
push ecx
push ebx
mov eax, 0x763a1f70 // 获取MessageBox地址
call eax // call MessageBox push ebx
mov eax, 0x75874b80 // 获取ExitProcess地址
call eax // call ExitProcess
}
return 0;
}

我们把上方的代码编译下,然后在程序的“_asm”位置先下一个【F9】断点,然后按【F5】启动调试,接着按下【alt+8】就能够查看所转换出来的机器码。

001613CE 83 EC 50             sub         esp,50h
001613D1 33 DB xor ebx,ebx
001613D3 53 push ebx
001613D4 68 74 21 20 20 push 20202174h
001613D9 68 61 6C 65 72 push 72656C61h
001613DE 8B C4 mov eax,esp
001613E0 53 push ebx
001613E1 68 6B 20 20 20 push 2020206Bh
001613E6 68 73 68 61 72 push 72616873h
001613EB 68 6F 20 6C 79 push 796C206Fh
001613F0 68 68 65 6C 6C push 6C6C6568h
001613F5 8B CC mov ecx,esp
001613F7 53 push ebx
001613F8 50 push eax
001613F9 51 push ecx
001613FA 53 push ebx
001613FB B8 70 1F 3A 76 mov eax,763A1F70h
00161400 FF D0 call eax
00161402 53 push ebx
00161403 B8 80 4B 87 75 mov eax,75874B80h
00161408 FF D0 call eax

我们直接将上方的这些机器码提取出来,从而编写出完整的ShellCode,最终测试代码如下。

#include <windows.h>
#include <stdio.h>
#include <string.h>
#pragma comment(linker,"/section:.data,RWE") unsigned char shellcode[] = "\x83\xec\x50"
"\x33\xdb"
"\x53"
"\x68\x74\x21\x20\x20"
"\x68\x61\x6c\x65\x72"
"\x8b\xc4"
"\x53"
"\x68\x6b\x20\x20\x20"
"\x68\x73\x68\x61\x72"
"\x68\x6f\x20\x6c\x79"
"\x68\x68\x65\x6c\x6c"
"\x8b\xcc"
"\x53"
"\x50"
"\x51"
"\x53"
"\xb8\x70\x1f\x3a\x76"
"\xff\xd0"
"\x53"
"\xb8\x80\x4b\x87\x75"
"\xff\xd0"; int main(int argc,char **argv)
{
LoadLibrary("user32.dll");
__asm{
lea eax,shellcode
call eax
}
return 0;
}

上方代码经过编译以后,运行会弹出一个我们自己DIY的MessageBox提示框。

编写并提取简易 ShellCode的更多相关文章

  1. 编写并提取通用 ShellCode

    简易 ShellCode 虽然可以正常被执行,但是还存在很多的问题,因为上次所编写的 ShellCode 采用了硬编址的方式来调用相应API函数的,那么就会存在一个很大的缺陷,如果操作系统的版本不统一 ...

  2. matlab逐行读取text文件,编写函数提取需要的文字

    在数学建模中遇到的数据比较难处理,而且给的是text格式,自己想了好长时间才编出来,现在分享一下,可以交流学习 目标的text文件是 只提取里面的数据 需要自编函数 clc,clear path='D ...

  3. Linux下shellcode的编写

    Linux下shellcode的编写 来源  https://xz.aliyun.com/t/2052 EdvisonV / 2018-02-14 22:00:42 / 浏览数 6638 技术文章 技 ...

  4. 缓冲区溢出分析第04课:ShellCode的编写

    前言 ShellCode究竟是什么呢,其实它就是一些编译好的机器码,将这些机器码作为数据输入,然后通过我们之前所讲的方式来执行ShellCode,这就是缓冲区溢出利用的基本原理.那么下面我们就来编写S ...

  5. 编写X86的ShellCode

    ShellCode 定义 ShellCode是不依赖环境,放到任何地方都能够执行的机器码 编写ShellCode的方式有两种,分别是用编程语言编写或者用ShellCode生成器自动生成 ShellCo ...

  6. 汇编Shellcode的技巧

    汇编Shellcode的技巧 来源  https://www.4hou.com/technology/3893.html 本文参考来源于pentest 我们在上一篇提到要要自定义shellcode,不 ...

  7. java版简易socket客户端

    android项目需要使用到心跳, 于是编写了一个简易的socket客户端程序 主要功能是给服务端发送心跳包,保持在线状态 没有使用框架,这样避免了需要引入包,直接使用的阻塞Socket通信. 主要逻 ...

  8. jmeter正则表达式提取器提取特定字符串后的全部内容

    jmeter进行JDBC请求时,请求后的响应结果在传递给下一个请求使用时,需要用到关联,也在jmeter中,关联通过正则表达式提取器实现. 但是,在JDBC请求后的响应结果中,往往需要关联的内容是只有 ...

  9. 如何使用Hive&R从Hadoop集群中提取数据进行分析

    一个简单的例子! 环境:CentOS6.5 Hadoop集群.Hive.R.RHive,具体安装及调试方法见博客内文档. 1.分析题目 --有一个用户数据样本(表名huserinfo)10万数据左右: ...

随机推荐

  1. UVALive 3716 DNA Regions ——(扫描法)

    乍一看这个问题似乎是很复杂,但其实很好解决. 先处理出每个点到原点的距离和到x正半轴的角度(从x正半轴逆时针旋转的角度).然后以后者进行排序. 枚举每一个点到圆心的距离,作为半径,并找出其他到圆心距离 ...

  2. Linux ubuntu centos 下 grep显示前后几行信息

    标准unix/linux下的grep通过下面参数控制上下文 grep -C 5 foo file 显示file文件里匹配foo字串那行以及上下5行grep -B 5 foo file 显示foo及前5 ...

  3. Build Telemetry for Distributed Services之OpenTracing简介

    官网地址:https://opentracing.io/ What is Distributed Tracing? Who Uses Distributed Tracing? What is Open ...

  4. iOS 11适配

    1.http://www.cocoachina.com/ios/20170915/20580.html   简书App适配iOS 11   2.http://www.jianshu.com/p/efb ...

  5. error_reporting函数引起的error_log配置失效的问题

    由于项目代码中大量使用了error_reporting(0);导致php.ini中的error_log失效,不记录错误日志, 导致调试起来非常不便,耗费大量的时间,所以在php.ini的配置中禁止掉e ...

  6. Spark源码(1): SparkConf

    1. 简介 SparkConf类负责管理Spark的所有配置项.在我们使用Spark的过程中,经常需要灵活配置各种参数,来使程序更好.更快地运行,因此也必然要与SparkConf类频繁打交道.了解它的 ...

  7. Run Hyper-V and VirtualBox on the same machine (轉載)

    Run Hyper-V and VirtualBox on the same machine Posted on September 5, 2012 by derek gusoff Recently ...

  8. Thingsboard源码编译,小白新坑随笔

    在Thingsboard源码编译过程中,遇见的问题总结: 1.Thingsboard Server UI 模块编译,无法执行“npm install ”:出现错误信息:Failed to execut ...

  9. C/C++编程

    基本的数据类型: 整型 浮点型(单精度.双精度) 在c语言中,所有的变量声明必须在任何执行语句之前(对当前域来说), 否则编译的时候会出现变量是未声明的标识符的错误. main 入口参数:argc 和 ...

  10. Linux selinux 防火墙

    cat /etc/selinux/config # This file controls the state of SELinux on the system. # SELINUX= can take ...