|
马上注册,结交更多好友,享用更多功能^_^
您需要 登录 才可以下载或查看,没有账号?立即注册
x
在非汇编语言中,处理并分割命令行参数(CmdLine)一般是由编译器在可执行文件中预置处理代码或者调用运行时库完成,
而在汇编中,我们需要手动调用Windows的API——GetCommandLine函数来获得一份杂乱的字符串(包含了可执行文件路径以及命令行参数),
还好在MASM32Library中,汇编爱好者们提供了一个取得命令行参数的函数,省去了我们亲自造轮子的功夫,
对于各种不按套路出牌的CmdLine,该函数将如何处理?这激起了我分析这段代码的兴趣。
当然,下面所列举的算法及出现特殊情况时的处置办法,可能与其他语言的处理方式不同,仅供参考。
算法流程如下(流程图绘制有bug,所以下面的一些文字可能有些怪异):
代码如下:
- ;原出处: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作者只判断了第一个字符是否被标记为"空引号标记"。
作者的脑洞还是不够大啊(笑) |
|