2016_9月_吭哧咔哧

九月份开学,到现在为止一个月,总结一下最近这一个月都做了一些什么。话说,在10月都过了三分之一的时候写这个是有一些尴尬。。。

现在做的这个东西希望监测未知程序的网络行为即其收发的信息,ip地址和端口。

首先从网上找了一下Client端和Server端,简单改了改,主要代码如下:

Server端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <WINSOCK2.H>
#include <stdio.h>
#include <iostream>
#pragma comment(lib,"ws2_32.lib")

int main()
{
WORD myVersionRequest;
WSADATA wsaData;
myVersionRequest = MAKEWORD(1,1);
int err;
err = WSAStartup(myVersionRequest,&wsaData);

if(!err)
{
printf("Server success.\n");
}
else
{
printf("Server failed.\n");
return 0;
}
SOCKET serSocket = socket(AF_INET,SOCK_STREAM,0);

//需要绑定的参数
SOCKADDR_IN addr;
addr.sin_family = AF_INET;
addr.sin_addr.S_un.S_addr=htonl(INADDR_ANY);//ip地址
addr.sin_port=htons(6000);//绑定端口

bind(serSocket,(SOCKADDR*)&addr,sizeof(SOCKADDR));//绑定完成
listen(serSocket,5);//其中第二个参数代表能够接收的最多的连接数

SOCKADDR_IN clientsocket;
int len = sizeof(SOCKADDR);
while (1)
{
SOCKET serConn = accept(serSocket,(SOCKADDR*)&clientsocket,&len);
char sendBuf[100];

sprintf(sendBuf,"welcome %s to bejing",inet_ntoa(clientsocket.sin_addr));//找对对应的IP并且将这行字打印到那里
send(serConn,sendBuf,strlen(sendBuf)+1,0);
char receiveBuf[100];//接收
recv(serConn,receiveBuf,strlen(receiveBuf)+1,0);
printf("%s\n",receiveBuf);
closesocket(serConn);//关闭
}
WSACleanup();//释放资源的操作
return 0;
}

Client端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <WINSOCK2.H>
#include <stdio.h>
#pragma comment(lib,"ws2_32.lib")

int main()
{
int err;
WORD versionRequired;
WSADATA wsaData;
versionRequired = MAKEWORD(1,1);
err = WSAStartup(versionRequired,&wsaData);//协议库的版本信息
if (!err)
{
printf("Client success\n");
}
else
{
printf("Client failed\n");
return 0;
}

SOCKET clientSocket = socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN clientsock_in;
clientsock_in.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
clientsock_in.sin_family = AF_INET;
clientsock_in.sin_port = htons(6000);

connect(clientSocket,(SOCKADDR*)&clientsock_in,sizeof(SOCKADDR));

char receiveBuf[100];
recv(clientSocket,receiveBuf,101,0);

printf("%s\n",receiveBuf);

send(clientSocket,"hello,this is client",strlen("hello,this is client")+1,0);

closesocket(clientSocket);
WSACleanup();
return 0;
}

之后上网查找资料得知,程序进行网络行为是通过调用Ring0层下的NtDeviceIoControlFile函数,关于这个函数的信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
NTSTATUS ZwDeviceIoControlFile
(
_In_ HANDLE FileHandle,
_In_opt_ HANDLE Event,
_In_opt_ PIO_APC_ROUTINE ApcRoutine,
_In_opt_ PVOID ApcContext,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_In_ ULONG IoControlCode,//行为类型,这个变量的值决定了InputBuffer和OutputBuffer的格式和长度
_In_opt_ PVOID InputBuffer,
_In_ ULONG InputBufferLength,
_Out_opt_ PVOID OutputBuffer,
_In_ ULONG OutputBufferLength
);

参数主要在第5个(从0算起)IoControlCode,分为很多行为:

1
2
3
4
5
6
7
8
9
10
/*IoControlCode*/
#define IO_AFD_BIND 0x12003
#define IO_AFD_CONNECT 0x12007
#define IO_AFD_SET_CONTEXT 0x12047
#define IO_AFD_RECV 0x12017
#define IO_AFD_SEND 0x1201f
#define IO_AFD_SELECT 0x12024
#define IO_AFD_SENDTO 0x12023
#define IO_AFD_RECVFROM 0x1201B
#define IO_AFD_GET_INFO 0x1207B

