【老刘谈算法003】命令行参数的处理和获取——ArgCl函数实现分析
在非汇编语言中,处理并分割命令行参数(CmdLine)一般是由编译器在可执行文件中预置处理代码或者调用运行时库完成,而在汇编中,我们需要手动调用Windows的API——GetCommandLine函数来获得一份杂乱的字符串(包含了可执行文件路径以及命令行参数),
还好在MASM32Library中,汇编爱好者们提供了一个取得命令行参数的函数,省去了我们亲自造轮子的功夫,
对于各种不按套路出牌的CmdLine,该函数将如何处理?这激起了我分析这段代码的兴趣。
当然,下面所列举的算法及出现特殊情况时的处置办法,可能与其他语言的处理方式不同,仅供参考。
算法流程如下(流程图绘制有bug,所以下面的一些文字可能有些怪异):
https://attach.52pojie.cn/forum/201808/23/202933rebzct13tte3ieub.png
代码如下:
;原出处: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 :BYTE
LOCAL tmpBuffer :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
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 ,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:BYTE
;
; eax中的返回值
;
; 0 = GetCommandLine返回Null
; 1 = cmdline成功处理
; 2 = 已经尝试处理,但是参数数量不够或ArgNum为0
; 3 = 引号数目不匹配(不为偶数)
; 4 = 空的引号标记(类似"")
;
; Tab和空格都为界定字符
;
; #########################################################################
end
## 漏洞分析 ##
当有效参数长度>128时,或当PE文件路径后面的参数长>192时,会发生溢出bug。
当传入参数的字符串内部出现半角双引号时(如 arg""arg),会触发"空引号标记"未换回BUG,原因为Line280~Line291作者只判断了第一个字符是否被标记为"空引号标记"。
作者的脑洞还是不够大啊(笑)
页:
[1]