📄 对一个用win32asm编写的tcp端口扫描程序的分析.txt
字号:
不参加扫描,这种思想和C语言的字符串结构一样,不错。
好了,现在看看最重要的代码段了,看看程序到底在做什么了:
代码:
.code
conscan:
call main ;主函数,全部流程都在这里了。
call ExitProcess ;正常终止程序必须的。
main proc
invoke GetCL,1,addr hostname ;GetCL是masm32.inc中函数
cmp eax,1
jne Arg_Error
mov sa.sin_family, AF_INET
lea edi,pl
Port_Scan_Loop:
mov eax,[edi]
cmp eax,0
je Port_Scan_Complete
inc edi
mov port,eax
invoke WSAStartup,101h,addr wsa
invoke socket, AF_INET, SOCK_STREAM, 0
mov sfd,eax
invoke htons, port
mov sa.sin_port, ax
invoke gethostbyname, addr hostname
mov eax,[eax+12]
mov eax,[eax]
mov eax,[eax]
mov sa.sin_addr,eax
invoke connect,sfd,addr sa,SIZEOF sa
cmp eax, 0
jne Port_Closed
invoke wsprintf,addr buffer,addr fmt,port
invoke StdOut,addr buffer
Port_Closed:
invoke closesocket,sfd
call WSACleanup
jmp Port_Scan_Loop
Arg_Error:
invoke StdOut,addr szusg
ret
Port_Scan_Complete:
print "-- Scan Complete --"
ret
main endp
end conscan
整个代码段定义了一个main函数,从 end conscan来看程序的入口点就是main函数的第一条指令了,先看看
invoke GetCL,1,addr hostname
cmp eax,1
jne Arg_Error
。。。。。
。。。。。
Arg_Error:
invoke StdOut,addr szusg
ret
第一个GetCL函数的功能是获取命令行中的第一个参数,要注意的是第0个参数就是程序名本身,这一点和C语言
中的程序参数一样。如果函数成功获取参数,则将参数字符串填充到hostname参数中,这个参数至少要有128字节
的空间。并在eax中返回1表示执行成功,详细介绍请看masm32\help\masm32lib.hlp 。整段代码就是获取第一个
参数,如果不成功则转到Arg_Error:中执行StdOut函数,输出程序的使用帮助。
现在最重要的来了:
代码:
mov sa.sin_family, AF_INET
lea edi,pl
Port_Scan_Loop:
mov eax,[edi]
cmp eax,0
je Port_Scan_Complete
inc edi
mov port,eax
invoke WSAStartup,101h,addr wsa
invoke socket, AF_INET, SOCK_STREAM, 0
mov sfd,eax
invoke htons, port
mov sa.sin_port, ax
invoke gethostbyname, addr hostname
mov eax,[eax+12]
mov eax,[eax]
mov eax,[eax]
mov sa.sin_addr,eax
invoke connect,sfd,addr sa,SIZEOF sa
cmp eax, 0
jne Port_Closed
invoke wsprintf,addr buffer,addr fmt,port
invoke StdOut,addr buffer
Port_Closed:
invoke closesocket,sfd
call WSACleanup
jmp Port_Scan_Loop
Arg_Error:
invoke StdOut,addr szusg
ret
Port_Scan_Complete:
print "-- Scan Complete --"
ret
mov sa.sin_family, AF_INET
lea edi,pl
首先填充 sa变量,sin_family表示使用的协议族,这个没的说,一般都是AF_INET。第二句是将pl数组的首地址
给edi,代表edi指向要扫描的端口列表。
Port_Scan_Loop:
mov eax,[edi]
cmp eax,0
je Port_Scan_Complete
inc edi
mov port,eax
。。。。。
。。。。。
Port_Scan_Complete:
print "-- Scan Complete --"
ret
扫描循环开始了,首先将第一个要扫描的端口给eax,立即比较eax是否为0,如果为0则表示所有要扫描的端口都
扫描过了,是结束扫描的时候了,如果真为0则跳到Port_Scan_Complete:下打印 "-- Scan Complete --" 信息后
就退出整个程序。如果不为0则将edi加1,以便指向下一个要扫描的端口。然后将端口号存入port变量中。
1: invoke WSAStartup,101h,addr wsa
2: invoke socket, AF_INET, SOCK_STREAM, 0
3: mov sfd,eax
4: invoke htons, port
5: mov sa.sin_port, ax
6: invoke gethostbyname, addr hostname
7: mov eax,[eax+12]
8: mov eax,[eax]
9: mov eax,[eax]
10: mov sa.sin_addr,eax
第一行用来初始化动态链接库,101h表明使用1.1的winsock。第二行建立一个新的套接字,协议为AF_INET,方式为
SOCK_STREAM,即为流套接字,这种套接字使用在TCP协议中,如果是使用UDP协议则应该用SOCK_DGRAM。该函数的第
三个参数为0,这个参数是表示需要使用的协议类型,与第二个参数配合使用,因为SOCK_STREAM已经表示使用TCP协议
了,所以这里设置为0。同样SOCK_DGRAM表示使用UDP协议,也可将该参数设置为0。socket函数成功执行后返回一个
socket句柄,以后要发送或接受数据都要靠它作为身份表示了。第三行即将socket句柄保存到sfd中。
第四行htons函数是将port变量的内容由小尾顺序转换为Internet上使用的大尾顺序。第五行即将返回的端口填入
sa的sin_port字段中,第六行使用gethostbyname函数将域名转换为IP地址,该函数返回指向位于winsock内部缓冲区
中的一个hostent结构指针。结构体如下:
hostent STRUCT
h_name dword ? ;指针,指向和IP地址对应的主机名
h_alias dword ? ;指针,指向一个包含别名指针的列表
h_addr word ? ;返回的IP地址类型
h_len word ? ;每个地址的长度
h_list dword ? ;指向一个指针列表,列表中保存指向各个IP地址的指针
hostent ENDS
现在再看看代码,第六行执行完gethostbyname后返回一个指针,第七行的[eax+12]表示指向hostent结构体的h_list字段
然后作为地址寻找h_list指向的内容,执行完第八行后,eax即指向了一个指针列表,这个列表里的指针指向由gethostbyname
那里得到的各个IP地址,因为一个域名对应多个IP地址是允许的。第九行又将eax作为地址,指向完第九行后eax里已经是
实际返回的IP地址了。这时得到的IP地址已经是大尾顺序,可以直接使用而不需要转换。所以第十行代码直接将IP地址
填入sa的sin_addr字段。
代码:
可以表示为: (hostent结构) (IP地址指针列表) (IP地址)
gethostbyname的返回值----> h_name | |
h_alias | |
h_addr | |
h_len | |
h_list -----> IP地址1的指针 -----> IP地址1
IP地址2的指针 -----> IP地址2
IP地址3的指针 -----> IP地址3
..... -----> IP地址n
最后来看看这段代码:
代码:
1: invoke connect,sfd,addr sa,SIZEOF sa
2: cmp eax, 0
3: jne Port_Closed
4: invoke wsprintf,addr buffer,addr fmt,port
5: invoke StdOut,addr buffer
6: Port_Closed:
7: invoke closesocket,sfd
8: call WSACleanup
9: jmp Port_Scan_Loop
第一行用connect与sfd指定的套接字建立连接,而目标IP和端口在sa中指定。如果eax返回值为0表示目标IP的这个
端口关闭了,跳到第七行,用closesocket函数关闭一个socket,然后用WSACleanup释放winsock库,这代表不再使用
winsock函数了。如果eax返回值不为0表示成功和目标IP的指定端口建立了连接,这时4 5行的代码即在标准控制台窗口
输出该端口已打开的信息。而第9行的代码又重新跳到开始处继续下一个端口的扫描,如此循环直到端口列表中端口号
为0。
好了,整段代码解释完了,好像看似100多行的代码却隐含了如此多的知识,确实,直接用winsock函数编程属于较底层
的编程了,最后来总结一下整个程序的流程吧:
1: 先获取一个需要扫描的端口
2: 如果端口号为0则跳到第11步
3: 如果端口号不为0则做下一步
4: 用WSAStartup初始化winsock库
5: 用socket函数建立一个套接字
6: 填充 SOCKADDR_IN 结构,用来指定套接字使用的IP地址&端口号
7: 用connect函数来连接一个套接字,真正连接到目标IP地址的指定端口
8: 如果成功连接则输出端口已打开的信息
9: 如果不成功则关闭当前这个socket和释放winsock库
10: 跳到第1步骤继续执行
11: 结束程序
整个程序分析完了,但还要说几句话,实际上这个扫描程序的功能很有限,只能扫描一个IP的几个常用端口,而且是
在控制台下,扫描之前也没有对这个IP地址进行Ping探测。有兴趣的朋友可以深入到这方面,要是能做到像SuperScan
那样强大就好了。另一个方面是这份代码没有任何注释,大家都知道汇编代码很难阅读,而注释和格式编排是唯一可以
增加可读性的方法了,而这份代码这两样都没有做好,也不知道这位外国朋友本身的技术如何。总之我是不提倡这种
编程风格的,希望各位学winasm的朋友不要学这种方式。
如果这篇文章有诉说错误或不恰当的地方请各位朋友批评指正。
--jhkdiy
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -