1. 程式人生 > >【老劉談算法003】命令行參數的處理和獲取——ArgCl函數實現分析

【老劉談算法003】命令行參數的處理和獲取——ArgCl函數實現分析

連續 文件中 local get mod 中文註釋 bit 長度 有效

在非匯編語言中,處理並分割命令行參數(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函數實現分析