注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

熊猫正正的博客

熊猫正正的天空

 
 
 

日志

 
 

对一个用Win32Asm编写的TCP端口扫描程序的分析  

2012-03-25 20:08:15|  分类: Win32汇编学习 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
  • 标 题: 对一个用Win32Asm编写的TCP端口扫描程序的分析
  • 作 者:jhkdiy
  • 时 间:2006-03-25 00:35 
  • 链 接:http://bbs.pediy.com/showthread.php?threadid=23110

    作者:jhkdiy
    邮件:jhkdiy_gzb@21cn.net
    论坛:www.20cn.net
    日期:06年3月24日

        有一段时间没有去过ASMCommunity Messageboard(http://www.asmcommunity.net/board/index.php)了,以前直接可以访问。
    现在只能通过代理才能访问,至少我这边是这样。在里面随便逛了逛,无意看到一位论坛会员问怎样用Asm来做一个端口扫描器,
    结果其他会员给了一个源代码,在window的控制台窗口下运行,以阻塞模式扫描一个目标主机的端口。我觉得这个代码对刚学习
    Windows Socket编程的朋友来说很有启发性,所以现在详细讲解一下这份代码,希望对大家有所帮助。若各位读者想先试试该程序
    可以先用RadAsm建立一个console工程,然后将下面的代码粘贴到asm文件中,编译&链接。在控制台下运行该程序即可。
    源代码如下,只做了一点的格式编排:

    代码:   
    ; Conscan - CLI "test" .386  .model flat, stdcall  option casemap :none  include windows.inc  include user32.inc  include kernel32.inc  include masm32.inc include wsock32.inc includelib user32.lib  includelib kernel32.lib  includelib masm32.lib includelib wsock32.lib  print macro lpszText:VARARG  local txt  .data   txt db lpszText,13,10,0  .code   invoke StdOut,addr txt ENDM  SOCKADDR_IN struct  sin_family WORD ?  sin_port WORD ?  sin_addr DWORD ?  sin_zero BYTE 8 dup (?) SOCKADDR_IN ends  .data  szusg db "usage: conscan <hostname>",13,10,0  fmt   db "%d OPEN",13,10,0  sa    SOCKADDR_IN <>  wsa   WSADATA <>  sfd   dd 0  port  dd 0  pl    dd 21        dd 22        dd 23        dd 25        dd 80        dd 137        dd 350        dd 8080        dd 6667        dd 31337        dd 0       .data?  hostname db 2024 dup (?)  buffer   db 100 dup (?)   .code conscan:  call main  call ExitProcess  main proc     invoke GetCL,1,addr hostname     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  



    整份代码才100行左右,那么,这短短的100行代码到底做了些什么呢?首先请将代码看一遍,
    如果是似明非明的话就需要继续看下面的解释了。我们就一段段来分析吧,首先是第一部分:
    .386 
    .model flat, stdcall 
    option casemap :none

    include windows.inc 
    include user32.inc 
    include kernel32.inc 
    include masm32.inc
    include wsock32.inc
    includelib user32.lib 
    includelib kernel32.lib 
    includelib masm32.lib
    includelib wsock32.lib

    开头的部分相信任何一位学win32asm的朋友都能看懂,include 和 includelib的部分确实有需要
    留意的部分,那就是:
    include wsock32.inc
    includelib wsock32.lib

    编写windows socket程序就要包含相应的windows socket头文件和库文件,当前可以使用的winsock
    接口动态链接库版本有1.1版本和2.0版本。wsock32.inc & wsock32.lib 文件表示使用32位的
    1.1版本的winsock,如果是使用2.0的winsock就需要使用WS2_32.inc 和 WS2_32.lib 文件。如果
    对masm32.inc也不熟悉的话,在这里简单的说说,它是一系列常用的宏和函数的集合,主要是为了
    编程方便,减少重复编写同样的代码而做的,包含了许多实用的宏和函数,在这个代码中StdOut等函数
    就使用了这个文件。

    再往下看,看到了一个宏:
    print macro lpszText:VARARG
     local txt
     .data
      txt db lpszText,13,10,0
     .code
      invoke StdOut,addr txt
    ENDM
    该宏即使用了masm32.inc中的StdOut函数,该函数在windows标准控制台下输出一段文本,有点像C语言下的
    printf()函数。紧跟着看到一段结构体定义:
    SOCKADDR_IN struct
         sin_family WORD ?
         sin_port WORD ?
         sin_addr DWORD ?
         sin_zero BYTE 8 dup (?)
    SOCKADDR_IN ends

    该结构用来将套接字绑定到IP地址和端口使用,当然在后续的接受和发送等等的工作中都会用到。各字段注释
    如下:

    SOCKADDR_IN struct
         sin_family WORD ?              ;地址格式
         sin_port WORD ?                ;端口号(使用网络字节顺序)
         sin_addr DWORD ?               ;IP地址(使用网络字节顺序)
         sin_zero BYTE 8 dup (?)        ;空字节
    SOCKADDR_IN ends

    sin_family在不同的操作系统下有不同的值,但winsock环境一般都固定为 AF_INET,也可以是PF_INET,在windows.inc
    中被定义为与AF_INET同样的值。sin_port 和 sin_addr 字段分别指定端口号和IP地址,放入这两个字段的数据字节
    顺序必须是Internet顺序。这到底是怎么一回事呢?学过汇编语言的朋友都知道,Intel的CPU对内存操作数使用的是小尾
    顺序,这种方式是将位数高的字节排在内存地址高的位置,而位数低的字节排在内存的低地址处,例如 12345678h ,
    如果以字类型来存储,在内存表示为 78h 56h 34h 12h。如果以双字来存储,表示为5678h 1234h。而其它的CPU如Motorola
    的CPU使用的是大尾顺序,这种顺序刚好相反,低位数排在内存的高位,高位数排在内存的低位,例如 12345678h表示为
    12h 34h 56h 78h,这种方式比较适合我们一般的看书习惯,都是从左到右一路看过去。
    本来这些东东不关我们事的,但是当两种不同CPU的主机通讯时就不行了,而Internet协议是一组开放的协议,需要在不同的
    计算机平台之间进行通信,所以在实现时不能包含与特定平台有关的东西,即做到平台无关性。这就需要采用一种共同的数据
    排列顺序才行,而Internet协议采用了大尾顺序,所以凡是80X86平台的winsock编程,当使用IP地址和端口号等参数时,都需要
    将它们转换为大尾顺序。

    好了,到了数据定义的内容了,我们看看都定义了什么:
    .data
     szusg db "usage: conscan <hostname>",13,10,0       ;使用帮助
     fmt   db "%d OPEN",13,10,0                         ;输出时显示打开的端口
     sa    SOCKADDR_IN <>                               ;使用connect函数时用到
     wsa   WSADATA <>                                   ;使用WSAStartup函数时用到
     sfd   dd 0                                         ;保存Socket句柄
     port  dd 0                                         ;保存要扫描的端口
     pl    dd 21                                        ;这是程序将要扫描的端口列表
           dd 22
           dd 23
           dd 25
           dd 80
           dd 137
           dd 350
           dd 8080
           dd 6667
           dd 31337
           dd 0
         
    .data?
     hostname db 2024 dup (?)           ;用于存贮域名
     buffer   db 100 dup (?)            

    在.data段中,值得注意的是 定义了 一个 SOCKADDR_IN 类型的 sa变量,还有一个就是 WSADATA型的 wsa变量
    WSADATA是用来初始化winsock库的,在使用winsock函数之前必须先使用WSAStartup函数来装入并初始化动态连接
    库WS2_32.dll。例如在代码段中的     invoke WSAStartup,101h,addr wsa 即表示使用1.1版本的socket并初始化。
    在端口列表pl方面,这种直接定义在程序内部的方式缺少了灵活性,如果能将常扫描的端口存入一个文件里,需要
    扫描的时候才读入端口列表来扫描就好多了,注意,最后一个0是不能缺少的,实际上它表示端口列表的终止,而并
    不参加扫描,这种思想和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

  评论这张
 
阅读(187)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017