然后通过判断IO_AFD_RECV与IO_AFD_SEND就可以在未知程序执行时对其进行插桩,顺利地得到了它们收发的socket。其中RECV的socket是在函数执行之后的OutputBuffer里,SEND的socket在函数进入时的InputBuffer里。主要代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if(this->Device_io_con == IO_AFD_SEND)
{
cout << "\tEnter_IO_AFD_SEND" << endl;

PAFD_INFO input_buffer = (PAFD_INFO)PIN_GetSyscallArgument(ctx, std, 6);
WINDOWS::ULONG input_buffer_len = (WINDOWS::ULONG)PIN_GetSyscallArgument(ctx,std,7);

if(input_buffer)
{
cout << "\tinput_buffer_length: " << input_buffer_len << endl;
if(input_buffer->BufferArray)
{
cout << "\tSend: " << input_buffer->BufferArray->buf << endl;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
if(val->IoControlCode == IO_AFD_RECV)
{
cout << "\texit_IO_AFD_RECV" << endl;
PAFD_INFO output_buffer = (PAFD_INFO)(val->OutputBuffer);
if(output_buffer)
{
if(output_buffer->BufferArray)
{
cout << "\tRecv: " << output_buffer->BufferArray->buf << endl;
}
}
}

使用Pin进行二进制插桩之后的运行结果如下:

image

到这里我都不禁为我强劲的学习能力鼓掌了,太TM牛cha了。之后找ip和端口很明显了嘛,就在bind或者connect里面喽,结果。。。令我痛不欲生的事情就接踵而至了。

msdn给的信息极其有限,根据程序调用函数的日志中可以知晓,在程序的一个执行区间中多次调用NtDeviceIoControlFile,并且之前的行为与之后的行为都没有太多关联,所以ip与端口的信息只可能存在于NtDeviceIoControlFile里面。

image

然后判断行为得知,信息应该存在于IO_AFD_BIND,IO_AFD_SET_CONNECT,IO_AFD_CONNECT之中。

之前我对AFD这个东西是完全不了解的,直到发现了这篇blog,了解到AFD这个东西是存在于socket接口与TDI接口令之间的结构,本质上是一个TDI的客户端,在底层通过调用TDI接口来实现网络部件的TCP/IP功能。当然这篇文章是讲通过Hook对流量进行控制的,与我要做的没什么关系。

好,现在发现BIND和CONNECT并没有合适的struct。。。在谷歌上搜索得到的信息寥寥,在论坛上发现了一篇这个。哇,这个就是AFD_BIND的struct?然后又在msdn上找到了TRANSPORT_ADDRESS,最后又是这里面的TA_ADDRESS,根据下面的说明将Address输出,发现结果是乱码。。。

就在这时在论坛上找到了关于TDI过滤获取IP及端口的帖子,借鉴了一下取ip和端口的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
PTDI_ADDRESS_IP TDI_data ;
typedef struct _NETWORK_ADDRESS
{
unsigned char address [4];
unsigned char port [2];
} NETWORK_ADDRESS ;

NETWORK_ADDRESS data ;
unsigned short Port =0;
unsigned long Address =0;
StackIrpPointer=IoGetCurrentIrpStackLocation(Irp);
....
//这里判断如果StackIrpPointer->MinorFunction == TDI_CONNECT的话就输出IP及端口信息
....
TDI_connectRequest =( PTDI_REQUEST_KERNEL_CONNECT )(& StackIrpPointer -> Parameters );
TA_Address_data =( ( PTRANSPORT_ADDRESS )( TDI_connectRequest -> RequestConnectionInformation ->RemoteAddress ))-> Address ;
TDI_data = ( PTDI_ADDRESS_IP ) ( TA_Address_data -> Address );

Address = TDI_data -> in_addr ;
Port = TDI_data -> sin_port ;

data . address [0] = (( char *)& Address )[0];
data . address [1] = (( char *)& Address )[1];
data . address [2] = (( char *)& Address )[2];
data . address [3] = (( char *)& Address )[3];

data . port [0] = (( char *)& Port )[0];
data . port [1] = (( char *)& Port )[1];
Port = data . port [0] + data . port [1];

我个人是对这个代码有一处不懂的,data中的port如果是char类型的最后相加那一定在254以内啊,最终的port结果输出也确实是很小的数字,这么样的取法得到了ip和端口,但结果不对。

再次搜,找到了这篇帖子,上面说的是使用ntohs将网络字节顺序转换为主机字节顺序,昨天看到的时候特别兴奋,觉得这个肯定可以,结果输出的还是不对= =。

另外后面,还找到了一个afd手册,又像是发现了一个宝藏,里面包含了好多AFD的定义。根据这些我更加确信自己所使用的方法应该是正确的呀,然后也确实抓到了结果,但结果怎么试验都不正确= =。

写到这里发现写得好少啊,第一个月这么过去了。马上要开题了,不知道菜如自己能否毕业。。。接下来的明天要学习DECAF,QEMU,一直叫嚣着要看要看一直没怎么看。每次在列计划的时候都充满希望,信心满满,仿佛一定可以背着好多行囊踏过终点,但每一次真到了终点的时候捧起的往往是失望的残骸,但虽说如此,自己还是很乐观的,在下一个跑道上依旧斗志盎然。这个月看了<<快乐的linux>>,发觉这本书真是好看啊。。。在玩codingame。九月份的大部分时间又是喂了狗,无论怎样,好的坏的,都是珍馐。十月继续战个痛。