BLOG main image
분류 전체보기 (17)
Life (2)
Dump Analysis (9)
Reversing (1)
Windows (1)
Book (2)
Reference (2)
Visitors up to today!
Today hit, Yesterday hit
daisy rss
tistory 티스토리 가입하기!
2020. 1. 23. 00:00

윈도우에서 제공하는 매우 강력하고 골치 아픈 쉘인 파워쉘(powershell.exe) 관련 기능 개발 중에 발견한 이슈다.

공격하는 애들이 커맨드라인만을 이용한 Fileless 파워쉘 공격을 워낙 즐겨 쓰다보니 최근에 커널 드라이버에 파워쉘 탐지 기능을 많이 넣었다.
대단한 건 아니고 악의적인 파워쉘은 커맨드라인을 난독화하고 그걸 다시 인코딩한 형태로 주로 실행되는데 런타임에 커널에서 디코딩해서 난독화를 제거하는 단순한 기능이다.
근데 악성 샘플 몇 만개를 돌려보면 가끔 한 두개 씩 커맨드라인 디코딩에 실패하는 일이 발생해서 어쩔 수 없이 분석했다ㅠ

결론부터 말하면 cmd.exe의 배치 파일(bat) 파싱 버그로 커맨드라인이 한 글자가 잘려 실행되는 것이 원인이었다.
정확히는 배치 파일 안에 실행 경로를 제외한 나머지 커맨드라인의 길이가 8,191자 이상인 경우 8,191번째 글자가 누락되는 이슈다.

cmd.exe에서 커맨드라인은 XP 이상 O/S에서 8,191자(XP 미만 2,147자) 이상 지정할 수 없다.
하지만, 배치 파일을 인자로 지정한 경우 파일 내용을 읽어 대신 CreateProcess를 실행해준다.
따라서 CreateProcess의 lpCommandLine 인자의 최대 크기인 32,767자까지 배치 파일을 통해 실행시킬 수 있다.
(즉, cmd.exe에 의해 실행된 프로세스의 커맨드라인은 8K 이상 길이가 될 수 있다)

이 문제는 cmd.exe에서 CreateProcess의 커맨드라인 인자 최대 크기인 32,767자를 지원하기 위해 버퍼를 조합하는 과정에서 cmd!Lex 함수의 버그로 발생한다.
cmd.exe는 배치 파일의 커맨드를 8,191 단위로 읽어 조합하는데 이 과정에서 8,191번째마다 마지막 한 글자씩 누락된다.

다음은 내가 탐지 테스트를 하려고 배치 파일로 구성했던 인코딩된 악성 파워쉘 커맨드라인이다.(test_long.bat)

powershell -EncodedCommand JABzAEQAYgAgAD0AIAAnACQAcwB4AFkAQQBmAEEAdAAgAD0AIAAnACcAWwBEAGwAbABJAG ...
HMARABiACkAKQA7AA==

정상적인 경우에는 powershell.exe에서 -EncodedCommand 명령어를 해석해서 뒤에 따라오는 Base64 인코딩 문자열을 디코딩해서 스크립트가 실행된다.
근데 이 커맨드라인 중에 한 글자가 제거된 채로 실행되어 내 탐지 모듈도 디코딩에 실패하고, powershell.exe도 실행에 실패하는 재미난 증상이다.

이쯤에서 윈도우에서 배치 파일을 읽어 실행하는 과정 중에 뻘짓을 했을 것 같은 예감이 들긴 했는데 사람 일은 모르는거라 계속 분석해봤다.

다음은 배치 파일(test_long.bat) 실행시 cmd.exe의 콜 스택이다.
배치 파일을 실행하면 cmd.exe는 cmd!Parser 함수를 통해 커맨드라인 파싱을 진행한다.

kd> !thread
THREAD 855ab3f0 Cid 117c.0df0 Teb: 7ffde000 Win32Thread: fdc862f8 RUNNING on processor 0
Not impersonating
DeviceMap 8dd5dd60
Owning Process 84f9dd40 Image: cmd.exe
Attached Process N/A Image: N/A
Wait Start TickCount 11943303 Ticks: 0
Context Switch Count 47 IdealProcessor: 0
UserTime 00:00:00.031
KernelTime 00:00:00.171
Win32 Start Address cmd!mainCRTStartup (0x4a7f829a)
Stack Init ae8bced0 Current ae8bc4c8 Base ae8bd000 Limit ae8ba000 Call 00000000
Priority 8 BasePriority 8 PriorityDecrement 0 IoPriority 2 PagePriority 5
ChildEBP RetAddr Args to Child
0026f07c 4a7f1aa7 00000084 4a81c640 00002000 cmd!ReadBufFromFile+0x3 (FPO: [Non-Fpo])
0026f0a8 4a7f1e56 4a7f1d73 00000003 4a828640 cmd!FillBuf+0x1c8 (FPO: [Non-Fpo])
0026f0ac 4a7f1d73 00000003 4a828640 00000000 cmd!GetByte+0x11 (FPO: [0,0,0])
0026f0c8 4a7f1ce6 4a828640 00002000 00000008 cmd!Lex+0x75 (FPO: [Non-Fpo])
0026f0e0 4a7f1c8d 00000008 00000000 0026f104 cmd!GeToken+0x27 (FPO: [Non-Fpo])
0026f0f0 4a7f1c18 00000000 003d7d18 00000000 cmd!ParseStatement+0x36 (FPO: [Non-Fpo])
0026f104 4a7f4c6a 00000003 00000003 00000011 cmd!Parser+0x46 (FPO: [Non-Fpo]) *** 커맨드라인 파싱 함수
0026f130 4a7f5718 003d7d18 003d7938 00000104 cmd!BatLoop+0xbf (FPO: [Non-Fpo])
0026f160 4a7f6b85 003d7938 003d79e8 00000104 cmd!BatProc+0x1bb (FPO: [Non-Fpo])
0026f3b8 4a7f3d48 003d7938 00000000 00000000 cmd!ECWork+0xd8 (FPO: [Non-Fpo])
0026f3d0 4a7f15c5 003d7938 5bd5f11f 00000001 cmd!ExtCom+0x47 (FPO: [Non-Fpo])
0026f82c 4a7f22c0 003d7938 003d7938 77658e7f cmd!FindFixAndRun+0x1f7 (FPO: [Non-Fpo])
0026f87c 4a7f7489 00000000 003d7938 4a814204 cmd!Dispatch+0x14b (FPO: [Non-Fpo])
0026f8c0 4a7f835e 00000003 006712b0 00671648 cmd!main+0x11d (FPO: [Non-Fpo])
0026f904 7764ed6c 7ffdf000 0026f950 778e37f5 cmd!_initterm_e+0x163 (FPO: [Non-Fpo])
0026f910 778e37f5 7ffdf000 7ca96ff4 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
0026f950 778e37c8 4a7f829a 7ffdf000 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo])
0026f968 00000000 4a7f829a 7ffdf000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])

 

첫 번째 8K(8,191) 버퍼 읽기

커맨드라인의 인자에 배치 파일이 있으면 파일을 읽어 파일 안의 커맨드를 실행해야 한다.
cmd!ReadBufFromFile 함수에서 배치 파일의 내용을 읽는 동작을 처리한다.
cmd!ReadBufFromFile 함수의 첫 번째 인자로 핸들 값인 0x84를 전달해서 실행되는 상황이다.

kd> !handle 84
PROCESS 84f9dd40 SessionId: 1 Cid: 117c Peb: 7ffdf000 ParentCid: 0868
DirBase: 04fe7260 ObjectTable: 8a097380 HandleCount: 32.
Image: cmd.exe
Handle table at 8a097380 with 32 entries in use
0084: Object: 8624f4d0 GrantedAccess: 00120089 (Inherit) Entry: afa7f108
Object: 8624f4d0 Type: (84ee2970) File
ObjectHeader: 8624f4b8 (new version)
HandleCount: 1 PointerCount: 1
Directory Object: 00000000 Name: \test\test_long.bat {HarddiskVolume1}

kd> ub eip L10
cmd!ReadBufFromFile+0x10:
4a7f4daa 53 			push ebx
4a7f4dab ff7508 		push dword ptr [ebp+8]
4a7f4dae bf4066814a 		mov edi,offset cmd!AnsiBuf (4a816640)
4a7f4db3 8bf7 			mov esi,edi
4a7f4db5 ff1584127f4a 		call dword ptr [cmd!_imp__SetFilePointer (4a7f1284)]
4a7f4dbb 8945f4 		mov dword ptr [ebp-0Ch],eax
4a7f4dbe b8ff1f0000 		mov eax,1FFFh 		// nNumberOfBytesToRead = 8,191
4a7f4dc3 394510 		cmp dword ptr [ebp+10h],eax
4a7f4dc6 73c8 			jae cmd!ReadBufFromFile+0x2e (4a7f4d90)
4a7f4dc8 53 			push ebx
4a7f4dc9 ff7514 		push dword ptr [ebp+14h] 	// lpNumberOfBytesRead
4a7f4dcc c705f840814a01000000 	mov dword ptr [cmd!DoNotCancelIo (4a8140f8)],1
4a7f4dd6 ff7510 		push dword ptr [ebp+10h] 	// nNumberOfBytesToRead
4a7f4dd9 57 			push edi 			// lpBuffer
4a7f4dda ff7508 		push dword ptr [ebp+8] 	// hFile
4a7f4ddd ff15b4127f4a 		call dword ptr [cmd!_imp__ReadFile (4a7f12b4)]

핸들 값 0x84는 test_long.bat 파일이고, cmd!ReadBufFromFile 함수 안에서 ReadFile을 통해 8,191 바이트만큼 읽는 것을 알 수 있다.

다음은 ReadFile 후에 lpBuffer에 읽은 내용을 확인한 것이다.

kd> db edi L1fff // 8,191
4a816640 70 6f 77 65 72 73 68 65-6c 6c 20 2d 45 6e 63 6f powershell -Enco
4a816650 64 65 64 43 6f 6d 6d 61-6e 64 20 4a 41 42 7a 41 dedCommand JABzA
4a816660 45 51 41 59 67 41 67 41-44 30 41 49 41 41 6e 41 EQAYgAgAD0AIAAnA
4a816670 43 51 41 63 77 42 34 41-46 6b 41 51 51 42 6d 41 CQAcwB4AFkAQQBmA
4a816680 45 45 41 64 41 41 67 41-44 30 41 49 41 41 6e 41 EEAdAAgAD0AIAAnA
4a816690 43 63 41 57 77 42 45 41-47 77 41 62 41 42 4a 41 CcAWwBEAGwAbABJA
4a8166a0 47 30 41 63 41 42 76 41-48 49 41 64 41 41 6f 41 G0AcABvAHIAdAAoA
4a8166b0 43 49 41 61 77 42 6c 41-48 49 41 62 67 42 6c 41 CIAawBlAHIAbgBlA
... ...
4a8185e0 48 51 41 63 67 42 70 41-47 34 41 5a 77 41 6f 41 HQAcgBpAG4AZwAoA
4a8185f0 46 73 41 55 77 42 35 41-48 4d 41 64 41 42 6c 41 FsAUwB5AHMAdABlA
4a818600 47 30 41 4c 67 42 55 41-47 55 41 65 41 42 30 41 G0ALgBUAGUAeAB0A
4a818610 43 34 41 52 51 42 75 41-47 4d 41 62 77 42 6b 41 C4ARQBuAGMAbwBkA
4a818620 47 6b 41 62 67 42 6e 41-46 30 41 4f 67 41 36 41 GkAbgBnAF0AOgA6A
4a818630 46 55 41 62 67 42 70 41-47 4d 41 62 77 42 6b FUAbgBpAGMAbwBk

정상적으로 8,191 크기만큼 Ansi 인코딩으로 읽어왔다.

ReadFile 이후에는 MultiByteToWideChar 함수를 통해 UTF-16 인코딩으로 변환한다.

kd> ub eip
cmd!ReadBufFromFile+0xbb:
4a7f4e46 56 		push esi
4a7f4e47 e841fdffff 	call cmd!IsMBTWCConversionTypeFlagsSupported (4a7f4b8d)
4a7f4e4c f7d8 		neg eax
4a7f4e4e 1bc0 		sbb eax,eax
4a7f4e50 f7d8 		neg eax
4a7f4e52 50 		push eax
4a7f4e53 56 		push esi
4a7f4e54 ff15b0127f4a 	call dword ptr [cmd!_imp__MultiByteToWideChar (4a7f12b0)]

kd> db 4a81c640
4a81c640 70 00 6f 00 77 00 65 00-72 00 73 00 68 00 65 00 p.o.w.e.r.s.h.e.
4a81c650 6c 00 6c 00 20 00 2d 00-45 00 6e 00 63 00 6f 00 l.l. .-.E.n.c.o.
4a81c660 64 00 65 00 64 00 43 00-6f 00 6d 00 6d 00 61 00 d.e.d.C.o.m.m.a.
4a81c670 6e 00 64 00 20 00 4a 00-41 00 42 00 7a 00 41 00 n.d. .J.A.B.z.A.
4a81c680 45 00 51 00 41 00 59 00-67 00 41 00 67 00 41 00 E.Q.A.Y.g.A.g.A.
4a81c690 44 00 30 00 41 00 49 00-41 00 41 00 6e 00 41 00 D.0.A.I.A.A.n.A.
4a81c6a0 43 00 51 00 41 00 63 00-77 00 42 00 34 00 41 00 C.Q.A.c.w.B.4.A.
4a81c6b0 46 00 6b 00 41 00 51 00-51 00 42 00 6d 00 41 00 F.k.A.Q.Q.B.m.A.

 

두 번째 8K(8,191) 버퍼 읽기

배치 파일 내의 커맨드가 8K 이상이라면 추가로 파일을 더 읽어서 버퍼를 조합해야 한다.
따라서 추가적으로 한 번 더 파일을 읽는 동작이 발생한다.
다음 콜 스택은 cmd!ParseStatement 함수에서 읽은 버퍼 크기를 판단해서 cmd!ParseS0 함수를 통해 추가 읽기 동작이 발생하는 상황이다.

kd> kv
# ChildEBP RetAddr Args to Child
00 0026efc8 4a7f2825 0026efe8 0026effc 003c0848 cmd!TextCheck+0xd (FPO: [Non-Fpo])
01 0026efec 4a7f1ce6 4a82c62a 00002000 00000041 cmd!Lex+0x1f3 (FPO: [Non-Fpo])
02 0026f004 4a7f2aba 00000001 00004000 00000003 cmd!GeToken+0x27 (FPO: [Non-Fpo])
03 0026f024 4a7f2894 00000003 00004000 00000000 cmd!ParseCmd+0x31 (FPO: [Non-Fpo])
04 0026f040 4a7f2973 00000003 00004000 00000000 cmd!ParseS4+0x2b (FPO: [Non-Fpo])
05 0026f054 4a7f2962 4a7f2728 00000032 4a7f294c cmd!BinaryOperator+0xb (FPO: [Non-Fpo])
06 0026f068 4a7f2973 00000003 00004000 00000000 cmd!ParseS3+0x16 (FPO: [0,0,0])
07 0026f07c 4a7f2a57 4a7f2a58 00000031 4a7f2a41 cmd!BinaryOperator+0xb (FPO: [Non-Fpo])
08 0026f090 4a7f2973 00000003 00004000 00000000 cmd!ParseS2+0x16 (FPO: [0,0,0])
09 0026f0a4 4a7f2a79 4a7f2a7c 00000030 4a7f2a63 cmd!BinaryOperator+0xb (FPO: [Non-Fpo])
0a 0026f0b8 4a7f2973 00000003 00004000 00000000 cmd!ParseS1+0x16 (FPO: [0,0,0])
0b 0026f0cc 4a7f2725 4a7f272c 0000002f 4a7f26ad cmd!BinaryOperator+0xb (FPO: [Non-Fpo])
0c 0026f0e4 4a7f1ca6 00000000 0026f104 4a7f1c18 cmd!ParseS0+0xb5 (FPO: [0,0,4])
0d 0026f0f0 4a7f1c18 00000000 003d7d18 00000000 cmd!ParseStatement+0x58 (FPO: [Non-Fpo])
0e 0026f104 4a7f4c6a 00000003 00000003 00000011 cmd!Parser+0x46 (FPO: [Non-Fpo])
0f 0026f130 4a7f5718 003d7d18 003d7938 00000104 cmd!BatLoop+0xbf (FPO: [Non-Fpo])
10 0026f160 4a7f6b85 003d7938 003d79e8 00000104 cmd!BatProc+0x1bb (FPO: [Non-Fpo])
11 0026f3b8 4a7f3d48 003d7938 00000000 00000000 cmd!ECWork+0xd8 (FPO: [Non-Fpo])
12 0026f3d0 4a7f15c5 003d7938 5bd5f11f 00000001 cmd!ExtCom+0x47 (FPO: [Non-Fpo])
13 0026f82c 4a7f22c0 003d7938 003d7938 77658e7f cmd!FindFixAndRun+0x1f7 (FPO: [Non-Fpo])
14 0026f87c 4a7f7489 00000000 003d7938 4a814204 cmd!Dispatch+0x14b (FPO: [Non-Fpo])
15 0026f8c0 4a7f835e 00000003 006712b0 00671648 cmd!main+0x11d (FPO: [Non-Fpo])
16 0026f904 7764ed6c 7ffdf000 0026f950 778e37f5 cmd!_initterm_e+0x163 (FPO: [Non-Fpo])
17 0026f910 778e37f5 7ffdf000 7ca96ff4 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
18 0026f950 778e37c8 4a7f829a 7ffdf000 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo])
19 0026f968 00000000 4a7f829a 7ffdf000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])

cmd!TextCheck 함수에서 문자 체크 후 cmd!ReadBufFromFile 함수가 다시 호출된다.

이번에는 먼저 읽은 8,191자 이후의 나머지 52자를 읽는 부분이다.

kd> ub eip L10
cmd!ReadBufFromFile+0x10:
4a7f4daa 53 			push ebx
4a7f4dab ff7508 		push dword ptr [ebp+8]
4a7f4dae bf4066814a 		mov edi,offset cmd!AnsiBuf (4a816640)
4a7f4db3 8bf7 			mov esi,edi
4a7f4db5 ff1584127f4a 		call dword ptr [cmd!_imp__SetFilePointer (4a7f1284)]
4a7f4dbb 8945f4 		mov dword ptr [ebp-0Ch],eax
4a7f4dbe b8ff1f0000 		mov eax,1FFFh // nNumberOfBytesToRead = 8,191
4a7f4dc3 394510 		cmp dword ptr [ebp+10h],eax
4a7f4dc6 73c8 			jae cmd!ReadBufFromFile+0x2e (4a7f4d90)
4a7f4dc8 53 			push ebx
4a7f4dc9 ff7514 		push dword ptr [ebp+14h] // lpNumberOfBytesRead
4a7f4dcc c705f840814a01000000 	mov dword ptr [cmd!DoNotCancelIo (4a8140f8)],1
4a7f4dd6 ff7510 		push dword ptr [ebp+10h] // nNumberOfBytesToRead
4a7f4dd9 57 push 		edi // lpBuffer
4a7f4dda ff7508 		push dword ptr [ebp+8] // hFile
4a7f4ddd ff15b4127f4a 		call dword ptr [cmd!_imp__ReadFile (4a7f12b4)]

// 읽은 바이트 수
kd> dd poi(ebp+14) L1
0026efac 00000034 // 52자

kd> db edi L34
                                                                   * 포인트
4a816640 41 47 55 41 4c 67 42 48-41 47 55 41 64 41 42 43 AGUALgBHAGUAdABC
4a816650 41 48 6b 41 64 41 42 6c-41 48 4d 41 4b 41 41 6b AHkAdABlAHMAKAAk
4a816660 41 48 4d 41 52 41 42 69-41 43 6b 41 4b 51 41 37 AHMARABiACkAKQA7
4a816670 41 41 3d 3d AA==

// 이후 MultiByteToWideChar 함수를 통한 UTF-16 인코딩도 성공
kd> db 4a81c640
4a81c640 41 00 47 00 55 00 41 00-4c 00 67 00 42 00 48 00 A.G.U.A.L.g.B.H.
4a81c650 41 00 47 00 55 00 41 00-64 00 41 00 42 00 43 00 A.G.U.A.d.A.B.C.
4a81c660 41 00 48 00 6b 00 41 00-64 00 41 00 42 00 6c 00 A.H.k.A.d.A.B.l.
4a81c670 41 00 48 00 4d 00 41 00-4b 00 41 00 41 00 6b 00 A.H.M.A.K.A.A.k.
4a81c680 41 00 48 00 4d 00 41 00-52 00 41 00 42 00 69 00 A.H.M.A.R.A.B.i.
4a81c690 41 00 43 00 6b 00 41 00-4b 00 51 00 41 00 37 00 A.C.k.A.K.Q.A.7.
4a81c6a0 41 00 41 00 3d 00 3d 00-77 00 42 00 34 00 41 00 A.A.=.=.

Base64 인코딩 부분의 마지막 "==" 부분까지 정상적으로 읽어온다.
최종적으로 UTF-16 인코딩으로 변환도 성공적으로 수행한다.
배치 파일을 읽어오는 과정에는 아무 문제없이 정상적으로 동작한다.

이렇게 읽은 버퍼는 cmd 내부의 Lex 버퍼라는 곳에 8,192 바이트 단위(8,191 + null 1)의 배열로 관리된다.

 

읽은 버퍼 조합

배치 파일의 내용을 버퍼로 나눠서 읽어 들였으므로 읽은 버퍼를 합치는 과정이 필요할 것이다.
cmd!ReadBufFromFile 함수를 통해 읽은 버퍼 내용은 cmd!Lex 함수에서 1글자씩 검증 후 조합하여 최종 버퍼로 생성한다.
cmd!Lex - cmd!TextCheck - cmd!GetByte 함수를 통해 문자 하나를 가져오는 방식이다.
cmd!Lex 함수가 호출되는 콜 스택과 함수 흐름은 다음과 같다.

kd> kbL
# ChildEBP RetAddr Args to Child
00 0024f620 4a7f1ce6 4a82c63e 00002000 00000041 cmd!Lex+0x1d4
01 0024f638 4a7f2aba 00000001 00004000 00000003 cmd!GeToken+0x27
02 0024f658 4a7f2894 00000003 00004000 00000000 cmd!ParseCmd+0x31
03 0024f674 4a7f2973 00000003 00004000 00000000 cmd!ParseS4+0x2b
04 0024f688 4a7f2962 4a7f2728 00000032 4a7f294c cmd!BinaryOperator+0xb
05 0024f69c 4a7f2973 00000003 00004000 00000000 cmd!ParseS3+0x16
06 0024f6b0 4a7f2a57 4a7f2a58 00000031 4a7f2a41 cmd!BinaryOperator+0xb
07 0024f6c4 4a7f2973 00000003 00004000 00000000 cmd!ParseS2+0x16
08 0024f6d8 4a7f2a79 4a7f2a7c 00000030 4a7f2a63 cmd!BinaryOperator+0xb
09 0024f6ec 4a7f2973 00000003 00004000 00000000 cmd!ParseS1+0x16
0a 0024f700 4a7f2725 4a7f272c 0000002f 4a7f26ad cmd!BinaryOperator+0xb
0b 0024f718 4a7f1ca6 00000000 0024f738 4a7f1c18 cmd!ParseS0+0xb5
0c 0024f724 4a7f1c18 00000000 00337d18 00000000 cmd!ParseStatement+0x58
0d 0024f738 4a7f4c6a 00000003 00000003 00000011 cmd!Parser+0x46
0e 0024f764 4a7f5718 00337d18 00337938 00000104 cmd!BatLoop+0xbf
0f 0024f794 4a7f6b85 00337938 003379e8 00000104 cmd!BatProc+0x1bb
10 0024f9ec 4a7f3d48 00337938 00000000 00000000 cmd!ECWork+0xd8
11 0024fa04 4a7f15c5 00337938 fd7f54cb 00000001 cmd!ExtCom+0x47
12 0024fe60 4a7f22c0 00337938 00337938 77658e7f cmd!FindFixAndRun+0x1f7
13 0024feb0 4a7f7489 00000000 00337938 4a814204 cmd!Dispatch+0x14b
14 0024fef4 4a7f835e 00000003 004f12b0 004f1648 cmd!main+0x11d
15 0024ff38 7764ed6c 7ffdd000 0024ff84 778e37f5 cmd!_initterm_e+0x163
16 0024ff44 778e37f5 7ffdd000 7c8a6dc9 00000000 kernel32!BaseThreadInitThunk+0xe
17 0024ff84 778e37c8 4a7f829a 7ffdd000 00000000 ntdll!__RtlUserThreadStart+0x70
18 0024ff9c 00000000 4a7f829a 7ffdd000 00000000 ntdll!_RtlUserThreadStart+0x1b

kd> uf /c cmd!Lex
Flow analysis was incomplete, some code may be missing
cmd!Lex (4a7f1d26)
		... ...
	cmd!Lex+0xba (4a7f1db7):
		call to cmd!TextCheck (4a7f1f90)	// 문자 검증 후 획득
		... ...

kd> uf /c cmd!TextCheck
Flow analysis was incomplete, some code may be missing
cmd!TextCheck (4a7f1f90)
	cmd!TextCheck+0x8 (4a7f1f98):
		call to cmd!GetByte (4a7f1e26)	// 파일에서 읽은 버퍼에서 1글자씩 가져옴

cmd!GetByte 함수에서는 내부에서 관리하는 Lex 버퍼를 통해 문자를 가져온다.

kd> u cmd!GetByte Lc
cmd!GetByte:
4a7f1e26 8b0d9441814a 		mov ecx,dword ptr [cmd!LexBufPtr (4a814194)]
4a7f1e2c 66833900 		cmp word ptr [ecx],0
4a7f1e30 741f 			je cmd!GetByte+0xc (4a7f1e51)
4a7f1e32 6683390d 		cmp word ptr [ecx],0Dh
4a7f1e36 0f84842d0000 		je cmd!GetByte+0x1d (4a7f4bc0)
4a7f1e3c 833d5406834a00 	cmp dword ptr [cmd!ExtCtrlc+0x8 (4a830654)],0
4a7f1e43 7519 			jne cmd!GetByte+0x39 (4a7f1e5e)
4a7f1e45 0fb701 		movzx eax,word ptr [ecx] // 1글자 가져옴
4a7f1e48 41 			inc ecx
4a7f1e49 41 			inc ecx
4a7f1e4a 890d9441814a	 	mov dword ptr [cmd!LexBufPtr (4a814194)],ecx // (cmd!LexBufPtr)++
4a7f1e50 c3			ret

cmd!LexBufPtr은 파일에서 읽은 버퍼를 가리키는 포인터로 문자를 읽을 때마다 2바이트씩(1글자) 포인터가 증가한다.

kd> db 4a828640 // Lex 버퍼 : 파일에서 두 차례 읽은 버퍼(8K 이상이라 2번 읽음)
4a828640 20 00 2d 00 45 00 6e 00-63 00 6f 00 64 00 65 00 .-.E.n.c.o.d.e.
4a828650 64 00 43 00 6f 00 6d 00-6d 00 61 00 6e 00 64 00 d.C.o.m.m.a.n.d.
4a828660 20 00 4a 00 41 00 42 00-7a 00 41 00 45 00 51 00 .J.A.B.z.A.E.Q.
4a828670 41 00 59 00 67 00 41 00-67 00 41 00 44 00 30 00 A.Y.g.A.g.A.D.0.
4a828680 41 00 49 00 41 00 41 00-6e 00 41 00 43 00 51 00 A.I.A.A.n.A.C.Q.
4a828690 41 00 63 00 77 00 42 00-34 00 41 00 46 00 6b 00 A.c.w.B.4.A.F.k.
4a8286a0 41 00 51 00 51 00 42 00-6d 00 41 00 45 00 45 00 A.Q.Q.B.m.A.E.E.
4a8286b0 41 00 64 00 41 00 41 00-67 00 41 00 44 00 30 00 A.d.A.A.g.A.D.0.
... ...

kd> db poi(cmd!LexBufPtr) // Lex 버퍼의 현재 위치
4a82c658 41 00 64 00 41 00 42 00-43 00 41 00 48 00 6b 00 A.d.A.B.C.A.H.k.
4a82c668 41 00 64 00 41 00 42 00-6c 00 41 00 48 00 4d 00 A.d.A.B.l.A.H.M.
4a82c678 41 00 4b 00 41 00 41 00-6b 00 41 00 48 00 4d 00 A.K.A.A.k.A.H.M.
4a82c688 41 00 52 00 41 00 42 00-69 00 41 00 43 00 6b 00 A.R.A.B.i.A.C.k.
4a82c698 41 00 4b 00 51 00 41 00-37 00 41 00 41 00 3d 00 A.K.Q.A.7.A.A.=.
4a82c6a8 3d 00 00 00 42 00 34 00-41 00 46 00 6b 00 41 00 =...B.4.A.F.k.A.
4a82c6b8 51 00 51 00 42 00 6d 00-41 00 45 00 45 00 41 00 Q.Q.B.m.A.E.E.A.
4a82c6c8 64 00 41 00 41 00 67 00-41 00 44 00 30 00 41 00 d.A.A.g.A.D.0.A.

 

읽은 버퍼 조합 - cmd!Lex 함수 반복문

다음은 cmd!TextCheck(GetBuffer) 함수에서 가져 문자 하나를 최종 버퍼에 조합하는 cmd!Lex 함수의 반복문 부분이다.

kd> u 4a7f280c L17
cmd!Lex+0x1c6:
4a7f280c 46 		inc esi
4a7f280d 46 		inc esi
4a7f280e 8bfe		mov edi,esi
4a7f2810 2b7df8 	sub edi,dword ptr [ebp-8]
4a7f2813 d1ff		sar edi,1 // edi == i, i = 1

// begin loop
4a7f2815 8d4510 	lea eax,[ebp+10h]
4a7f2818 50 		push eax
4a7f2819 8d45fc		lea eax,[ebp-4]
4a7f281c 50		push eax
4a7f281d 897508 	mov dword ptr [ebp+8],esi
4a7f2820 e86bf7ffff 	call cmd!TextCheck (4a7f1f90)
4a7f2825 3bc3 		cmp eax,ebx
4a7f2827 7414 		je cmd!Lex+0x1f7 (4a7f283d)
4a7f2829 8b450c 	mov eax,dword ptr [ebp+0Ch]
4a7f282c 48 		dec eax // eax == cchMaxBuffer, cchMaxBuffer - 1
4a7f282d 3bf8 		cmp edi,eax // i < cchMaxBuffer - 1?
4a7f282f 7d0c 		jge cmd!Lex+0x1f7 (4a7f283d) // exit loop
4a7f2831 668b45fc 	mov ax,word ptr [ebp-4]
4a7f2835 668906		mov word ptr [esi],ax
4a7f2838 46 		inc esi
4a7f2839 46 		inc esi
4a7f283a 47 		inc edi // i++

// end loop
4a7f283b ebd8 		jmp cmd!Lex+0x1e3 (4a7f2815)

kd> r eax
eax=00002000 // cchMaxBuffer = 8,192

반복문은 1부터 시작해서 8,190(i < 8192-1)번째까지 수행된다.
루프를 돌며 cmd!TextCheck 함수를 통해 문자를 하나씩 조합하는 단순한 반복문이다.

바로 이 반복문에서 문자 하나가 누락된다!

위의 어셈블리어를 의사 코드로 변환하면 다음과 같다.

cmd!Lex(...)
{
	... ...
	// cchMaxBuffer = 8,192
	for ( i = 1; TextCheck(C, &a3) != 256 && i < cchMaxBuffer - 1; ++i )
	{
		*v9 = C[0];
		++v9;
	}
}

반복문 조건에 의해 i가 8191이 되면 루프를 탈출한다.
반복 횟수는 i가 1부터 시작했으므로 8,191번이 아닌 8,190번까지만 수행하고 탈출한다.
따라서 cmd!TextCheck 함수에서 얻은 8,190개의 문자까지는 정상 조합하지만 8,191번째 문자는 버려진다.
그다음 cmd.exe는 다음 8,192번째 글자부터 문자열을 조합한다.

문제의 반복문 마지막 인덱스에서 버려진 문자를 확인해보자.

kd> r edi
edi=00001fff // 8,191

kd> dw ebp-4 L1
0024f61c 0055

kd> .formats 55
Evaluate expression:
	Hex: 00000055
	Decimal: 85
	Octal: 00000000125
	Binary: 00000000 00000000 00000000 01010101
	Chars: ...U		***
	Time: Thu Jan 1 09:01:25 1970
	Float: low 1.1911e-043 high 0
	Double: 4.19956e-322

버려지는 시점인 8,191번째 반복에서 저장하려고 했던 문자는 'U'(0x55)였다.

내가 실행한 배치 파일(test_long.bat)의 커맨드라인 중 'U' 문자는 이 시점에 버려지게 된다.

 

프로세스 실행을 위한 커맨드라인 생성

이번에는 cmd.exe에서 배치 파일을 프로세스로 실행하기 위해 커맨드라인을 생성하는 과정이다.
커맨드라인은 cmd!GetTitle 함수를 통해 얻어진다.

kd> !thread
THREAD 855ab3f0 Cid 117c.0df0 Teb: 7ffde000 Win32Thread: fdc862f8 RUNNING on processor 0
Not impersonating
DeviceMap 8dd5dd60
Owning Process 84f9dd40 Image: cmd.exe
Attached Process N/A Image: N/A
Wait Start TickCount 11949946 Ticks: 0
Context Switch Count 1350 IdealProcessor: 0
UserTime 00:00:00.140
KernelTime 00:00:36.083
Win32 Start Address cmd!mainCRTStartup (0x4a7f829a)
Stack Init ae8bced0 Current ae8bc9d0 Base ae8bd000 Limit ae8ba000 Call 00000000
Priority 11 BasePriority 8 PriorityDecrement 2 IoPriority 2 PagePriority 5
ChildEBP RetAddr Args to Child
0026e9f8 4a7f3c62 003c0848 5bd5e577 ffffffff cmd!GetTitle (FPO: [Non-Fpo])
0026ec44 4a7f3d48 003c0848 00000000 00000000 cmd!ECWork+0x30 (FPO: [Non-Fpo])
0026ec5c 4a7f15c5 003c0848 5bd5f98b 00000001 cmd!ExtCom+0x47 (FPO: [Non-Fpo])
0026f0b8 4a7f22c0 003c0848 4a828640 003c0848 cmd!FindFixAndRun+0x1f7 (FPO: [Non-Fpo])
0026f108 4a7f4d0e 00000002 003c0848 003d7d18 cmd!Dispatch+0x14b (FPO: [Non-Fpo])
0026f130 4a7f5718 003d7d18 003d7938 00000104 cmd!BatLoop+0x20b (FPO: [Non-Fpo])
0026f160 4a7f6b85 003d7938 003d79e8 00000104 cmd!BatProc+0x1bb (FPO: [Non-Fpo])
0026f3b8 4a7f3d48 003d7938 00000000 00000000 cmd!ECWork+0xd8 (FPO: [Non-Fpo])
0026f3d0 4a7f15c5 003d7938 5bd5f11f 00000001 cmd!ExtCom+0x47 (FPO: [Non-Fpo])
0026f82c 4a7f22c0 003d7938 003d7938 77658e7f cmd!FindFixAndRun+0x1f7 (FPO: [Non-Fpo])
0026f87c 4a7f7489 00000000 003d7938 4a814204 cmd!Dispatch+0x14b (FPO: [Non-Fpo])
0026f8c0 4a7f835e 00000003 006712b0 00671648 cmd!main+0x11d (FPO: [Non-Fpo])
0026f904 7764ed6c 7ffdf000 0026f950 778e37f5 cmd!_initterm_e+0x163 (FPO: [Non-Fpo])
0026f910 778e37f5 7ffdf000 7ca96ff4 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
0026f950 778e37c8 4a7f829a 7ffdf000 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo])
0026f968 00000000 4a7f829a 7ffdf000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])

cmd!GetTitle 함수를 좀 더 살펴보자.

