【老劉談算法003】命令行參數的處理和獲取——ArgCl函數實現分析
阿新 • • 發佈:2018-08-23
連續 文件中 local get mod 中文註釋 bit 長度 有效 在非匯編語言中,處理並分割命令行參數(CmdLine)一般是由編譯器在可執行文件中預置處理代碼或者調用運行時庫完成,
而在匯編中,我們需要手動調用Windows的API——GetCommandLine函數來獲得一份雜亂的字符串(包含了可執行文件路徑以及命令行參數),
還好在MASM32Library中,匯編愛好者們提供了一個取得命令行參數的函數,省去了我們親自造輪子的功夫,
對於各種不按套路出牌的CmdLine,該函數將如何處理?這激起了我分析這段代碼的興趣。
當然,下面所列舉的算法及出現特殊情況時的處置辦法,可能與其他語言的處理方式不同,僅供參考。
算法流程如下:
代碼如下:
而在匯編中,我們需要手動調用Windows的API——GetCommandLine函數來獲得一份雜亂的字符串(包含了可執行文件路徑以及命令行參數),
還好在MASM32Library中,匯編愛好者們提供了一個取得命令行參數的函數,省去了我們親自造輪子的功夫,
對於各種不按套路出牌的CmdLine,該函數將如何處理?這激起了我分析這段代碼的興趣。
當然,下面所列舉的算法及出現特殊情況時的處置辦法,可能與其他語言的處理方式不同,僅供參考。
算法流程如下:
代碼如下:
;原出處:MASM32 SDK ;註釋修改&中文註釋添加 By 老劉 ; ######################################################################### ; ; 這個程序是在Iczelion和Lucifer的技術支持下開發出來的。 ; ; ######################################################################### .386 .model flat, stdcall ; 32 bit memory model option casemap :none ; case sensitive include \MASM32\INCLUDE\kernel32.inc ; ------------------------------------ ; 請讀文本最後的用法 ; ------------------------------------ ArgCl PROTO :DWORD,:DWORD .code ; ######################################################################### ArgCl proc ArgNum:DWORD, ItemBuffer:DWORD LOCAL cmdLine :DWORD LOCAL cmdBuffer[192] :BYTE LOCAL tmpBuffer[192] :BYTE ; -------------- ; 儲存 esi & edi ; -------------- push esi push edi invoke GetCommandLine mov cmdLine, eax ; address command line cmp ArgNum, 0 jne @F xor eax, eax jmp jmp_In @@: mov esi, cmdLine lodsb ;al=byte ptr [esi] cmp al, 34 je @F ;如果不是半角雙引號,退出 pop edi pop esi xor eax, eax ; eax=0 ret @@: ; ------------------------------------------------- ; 統計引號標記來確認其是否雙雙匹配 ; ------------------------------------------------- xor ecx, ecx ; 將ecx作為引號個數的計數器 mov esi, cmdLine @@: lodsb cmp al, 0 ;到達末尾 je @F cmp al, 34 jne @B ;不為引號則繼續循環 inc ecx ;ecx+=1 jmp @B @@: push ecx ;儲存計數的值 shr ecx, 1 shl ecx, 1 ;ecx-=ecx%2 pop eax ;eax=計數的值 cmp eax, ecx je @F ;引號數量為偶數(匹配)則下跳 pop edi pop esi mov eax, 3 ;返回3 ret @@: ; ------------------------------------------- ; 下面的代碼移除(程序的)路徑及文件名 ; cmdline只留下參數 ; ------------------------------------------- mov esi, cmdLine ;esi=cmdline指針 lea edi, tmpBuffer ;edi=tmpBuffer指針 lodsb ;讀第一個引號 @@: ;去除字符直到讀到第二個引號 lodsb cmp al, 34 jne @B wtIn: lodsb cmp al, 0 je wtOut stosb ;轉移到tmpBuffer jmp wtIn wtOut: stosb ;tmpBuffer結尾 ; ----------------------------------- ; 處理空參數""(其實就是""->255) ; ----------------------------------- lea esi, tmpBuffer lea edi, cmdBuffer xor edx, edx ;清0,作為標誌寄存器使用 rnsSt: lodsb cmp al, 0 je rnsEnd ;esi指向null,即處理完畢 .if al != 34 ;該字符不為引號 .if edx == 1 ;前一字符為引號 xor edx, edx ;edx=0 jmp rnsWrt .endif .elseif al == 34 ;該字符為引號 .if edx == 1 ;前一字符為引號 mov al, 255 stosb ;edi指向地址=255,edi++,其實就是引號換255 mov al, 34 ;再寫入一個引號 stosb dec edx ;edx=0 jmp rnsSt .elseif edx == 0 inc edx ;標記 .endif .endif rnsWrt: stosb jmp rnsSt rnsEnd: stosb ;cmdBuffer結尾 ; ---------------------------------- ; 替換有引號包裹的空格(引號中的空格->254) ; 刪除引號(上面""換成的255不會被刪除) ; ---------------------------------- lea esi, cmdBuffer ;下面操作的是同一個地址,但由於先讀後寫,並不沖突。 lea edi, cmdBuffer subSt: lodsb cmp al, 0 jne @F jmp subOut ;完成 @@: cmp al, 34 jne subNxt jmp subSl ;如果有引號,進入子循環 subNxt: stosb ;寫回去 jmp subSt ; -------------------------- subSl: lodsb cmp al, 32 ;是空格 jne @F mov al, 254 ;空格換254 @@: cmp al, 34 jne @F jmp subSt ;子循環完畢,返回 @@: stosb ;寫回去 jmp subSl ; -------------------------- subOut: stosb ;結尾 ; ------------------------ ;Tab-->空格 ; ------------------------ lea esi, cmdBuffer lea edi, cmdBuffer @@: lodsb cmp al, 0 je rtOut cmp al, 9 jne rtIn ;不為Tab跳 mov al, 32 ;9-->32 rtIn: stosb jmp @B rtOut: stosb ; ---------------------------------------------------- ; 下面的代碼分割正確的arg, ; 並且將arg寫入目的地址 ; ---------------------------------------------------- lea eax, cmdBuffer mov esi, eax mov edi, ItemBuffer ; 目標地址 mov ecx, 1 ; 做計數器 ; --------------------------- ; 去掉先導空格(如果有的話) ; --------------------------- @@: lodsb cmp al, 32 je @B l2St: cmp ecx, ArgNum ; 傳入的ArgNum je clSubLp2 ;到達需要取參的位置 lodsb cmp al, 0 je cl2Out cmp al, 32 jne cl2Ovr ; if not space @@: lodsb cmp al, 32 ; 捕捉連續的空格 je @B inc ecx ;arg計數器++ cmp al, 0 je cl2Out cl2Ovr: jmp l2St clSubLp2: stosb ;參數第一個字符寫進去 @@: lodsb cmp al, 32 je cl2Out cmp al, 0 je cl2Out stosb jmp @B cl2Out: mov al, 0 stosb ; --------------------------------- ; 空格換回去 ; --------------------------------- mov esi, ItemBuffer mov edi, ItemBuffer @@: lodsb cmp al, 0 je @F cmp al, 254 jne nxt1 mov al, 32 nxt1: stosb jmp @B @@: ; ------------------------------------------------- ; 替換“”為0 ; ------------------------------------------------- mov esi, ItemBuffer mov edi, ItemBuffer lodsb cmp al, 255 ;見108+行 jne @F mov al, 0 ;替換為0 stosb mov eax, 4 ;返回4 pop edi pop esi ret @@: ; ---------------------------------- ; 如果參數數量不夠, ; ItemBuffer首位設0 ; ---------------------------------- .if ecx < ArgNum jmp_In: mov edi, ItemBuffer mov al, 0 stosb ;mov byte ptr [edi],al mov eax, 2 ;返回值為2說明參數數量不夠或ArgNum為0 jmp @F .endif mov eax, 1 ;cmdline成功處理 @@: pop edi pop esi ret ArgCl endp ; ######################################################################### ; ; 這個子程序接收2個參數 ; ; 1. 你要獲取的命令行參數的序號。 ; 2. 結果的緩存區地址。 ; ; Example: 如果有4個參數放置於CmdLine中, ; 如progname arg1 arg2 arg3 arg4 , 使用如下手段調用該函數, ; ; invoke ArgCl,3,ADDR buffer ; ; 將會將arg3放入buffer。 ; ; 註意,引用的Arg支持長文件名。 ; ; 確保緩存區的大小足夠接收參數, ; cmdline最大只能有128字節長(現在為32kb(非CMD中傳參),該代碼的編寫日期為1999年), ; 在一個函數中,使用 ; ; LOCAL Buffer[128]:BYTE ; ; eax中的返回值 ; ; 0 = GetCommandLine返回Null ; 1 = cmdline成功處理 ; 2 = 已經嘗試處理,但是參數數量不夠或ArgNum為0 ; 3 = 引號數目不匹配(不為偶數) ; 4 = 空的引號標記(類似"") ; ; Tab和空格都為界定字符 ; ; ######################################################################### end
漏洞分析
當有效參數長度>128時,或當PE文件路徑後面的參數長>192時,會發生溢出bug。
當傳入參數的字符串內部出現半角雙引號時(如 arg""arg),會觸發"空引號標記"未換回BUG,原因為Line280~Line291作者只判斷了第一個字符是否被標記為"空引號標記"。
作者的腦洞還是不夠大啊(笑)
【老劉談算法003】命令行參數的處理和獲取——ArgCl函數實現分析