PowerShell 스크립트 난독화 해제 방법
마지막 업데이트:
PowerShell 스크립트의 난독화를 해제한다는 것은 공격자가 스크립트의 실제 동작을 숨기기 위해 적용한 변환을 한 층씩 되돌리는 일이며, 스크립트를 단 한 번도 실행하지 않고 수행하는 일입니다. Base64 벽으로 도착한 의심스러운 한 줄 명령어나 문자 코드와 문자열 연결의 뒤엉킴도, 그 아래는 대개 지극히 평범한 PowerShell 명령어입니다. 다운로드, 디코딩된 페이로드, 디스크에 닿지 않고 메모리에서만 실행되는 명령어. 난독화는 영리해서 존재하는 것이 아닙니다. 서명 스캐너를 통과하고, 명령줄을 한눈에 읽을 수 없게 만들고, 인간 분석가가 흥미로운 부분에 도달하기 전에 포기하게 만들기 위해 존재합니다. 이 글에서는 각 계층이 왜 존재하는지, 불활성 텍스트로 어떻게 벗겨내는지, 발견한 조각들이 실제로 무엇을 의미하는지 설명합니다. 전체 흐름은 PowerShell 난독화 해제 도구에서 실행할 수 있습니다. 브라우저 안에서 계층별로 디코딩하며, 아무것도 실행하지 않고, 아무것도 컴퓨터 밖으로 나가지 않습니다.
공격자가 PowerShell을 난독화하는 이유 — 그리고 인코딩된 형태를 가장 먼저 벗겨야 하는 이유
PowerShell은 모든 Windows 컴퓨터에 내장되어 있고, 디스크에 파일을 쓰지 않고 인터넷에서 코드를 다운로드해 실행할 수 있으며, 실행한 사람의 권한으로 동작합니다. 이 조합이 바로 MITRE ATT&CK 프레임워크에서 가장 많이 악용되는 도구 중 하나(기법 T1059.001)인 이유이며, 공격자가 악성 PowerShell 명령어를 입수하면 가장 먼저 하는 일이 그것을 숨기는 이유입니다. DownloadString 뒤에 Invoke-Expression이 오는 평문 명령어는 탐지가 너무나 쉽습니다. 어떤 스캐너, 어떤 로그 검토, 프로세스 명령줄을 흘끗 보는 어떤 분석가도 이를 발견합니다. 인코딩되고, 압축되고, 조각난 같은 명령어는 알려진 어떤 서명과도 일치하지 않고 인간 독자에게는 아무 의미도 없는 문자열이 됩니다.
그래서 인코딩된 형태는 다른 무엇보다 먼저 벗겨야 하는 것입니다. 디코딩하기 전까지는 눈앞의 것이 설치 프로그램인지, 장난인지, 자격 증명 탈취 도구인지 알 수 없으며, 그 외의 모든 판단은 추측에 불과합니다. 좋은 소식은 난독화가 거의 항상 정적 해제를 통해 되돌릴 수 있다는 점입니다. 페이로드는 실행하는 컴퓨터가 디코딩할 수 있어야 하므로, 모든 계층에는 결정적인 역연산이 있습니다. Base64는 디코딩되고, gzip은 풀리고, 문자 코드는 다시 문자가 되며, 연결된 문자열은 이어집니다. 작업은 기계적이지 신비롭지 않으며, 의심스러운 스크립트를 분류할 때 가장 가치 높은 단일 단계입니다. 한 번의 디코딩으로 "읽을 수 없는 덩어리"가 실제로 추론할 수 있는 읽기 쉬운 명령어로 바뀝니다.
-EncodedCommand와 UTF-16LE: 의미 없어 보이는 Base64가 대개 읽기 쉬운 텍스트인 이유
만나게 될 가장 흔한 PowerShell 난독화 형태는 단 하나의 스위치입니다. powershell.exe -EncodedCommand 뒤에 긴 Base64 문자열이 옵니다. 무작위 문자처럼 보이지만, 거의 항상 평범한 스크립트 텍스트일 뿐이며, 처음 보면 놀라운 방식으로 인코딩되어 있습니다. PowerShell의 -EncodedCommand는 Base64가 UTF-16LE로 디코딩되기를 기대합니다. 문자당 2바이트, 각 ASCII 문자 뒤에 0 바이트가 오는 형태인데, 이는 Windows에서 .NET 문자열의 인코딩이기 때문입니다. 그래서 무해한 명령어 Write-Host "hello"의 Base64는 SQB3AHIAaQB0AGUALQBIAG8AcwB0ACA…로 시작하며, 반복되는 "A" 문자가 수많은 0 바이트의 흔적입니다.
이를 알면 인코딩된 덩어리를 읽는 방식이 달라집니다. UTF-8 난해한 문자열로 디코딩되는 Base64는 아마 애초에 UTF-8이 아닐 가능성이 큽니다. 먼저 UTF-16LE를 시도하면 "난해한 문자열"이 읽기 쉬운 PowerShell로 바뀝니다. -EncodedCommand 스위치가 보이지 않는 맨 Base64 덩어리도 마찬가지입니다. 인쇄 가능한 텍스트로 디코딩되는 유효한 Base64의 긴 토큰은 플래그 없는 같은 수법입니다. 실용적인 규칙은 간단합니다. PowerShell 명령줄에서 긴 Base64 토큰을 보면 디코딩으로 반증될 때까지 스크립트라고 가정하고, 채팅 창이나 터미널에 붙여넣어 결과를 보는 대신 절대 실행하지 않는 도구로 불활성 텍스트로 디코딩하십시오.
계층 케이크: 압축, 문자 코드, 연결, -f, 백틱
실제 페이로드가 한 계층에서 멈추는 경우는 거의 없습니다. -EncodedCommand를 디코딩하면 그 아래 스크립트 자체가 다시 인코딩되어 있는 경우가 많습니다. 또 다른 Base64 문자열로 감싼 gzip 압축 덩어리, [char]68+[char]105처럼 한 숫자씩 명령어를 철자하는 문자 코드 시퀀스, 조각으로 잘라 더하기 기호로 다시 붙인 문자열, -f 연산자가 실행 시 인수를 형식 문자열에 채우는 경우, 키워드에 백틱이 뿌려져 Do`wnloadString이 DownloadString에 대한 단순한 검색에 더 이상 걸리지 않게 된 경우 등입니다. 각 계층은 서로 다른 이유로 존재합니다. 압축은 긴 페이로드를 짧은 한 줄로 줄이고 텍스트 검색을 완전히 무력화합니다. 문자 코드와 연결은 의심스러운 단어가 파일에 문자 그대로 절대 나타나지 않도록 하여 서명 일치를 무력화합니다. -f 연산자와 백틱은 스크립트를 구문적으로 유효하게 유지하면서 단순한 문자열 검색을 무력화합니다.
이 모든 것에 대한 방어는 동일합니다. 순서대로 한 번에 한 계층씩 풀고, 더 이상 적용되는 것이 없을 때 멈춥니다. Base64를 디코딩한 뒤 결과가 압축되었는지 확인하고 풀고, 문자 코드를 해결하고, 조각을 이어 붙이고, 형식 인수를 대입하고, 백틱을 제거합니다. 각 단계는 다음 단계가 처리할 수 있는 일반 텍스트를 만들어냅니다. 이를 눈에 보이는 타임라인으로 표시하는 도구, 계층마다 전후를 보여주는 카드가 있는 도구는 최종 답만 조용히 출력하는 도구보다 가치가 큽니다. 타임라인 자체가 증거이기 때문입니다. 어떤 계층이 존재했는지 보여주고, 해제가 일찍 멈추면 — "더 이상 해제할 수 없음" — 그것은 발견이지 실패가 아닙니다. 페이로드에 정적 분석만으로는 도달할 수 없는 동적인 것, 암호화된 것, 실행 시 만들어지는 것이 포함되어 있음을 알려줍니다.
스크립트 블록 로깅 4104: 긴 스크립트가 분할되고 재조립되는 방식
디코딩이 전혀 필요 없는 난독화 해제된 PowerShell의 출처가 하나 있습니다. 바로 컴퓨터 자체입니다. 스크립트 블록 로깅이 활성화되면 Windows는 PowerShell이 처리하는 모든 스크립트 블록의 내용을 Microsoft-Windows-PowerShell/Operational 로그의 이벤트 4104로 기록합니다. 그리고 엔진이 -EncodedCommand와 난독화 계층을 이미 펼친 뒤 PowerShell이 본 그대로의 코드를 기록합니다. Base64로 도착한 의심스러운 한 줄 명령어는 4104 이벤트 안에서 실제로 실행된 읽기 쉬운 스크립트로 나타납니다. 그래서 이 기능은 방어자가 활성화할 수 있는 가장 가치 있는 PowerShell 로깅 설정이며, 동시에 기본적으로 꺼져 있는 이유이기도 합니다. 장황하고 로그에 자격 증명 등 민감한 데이터가 포함될 수 있기 때문에 Microsoft는 진단 목적 외의 용도에는 보호된 이벤트 로깅(Protected Event Logging)을 함께 사용할 것을 권장합니다.
문제는 Windows가 긴 스크립트 블록을 여러 4104 이벤트로 나눈다는 점입니다. 각 이벤트는 동일한 ScriptBlockId, MessageNumber, MessageTotal을 지닙니다. 예를 들어 5개 중 2번째 부분입니다. 전체 스크립트는 이 부분들을 해당 ID로 그룹화하고 메시지 번호순으로 정렬한 뒤 연결해야만 존재합니다. 단일 이벤트만으로는 단어 중간에서 끊길 수 있고, 첫 번째 조각만 읽는 검색은 페이로드가 끝에서 무엇을 하는지 놓칩니다. 따라서 재조립은 기계적인 단계이며 확인할 것은 하나뿐입니다. 빠진 부분이 있는지 여부입니다. 로그가 3개 중 2개라고 하고 2번째 부분이 끝내 도착하지 않았다면 — 로그가 순환했거나 지워졌기 때문 — 재조립된 텍스트는 불완전하고, 거기서 도출한 모든 결론은 잠정적입니다. 동일한 그룹화 논리는 EVTX 뷰어가 내보낸 .evtx 파일에서 4104 이벤트에 플래그를 지정할 때도 사용합니다. 관련 글 Windows 컴퓨터 없이 .evtx 파일 읽기도 참조하십시오.
지표 읽기: 다운로드 크레이들, AMSI 우회, 실행 정책 우회
스크립트를 읽을 수 있게 되면 다음 단계는 그것을 악성이라고 선언하는 것이 아니라, 그 안에 포함된 지표를 검증해야 할 단서로 읽는 것입니다. 다운로드 크레이들 — DownloadString, DownloadFile, Net.WebClient, Invoke-WebRequest, bitsadmin, certutil이 URL에서 파일을 가져오는 것 — 은 스크립트가 어딘가에서 코드를 가져온다는 것을 알려주며, 다음 질문은 어디에서 가져오는지, 그 목적지가 네트워크에서 예상되는 곳인지입니다. AMSI 우회 — amsiInitFailed, AmsiUtils, 또는 악성코드 방지 스캔 인터페이스를 패치하는 코드 — 는 작성자가 스캔 없이 스크립트를 실행하고 싶어 했다는 것을 알려줍니다. 이는 강한 신호이지만 판결은 아닙니다. 침투 테스터와 일부 합법적 도구도 같은 일을 합니다. 실행 정책 우회(-ExecutionPolicy Bypass, -ep bypass)는 작성자가 PowerShell 자체의 보호 장치가 스크립트를 막지 않기를 바랐다는 것을 알려줍니다. 이는 악성코드에서도, 서명되지 않은 스크립트를 실행하는 일상적인 자동화에서도 흔합니다.
규율은 각 지표를 답이 아니라 질문으로 다루는 것입니다. 다운로드 크레이들에 숨겨진 창(-WindowStyle Hidden)과 인코딩된 명령어가 더해지면 상향 보고할 가치가 있는 패턴이고, 그 중 어느 하나만으로는 확인할 가치가 있는 패턴입니다. 단서를 판결로 바꾸는 것은 맥락입니다. 누가 프로세스를 시작했는지, 부모 프로세스는 무엇인지, 연락하는 URL이 알려진 것인지, 같은 스크립트가 다른 컴퓨터에도 나타나는지 말입니다. 일치하는 스니펫과 "맥락에서 검증하십시오"라는 메모와 함께 지표를 나열하는 난독화 해제 도구는 정직한 일을 하고 있는 것이고, 패턴 목록만으로 스크립트에 "악성" 또는 "깨끗함" 도장을 찍는 도구는 정적 분석이 알 수 있는 것을 과장하고 있습니다. 반대도 마찬가지입니다. 지표가 발견되지 않은 스크립트가 안전하다고 증명된 것은 아닙니다. 지표 목록이 다루지 않는 기법을 사용하고 있거나, 실행 시 동적으로 페이로드를 구성하고 있을 뿐일 수 있습니다.
안전한 워크플로: 채팅에 붙여넣지 말고, 실행하지 말고, 불활성으로 디코딩하라
의심스러운 PowerShell 스크립트를 다룰 때 가장 중요한 규칙은 그것이 무엇을 하는지 알아보기 위해 실행하지 말고, AI에게 묻기 위해 채팅 창에 붙여넣지 말라는 것입니다. 실행하면 페이로드가 실행됩니다. 그것이 바로 피하려는 결과입니다. 채팅에 붙여넣으면 잠재적으로 악성인 스크립트가 타사 서비스로 전송되고, 통제할 수 없는 대화 기록에 남으며, AI 시스템이 악성일 수 있는 스크립트의 난독화 해제를 일상적으로 거부하기 때문에 어차피 거부당하는 경우가 많습니다. 안전한 워크플로는 의도적으로 지루합니다. 덩어리를 브라우저 로컬 난독화 해제 도구나 샌드박스 분석 컴퓨터에 복사하고, 디코딩된 출력을 읽을 불활성 텍스트로 다루며, 실행할 코드로는 절대 다루지 마십시오.
그 다음부터는 짧은 체크리스트입니다. 첫째, 텍스트가 더 이상 변하지 않을 때까지 계층별로 디코딩하고, 해제가 어디에서 멈췄는지 기록합니다. 둘째, 지표를 읽고 각각이 사실이라면 무엇을 의미하는지 적어둔 뒤 맥락과 대조해 하나씩 검증합니다. 부모 프로세스, 대상 URL, 출처 컴퓨터. 셋째, 결과를 다음 단계를 위해 만들어진 사람이나 시스템에 넘깁니다. EDR 콘솔, 샌드박스 기폭, 사고 대응 팀. 난독화 해제 도구의 일은 읽기 쉬운 텍스트와 정직한 지표에서 끝납니다. 판결은 샌드박스와 분석가의 몫이지 디코딩 도구의 몫이 아닙니다. 스크립트가 관리하는 컴퓨터에서 온 것이라면 스크립트 블록 로깅이 켜져 있었는지 확인하십시오. 켜져 있었다면 4104 이벤트는 실제로 실행된 내용의 기본 진실이며, 로그가 순환하기 전에 수집할 가치가 있습니다.