kd> ub eip L10
cmd!GetTitle+0x49:
4a7f24b5 85db 		test ebx,ebx
4a7f24b7 7428 		je cmd!GetTitle+0x75 (4a7f24e1)
4a7f24b9 ff7738 	push dword ptr [edi+38h] // edi = poi(ebp+8)+38
4a7f24bc 56		push esi
4a7f24bd 53 		push ebx
4a7f24be e897f3ffff 	call cmd!StringCchCopyW (4a7f185a) // "powershell"
4a7f24c3 837f3c00 	cmp dword ptr [edi+3Ch],0
4a7f24c7 7416 		je cmd!GetTitle+0x73 (4a7f24df)
4a7f24c9 68b8257f4a	push offset cmd!`string' (4a7f25b8)
4a7f24ce 56 		push esi
4a7f24cf 53 		push ebx
4a7f24d0 e8d4fbffff 	call cmd!StringCchCatW (4a7f20a9) // ' ' (멋대로 공백 1개 추가)
4a7f24d5 ff773c 	push dword ptr [edi+3Ch]
4a7f24d8 56 		push esi
4a7f24d9 53		push ebx
4a7f24da e8cafbffff 	call cmd!StringCchCatW (4a7f20a9) // " -EncodedCommand ~"

kd> dd poi(ebp+8)+38
003c0880 003d3720 003e4dc0 00000000 00000000
003c0890 00000000 00000000 73b87f89 0800f2cb
003c08a0 00000030 003e4db8 003a0043 0074005c
003c08b0 00730065 00320074 00700000 0077006f
003c08c0 00720065 00680073 006c0065 0000006c
003c08d0 6ab87f90 0a00f2c0 000000e6 003c08a0
003c08e0 00430022 005c003a 00650074 00740073
003c08f0 00220032 0043003b 005c003a 00690057

kd> db 003d3720
003d3720 70 00 6f 00 77 00 65 00-72 00 73 00 68 00 65 00 p.o.w.e.r.s.h.e.
003d3730 6c 00 6c 00 00 00 00 00-0a 4f 29 4b 00 00 00 88 l.l......O)K....
003d3740 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
003d3750 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
003d3760 01 4f 29 4b 00 00 00 80-b6 00 00 00 00 00 00 00 .O)K............
003d3770 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
003d3780 00 00 00 00 00 00 00 00-1c 4f 29 4b 00 00 00 80 .........O)K....
003d3790 bb 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

kd> db 003e4dc0
003e4dc0 20 00 2d 00 45 00 6e 00-63 00 6f 00 64 00 65 00 .-.E.n.c.o.d.e.
003e4dd0 64 00 43 00 6f 00 6d 00-6d 00 61 00 6e 00 64 00 d.C.o.m.m.a.n.d.
003e4de0 20 00 4a 00 41 00 42 00-7a 00 41 00 45 00 51 00 .J.A.B.z.A.E.Q.
003e4df0 41 00 59 00 67 00 41 00-67 00 41 00 44 00 30 00 A.Y.g.A.g.A.D.0.
003e4e00 41 00 49 00 41 00 41 00-6e 00 41 00 43 00 51 00 A.I.A.A.n.A.C.Q.
003e4e10 41 00 63 00 77 00 42 00-34 00 41 00 46 00 6b 00 A.c.w.B.4.A.F.k.
003e4e20 41 00 51 00 51 00 42 00-6d 00 41 00 45 00 45 00 A.Q.Q.B.m.A.E.E.
003e4e30 41 00 64 00 41 00 41 00-67 00 41 00 44 00 30 00 A.d.A.A.g.A.D.0.

첫 번째 파라미터인 ebp+8에서 +38, +3C offset 위치의 문자열 2개를 조합한다.
이 문자열은 앞서 Lex 버퍼를 읽어들인 문자열을 실행 경로와 파라미터로 나눠놓은 것이다.
cmd!GetTitle 함수는 "실행 경로 + 공백 + 파라미터"로 조합된 문자열을 리턴한다.

이 시점에는 이미 반복문의 버그로 한 글자가 누락된 상태라 커맨드라인 역시 한 글자가 잘린 채로 생성된다.
비교를 위해 "두 번째 8K 버퍼 읽기 단계"에서 파일에서 읽었던 버퍼 내용을 다시 살펴보자.

4a81c640 41 00 47 00 55 00 41 00-4c 00 67 00 42 00 48 00 A.G.U.A.L.g.B.H.
4a81c650 41 00 47 00 55 00 41 00-64 00 41 00 42 00 43 00 A.G.U.A.d.A.B.C.
4a81c660 41 00 48 00 6b 00 41 00-64 00 41 00 42 00 6c 00 A.H.k.A.d.A.B.l.
4a81c670 41 00 48 00 4d 00 41 00-4b 00 41 00 41 00 6b 00 A.H.M.A.K.A.A.k.
4a81c680 41 00 48 00 4d 00 41 00-52 00 41 00 42 00 69 00 A.H.M.A.R.A.B.i.
4a81c690 41 00 43 00 6b 00 41 00-4b 00 51 00 41 00 37 00 A.C.k.A.K.Q.A.7.
4a81c6a0 41 00 41 00 3d 00 3d 00-77 00 42 00 34 00 41 00 A.A.=.=.

그다음 GetTitle 함수에서 읽어들인 동일한 부분의 버퍼 내용을 확인해보자.

003ece28 41 00 47 00 55 00 41 00-4c 00 67 00 42 00 48 00 A.G.U.A.L.g.B.H.
                                                             * 'U' has gone!
003ece38 41 00 47 00 41 00 64 00-41 00 42 00 43 00 41 00 A.G.A.d.A.B.C.A.
003ece48 48 00 6b 00 41 00 64 00-41 00 42 00 6c 00 41 00 H.k.A.d.A.B.l.A.
003ece58 48 00 4d 00 41 00 4b 00-41 00 41 00 6b 00 41 00 H.M.A.K.A.A.k.A.
003ece68 48 00 4d 00 41 00 52 00-41 00 42 00 69 00 41 00 H.M.A.R.A.B.i.A.
003ece78 43 00 6b 00 41 00 4b 00-51 00 41 00 37 00 41 00 C.k.A.K.Q.A.7.A.
003ece88 41 00 3d 00 3d 00 00 00 A.=.=...

생성되는 커맨드라인에는 반복문에서 8,191번째에 해당했던 'U' 문자가 없는 상태다.

의미있는 원인 분석은 다 끝났고 뒷 내용은 실제 프로세스 생성을 따라가면서 잘못된 커맨드라인이 powershell.exe에 전달되어 실행(CreateProcess)되는 과정을 끄적인 의미 없는 보고용 분석 내용이다. ㅋㅋ

프로세스 실행

마지막으로 생성된 커맨드라인으로 프로세스를 실행하는 단계다.
cmd.exe test_long.bat 구문을 통해 cmd.exe는 배치 파일의 "powershell -EncodedCommand..." 커맨드를 읽어 들였다.
이 단계는 powershell.exe 프로세스를 CreateProcess 함수로 실행시키는 마지막 단계다.
이 동작은 cmd!ExecPgm 함수를 통해 수행된다.

kd> !thread
THREAD 855ab3f0 Cid 117c.0df0 Teb: 7ffde000 Win32Thread: fdc862f8 RUNNING on processor 0
Not impersonating
DeviceMap 8dd5dd60
Owning Process 84f9dd40 Image: cmd.exe
Attached Process N/A Image: N/A
Wait Start TickCount 11950624 Ticks: 0
Context Switch Count 1536 IdealProcessor: 0
UserTime 00:00:00.156
KernelTime 00:00:41.293
Win32 Start Address cmd!mainCRTStartup (0x4a7f829a)
Stack Init ae8bced0 Current ae8bc4c8 Base ae8bd000 Limit ae8ba000 Call 00000000
Priority 8 BasePriority 8 PriorityDecrement 0 IoPriority 2 PagePriority 5
ChildEBP RetAddr Args to Child
0026e9e4 4a7f3cb5 003c0848 00000000 00000000 cmd!ExecPgm+0x206 (FPO: [Non-Fpo])
0026ec44 4a7f3d48 003c0848 00000000 00000000 cmd!ECWork+0x7f (FPO: [Non-Fpo])
0026ec5c 4a7f15c5 003c0848 5bd5f98b 00000001 cmd!ExtCom+0x47 (FPO: [Non-Fpo])
0026f0b8 4a7f22c0 003c0848 4a828640 003c0848 cmd!FindFixAndRun+0x1f7 (FPO: [Non-Fpo])
0026f108 4a7f4d0e 00000002 003c0848 003d7d18 cmd!Dispatch+0x14b (FPO: [Non-Fpo])
0026f130 4a7f5718 003d7d18 003d7938 00000104 cmd!BatLoop+0x20b (FPO: [Non-Fpo])
0026f160 4a7f6b85 003d7938 003d79e8 00000104 cmd!BatProc+0x1bb (FPO: [Non-Fpo])
0026f3b8 4a7f3d48 003d7938 00000000 00000000 cmd!ECWork+0xd8 (FPO: [Non-Fpo])
0026f3d0 4a7f15c5 003d7938 5bd5f11f 00000001 cmd!ExtCom+0x47 (FPO: [Non-Fpo])
0026f82c 4a7f22c0 003d7938 003d7938 77658e7f cmd!FindFixAndRun+0x1f7 (FPO: [Non-Fpo])
0026f87c 4a7f7489 00000000 003d7938 4a814204 cmd!Dispatch+0x14b (FPO: [Non-Fpo])
0026f8c0 4a7f835e 00000003 006712b0 00671648 cmd!main+0x11d (FPO: [Non-Fpo])
0026f904 7764ed6c 7ffdf000 0026f950 778e37f5 cmd!_initterm_e+0x163 (FPO: [Non-Fpo])
0026f910 778e37f5 7ffdf000 7ca96ff4 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
0026f950 778e37c8 4a7f829a 7ffdf000 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo])
0026f968 00000000 4a7f829a 7ffdf000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])

다음은 cmd!ExecPgm 함수에서 CreateProcess 함수를 호출하는 순간이다.

kd> ub eip+6 L10
cmd!ExecPgm+0x1b6:
4a7f3f69 0100 		add dword ptr [eax],eax
4a7f3f6b 8d8568ffffff 	lea eax,[ebp-98h]
4a7f3f71 50 		push eax
4a7f3f72 8d851cffffff 	lea eax,[ebp-0E4h]
4a7f3f78 50 		push eax
4a7f3f79 be6052814a 	mov esi,offset cmd!CurDrvDir (4a815260)
4a7f3f7e 56 		push esi
4a7f3f7f 53 		push ebx
4a7f3f80 6800000800 	push 80000h
4a7f3f85 57 		push edi
4a7f3f86 53 		push ebx
4a7f3f87 53 		push ebx
4a7f3f88 ff758c 	push dword ptr [ebp-74h] // lpCommandLine
4a7f3f8b ff7598 	push dword ptr [ebp-68h] // lpApplicationName
4a7f3f8e ff157c137f4a 	call dword ptr [cmd!_imp__CreateProcessW (4a7f137c)]

첫 번째 파라미터인 lpApplicationName에는 cmd.exe 에서 구한 powershell.exe의 실행 경로가 설정돼있다.

// lpApplicationName
kd> dd ebp-68 L1
0026e97c 003c0b40

kd> du /c 100 003c0b40
003c0b40 "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe

두 번째 파마리터인 문제의 lpCommandLine에는 한 글자가 잘린 커맨드라인이 설정돼있다.

// lpCommandLine
kd> dd ebp-74 L1
0026e970 003e8e28

kd> du /c 40 003e8e28
003e8e28 "powershell -EncodedCommand JABzAEQAYgAgAD0AIAAnACQAcwB4AFkAQQBm"
003e8ea8 "AEEAdAAgAD0AIAAnACcAWwBEAGwAbABJAG0AcABvAHIAdAAoACIAawBlAHIAbgBl"
003e8f28 "AGwAMwAyAC4AZABsAGwAIgApAF0AcAB1AGIAbABpAGMAIABzAHQAYQB0AGkAYwAg"
003e8fa8 "AGUAeAB0AGUAcgBuACAASQBuAHQAUAB0AHIAIABWAGkAcgB0AHUAYQBsAEEAbABs"
003e9028 "AG8AYwAoAEkAbgB0AFAAdAByACAAbABwAEEAZABkAHIAZQBzAHMALAAgAHUAaQBu"
003e90a8 "AHQAIABkAHcAUwBpAHoAZQAsACAAdQBpAG4AdAAgAGYAbABBAGwAbABvAGMAYQB0"
... ...
003ece28 "AGUALgBHAGAdABCAHkAdABlAHMAKAAkAHMARABiACkAKQA7AA=="  // 실행될 커맨드라인

                    *
         "AGUALgBHAGUAdABCAHkAdABlAHMAKAAkAHMARABiACkAKQA7AA==" // 원본 커맨드라인(배치 파일)

결국 cmd.exe에 의해 실행되는 powershell.exe 프로세스는 잘린 커맨드라인으로 실행된다.

윈도우 10 최신 버전에서도 발생하길래 MSRC에 리포트했더니 cmd.exe 버그 맞다고 고친다고 한다.

분석하고 나니 딱히 영양가는 없는 버그인데 내 문제는 아니라 다행이라는 나 자신을 보니 왠지 씁쓸해졌다.