?? 對一個用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 + -