
악성코드 트라이어지 및 바이너리 분석을 위한 구성 가능한 커맨드라인 툴킷: 악성 파일과 셸코드에서 지표를 디코딩, 복호화, 캐르빙, 추출합니다.
__ __ High Octane Triage Analysis __
|| _||______ __ __________ _____ ||
|| \||___ \__| ____/ ______/___ / ____\ ||
==||=====|| | __/ |/ \ /==| / __ \ __\===]|
'======|| | \ | | \_ _| \ ___/| | ||
||____ /__|___|__/ / | \____]| | ||
=========''====\/=========/ /==|__|=====|__|======'
\ /
\/
Binary Refinery™는 압축 및 암호화와 같은 이진 데이터 변환을 구현하는 Python 스크립트 모음입니다.
우리는 종종 이 도구를 _refinery_라고 줄여 부르며, 이것은 해당 패키지의 이름이기도 합니다.
이 스크립트들은 오직 stdin에서 입력을 읽고 stdout으로 출력을 쓰도록 설계되었습니다.
핵심 철학은 모든 스크립트가 하나의 작업을 수행하는 유닛이어야 하며,
개별 유닛들은 명령줄에서 파이프 연산자 |를 사용해 _파이프라인_으로 결합하여 더 복잡한 작업을 수행할 수 있다는 것입니다.
이 프로젝트의 주요 초점은 악성코드 트라이지이며,
명령줄에서 CyberChef와 같은 것을 구현하려는 시도입니다.
Python 가상 환경을 만드세요. Python 3.10 이상이 필요합니다. 다음과 같이 refinery를 설치하세요:
python -m pip install -U pip
python -m pip install -U binary-refinery[extended]
유닛을 -h와 함께 실행하여 작동 방식을 알아보고, 문서를 grep하거나 binref 명령을 사용해 유닛을 찾을 수 있습니다.
실제 작동 모습을 보려면 최근 비디오를 시청하세요.
하지만 이 readme의 나머지 부분도 읽어 보시기 바랍니다.
정해진 릴리스 일정은 없지만 릴리스는 매우 자주 이루어지며 주기적으로 업데이트하는 것이 좋습니다. 버그 수정은 GIT 외부에는 문서화되지 않지만, 그 외 모든 변경 사항(즉, 새 기능)은 변경 로그에 문서화됩니다. 특히 영향력이 큰 릴리스에 대한 업데이트는 Mastodon에서 저를 팔로우하세요.
유닛을 -h 또는 --help 스위치와 함께 실행할 때 표시되는 도움말 텍스트가 해당 유닛의 주요 문서입니다.
자동 생성된 문서에는 각 유닛에 대한 해당 출력이 최상위 수준에 모아져 있으며,
또한 툴킷의 세 가지 기본 개념인 framing, multibin arguments, meta variables에 대한 사양도 포함되어 있습니다.
모든 유닛에 대한 설명과 도움말 텍스트의 전문 검색도 제공되는 binref 명령을 통해 명령줄에서 사용할 수 있습니다.
참조 문서가 다소 건조할 수 있다는 점을 감안하여, 튜토리얼 시리즈를 제작하는 작업이 진행 중입니다. 꼭 확인해 보시길 권합니다.
게다가 아래에 추가 리소스(제3자가 제작한 일부 포함)를 모아 두었습니다.
[!NOTE]
Refinery는 아직 알파 단계이며 인터페이스가 때때로 변경될 수 있습니다. 즉, 유닛과 매개변수가 제거되거나 이름이 바뀔 수 있습니다. 따라서 이전 비디오나 블로그 게시물의 특정 명령줄이 더 이상 작동하지 않을 수 있습니다.
2021/08] OALabs가 특집 비디오에서 툴킷을 시연하도록 흔쾌히 허락해 주었습니다.
영상에서 저는 본질적으로
첫 번째 튜토리얼의 내용을 살펴봅니다.2021/11] Johannes Bader가 binary refinery로 malspam을 분석하는 놀라운 블로그 게시물을 작성했습니다.2024/03] Malware Analysis For Hedgehogs가 refinery를 사용해 XWorm 샘플을 언패킹하는 비디오를 제작했습니다.2024/11] the CyberYeti가 스트림에서 refinery를 소개하는 자리에 저를 초대했습니다.2025/06] the CyberYeti와 함께 다시 스트림에 출연했는데, 이번 것은 조금 더 날것 그대로입니다.
여기서 볼 수 있는 모든 버그는 수정되었습니다. 😉쇼케이스에는 아래 예제 섹션과 튜토리얼의 샘플이 다시 포함됩니다.
Binary Refinery는 (c) 2019 Jesko Hüttenhain이며, 3-Clause BSD License에 따라 배포됩니다. 이 저장소에는 라이선스 전문 사본도 포함되어 있습니다. 이 라이선스가 적용되지 않는 용도로 사용하려면 주저하지 말고 저자에게 연락하세요.
refinery는 최소 Python 3.10이 필요합니다. 전용 가상 환경에 설치하는 것이 좋습니다. 이 패키지는 많은 의존성을 끌어들일 수 있으며, 전역 Python에 설치하면 버전 충돌이 발생하기 쉽습니다. 또한 이 툴킷은 많은 수의 새 명령을 도입하므로, 일부 시스템에서는 이러한 명령 중 일부가 충돌할 가능성이 높습니다. 명령을 별도의 가상 환경에 유지하는 것이 이를 방지하는 한 가지 방법입니다.
모든 refinery 명령을 셸에서 항상 사용할 수 있게 하려면(즉, 사용자 지정 가상 환경으로 전환하지 않고),
설치 시 _접두사_를 선택할 수도 있습니다.
이 접두사는 설치되는 모든 명령 실행 래퍼(shim) 앞에 붙습니다.
예를 들어 접두사로 r.을 선택하면 emit 유닛이 r.emit 명령으로 설치됩니다.
추가 이점으로 r.을 입력하고 Tab을 두 번 누르면 사용 가능한 모든 refinery 명령 목록을 얻을 수 있습니다.
단, 문서에서는 어떤 접두사도 가정하지 않으며, 대부분의 시스템에서 충돌하지 않는 것이 refinery의 개발 목표입니다.
저자는 접두사를 사용하지 않으며 이 옵션을 안전장치로 제공합니다.
refinery를 설치하고 업데이트하는 가장 간단한 방법은 pip를 사용하는 것입니다. 먼저 최신 버전을 실행했는지 확인하세요:
python -m pip install -U pip
그런 다음 refinery 패키지를 설치하기만 하면 됩니다:
pip install -U binary-refinery
모든 유닛에 사용할 접두사를 선택하려면 환경 변수 REFINERY_PREFIX로 지정할 수 있습니다.
예를 들어 다음 명령은 Linux에서 접두사 r.을 사용하여 현재 Python 환경에 refinery를 설치합니다:
REFINERY_PREFIX=r. pip install -U binary-refinery
Windows에서는 다음 명령을 실행해야 합니다:
set REFINERY_PREFIX=r.
pip install -U binary-refinery
특수 접두사 !를 지정하면 셸 명령이 전혀 생성되지 않고,
binary refinery가 라이브러리로만 설치됩니다.
현재 refinery HEAD를 설치하려면 pip 패키지 대신 이 저장소를 지정하여 위의 모든 단계를 반복하면 됩니다.
예를 들어 다음은 가장 최신 refinery 커밋을 설치합니다:
pip install -U git+git://github.com/binref/refinery.git
마지막으로 REMnux를 사용하는 경우 해당 refinery docker container를 사용할 수 있습니다.
로컬 악성코드 분석 claude에 binary refinery 사용법을 가르치려면 Binary Refinery Skill을 확인하세요.
다음은 현재 다양한 셸 환경이 얼마나 잘 지원되는지 요약한 것입니다:
다른 셸을 사용 중이고 공유할 피드백이 있다면 알려주세요!
상황에 따라 매우 특수한 유닛 중에는 (때로는 큰) 외부 의존성이 필요한 것들이 있습니다.
예를 들어 stego는 이미지 파싱 라이브러리 Pillow가 필요한 유닛입니다.
일부 라이브러리는 첫 사용자의 refinery 설치 시간을 합리적인 수준으로 유지하기 위해 기본적으로 설치되지 않습니다.
해당 유닛은 의존성이 없을 때 무엇을 해야 하는지 알려줍니다:
$ emit config.png | stego RG
(13:37:00) failure in stego: dependency Pillow is missing; run pip install Pillow
그런 다음 이러한 누락된 의존성을 수동으로 설치할 수 있습니다. 누락된 의존성에 신경 쓰고 싶지 않고 긴 refinery 설치 시간을 감수할 수 있다면 다음과 같이 패키지를 설치할 수 있습니다:
pip install -U binary-refinery[all]
그러면 필수 의존성 위에 모든 의존성이 설치됩니다. 보다 정확히는 다음과 같은 추가 범주를 사용할 수 있습니다:
| 이름 | 포함 의존성 |
|---|---|
default | 합리적인 의존성의 권장 선택, 저자의 선택 |
extended | 가장 흔하지 않은 것들만 제외한 확장 선택 |
all | 모든 refinery 유닛에 대한 모든 의존성 |
이 범주들은 오름차순으로 나열되어 있습니다. 즉, extended는 default가 설치하는 모든 것을 설치합니다.
또는 이 저장소를 클론하고 update.sh(Linux) 또는 update.ps1(Windows) 스크립트를 사용하여 refinery 패키지를 로컬 가상 환경에 설치할 수 있습니다. 이 방법의 설치 및 업데이트 과정은 단순히 스크립트를 실행하는 것입니다:
binary-refinery를 제거한 후,binary-refinery[all]을 설치합니다.모든 문서를 로컬에서 생성할 수도 있습니다.
이를 위해 run-pdoc3.py 스크립트를 실행하세요.
binary refinery가 Python 패키지로 설치된 환경에서 실행하지 않으면 실패합니다.
실행하려면 run-pdoc3.py의 첫 번째 명령줄 인수로 가상 환경의 경로를 지정해야 하며,
그러면 스크립트가 해당 환경의 인터프리터를 사용하여 자신을 다시 실행하게 됩니다.
run-pdoc3.py를 실행하려는 것이 확실하다면,
스크립트를 현재 기본 Python 인터프리터로 강제 실행하는 명령줄 스위치가 있습니다.
이 스크립트는 pdoc3 패키지를 설치하고 이를 사용해 refinery 패키지에 대한 HTML 문서를 생성합니다.
그러면 문서는 이 readme 파일 바로 옆의 html 하위 디렉터리에서 찾을 수 있습니다.
튜토리얼은 Jupyter 노트북이며, 가상 환경에 Jupyter가 설치되어 있다면 바로 실행할 수 있습니다. 언급할 가치가 있는 점은 Visual Studio Code가 Jupyter를 매우 편안하게 지원한다는 것입니다.
emit과 dump 유닛은 특별한 역할을 합니다. 전자는 데이터를 출력하는 데 사용되고, 후자는 데이터를 클립보드나 디스크에 덤프하는 데 사용됩니다. 예를 들어 다음 파이프라인을 살펴보세요:
emit M7EwMzVzBkI3IwNTczM3cyMg2wQA | b64 | zl | hex
여기서는 문자열 M7EwMzVzBkI3IwNTczM3cyMg2wQA를 emit으로 출력한 다음,
b64로 base64 디코딩하고,
결과를 zl로 zlib 압축 해제한 후,
마지막으로 압축 해제된 데이터를 hex로 디코딩합니다.
각 유닛은 기본적으로 특정 변환의 "디코딩" 작업을 수행하지만, 일부는 역방향 작업도 구현합니다.
역방향 작업이 있는 경우 항상 -R 또는 --reverse 명령줄 스위치를 통해 수행됩니다.
hex, zl, b64 모두 역방향 작업을 제공하므로 다음 명령으로 위의 base64 문자열을 생성할 수 있습니다:
emit "Hello World" | hex -R | zl -R | b64 -R
base64로 인코딩된 페이로드 버퍼를 포함하는 packed.bin 파일이 주어지면, 다음 파이프라인은 해당 페이로드를 payload.bin으로 추출합니다:
emit packed.bin | carve -l -t1 b64 | b64 | dump payload.bin
carve 유닛은 입력 버퍼에서 데이터 블록을 잘라내는 데 사용할 수 있습니다.
이 경우 base64로 인코딩된 데이터를 찾아 길이순으로 정렬하고(-l) 그중 첫 번째를 반환합니다(-t1).
그렇게 해서 packed.bin에서 가장 큰 base64 형태의 데이터 덩어리를 잘라냅니다.
그런 다음 데이터는 base64 디코딩되어 payload.bin 파일로 덤프됩니다.
pack 유닛은 텍스트 버퍼에서 모든 숫자 표현을 골라내어 이진 표현으로 변환합니다. 간단한 예는 다음 파이프라인입니다:
emit "0xBA 0xAD 0xC0 0xFF 0xEE" | pack | hex -R
이 파이프라인은 문자열 BAADC0FFEE를 출력합니다.
BLOB에서 가장 큰 base64 인코딩 데이터 조각을 추출하여 디코딩합니다:
emit file.exe | carve -ds b64
버퍼에서 ZIP 파일을 잘라내고, 그중 DLL을 선택하여 해당 정보를 표시합니다:
emit file.bin | carve-zip | xtzip file.dll | pemeta
PE 파일 섹션과 해당 SHA-256 해시를 나열합니다:
emit file.exe | vsect [| sha256 -t | pf {} {path} ]]
현재 디렉터리의 모든 파일을 재귀적으로 나열하고 각각의 SHA-256 해시를 표시합니다:
ef "**" [| sha256 -t | pf {} {path} ]]
현재 디렉터리 내에서 재귀적으로 열거된 모든 파일에서 지표를 추출합니다:
ef "**" [| xtp -n6 ipv4 socket url email | dedup ]]
네트워크 바이트 순서로 하드코딩된 IP 주소 0xC0A80C2A를 읽을 수 있는 형식으로 변환합니다:
emit 0xC0A80C2A | pack -EB4 | pack -R [| sep . ]
단일 바이트 XOR 무차별 대입을 수행하고 각 반복에서 PE 파일 페이로드 추출을 시도합니다:
emit file.bin | rep 0x100 [| xor v:index | carve-pe -R | peek | dump {name} ]
RemCos C2 서버 추출:
emit c0019718c4d4538452affb97c70d16b7af3e4816d059010c277c4e579075c944 \
| perc SETTINGS [| put keylen cut::1 | rc4 cut::keylen | xtp socket ]
AgentTesla 구성 추출:
emit fb47a566911905d37bdb464a08ca66b9078f18f10411ce019e9d5ab747571b40 \
| dnfields [| aes x::32 --iv x::16 -T ]] \
| rex -M "((??email))\n(.*)\n(.*)\n:Zone" addr={1} pass={2} host={3}
악성 XLS 매크로 드로퍼에서 PowerShell 페이로드 추출:
emit 81a1fca7a1fb97fe021a1f2cf0bf9011dd2e72a5864aad674f8fea4ef009417b [ \
| xlxtr 9.5:11.5 15.15 12.5:14.5 [ \
| scope -n 3 | chop -t 5 [| sorted -a | snip 2: | sep ] \
| pack 10 | alu --dec -sN B-S ]] \
| dump payload.cmd
그리고 다음 스테이지의 도메인을 가져옵니다:
emit payload.cmd | cmdarg | ps1 | xtp -f domain
언패킹된 HawkEye 샘플의 구성 추출:
emit ee790d6f09c2292d457cbe92729937e06b3e21eb6b212bf2e32386ba7c2ff22c \
| put cfg perc[RCDATA]:c:: [\
| xtp guid | pbkdf2 48 rep[8]:h:00 | cca eat:cfg | aes -Q x::32 --iv x::16 ] \
| dnds
Warzone RAT:
emit 4537fab9de768a668ab4e72ae2cce3169b7af2dd36a1723ddab09c04d31d61a5 \
| vsect .bss | struct I{key:{}}{} [\
| rc4 eat:key | struct I{host:{}}{port:H} {host:u16}:{port} ]
셸코드 로더에서 페이로드를 추출하고 그 C2를 잘라냅니다:
emit 58ba30052d249805caae0107a0e2a5a3cb85f3000ba5479fafb7767e2a5a78f3 \
| rex yara:50607080.* [| struct LL{s:L}{} | xor -B2 rand[s]:msvc | xtp url ]
이것이 일반적인 방식이던 잊혀진 시절의 악성 VBA 매크로를 가져옵니다:
emit ee103f8d64cd8fa884ff6a041db2f7aa403c502f54e26337c606044c2f205394 \
| vbamc
그런 다음 악성 다운로더 페이로드를 추출합니다:
emit ee103f8d64cd8fa884ff6a041db2f7aa403c502f54e26337c606044c2f205394 \
| doctxt | repl drp:c: | carve -s b64 | rev | b64 | rev | ppjscript
악성 PDF 문서에서 페이로드 URL을 추출합니다:
emit 066aec7b106f669e587b10b3e3c6745f11f1c116f7728002f30c072bd42d6253 \
| xt JS | csd string | csd string | url | xtp url [| urlfix ]]
수식 편집기 취약점 문서에서 페이로드 URL을 추출합니다:
emit e850f3849ea82980cf23844ad3caadf73856b2d5b0c4179847d82ce4016e80ee \
| officecrypt | xt oleObject | xt native | rex Y:E9[] | vstack -a=x32 -w=200 | xtp
data가 CBC 모드에서 256비트 AES로 암호화된 파일이라고 가정합니다.
키는 솔트 s4lty를 사용하는 PBKDF2 키 유도 루틴으로 비밀 암호 swordfish에서 파생되었습니다.
IV는 버퍼의 처음 16바이트로 앞에 붙어 있습니다.
다음 파이프라인으로 복호화할 수 있습니다:
emit data | aes --mode cbc --iv cut::16 pbkdf2[32,s4lty]:swordfish
여기서 cut:0:16과 pbkdf2[32,s4lty]:swordfish는 둘 다 특수 핸들러를 사용하는 multibin 인수입니다.
이 경우 cut:0:16은 입력 데이터에서 슬라이스 0:16(즉, 처음 16바이트)을 추출합니다. 이 multibin 핸들러를 적용한 후에는
입력 데이터에서 처음 16바이트가 제거되고 iv 인수가 정확히 그 16바이트로 설정됩니다.
마지막 인수는 32바이트 암호화 키를 지정합니다.
반면 pbkdf2[32,s4lty] 핸들러는 refinery에 pbkdf2 유닛의 인스턴스를 마치 명령줄 매개변수 32와 s4lty가 이 순서로 주어진 것처럼 생성하고, 이 유닛으로 바이트 문자열 swordfish를 처리하도록 지시합니다.
간단한 테스트로 다음 파이프라인은 샘플 텍스트 조각을 암호화하고 복호화합니다:
emit "Once upon a time, at the foot of a great mountain ..." ^
| aes pbkdf2[32,s4lty]:swordfish --iv md5:X -R | ccp md5:X ^
| aes pbkdf2[32,s4lty]:swordfish --iv cut:0:16
| Shell | Platform | 상태 | 비고 |
|---|
| Bash | Posix | 🔵 좋음 | 저자가 가끔 사용함. |
| CMD | Windows | 🔵 좋음 | 저자가 많이 사용함. |
| PowerShell | Windows | 🟡 양호 | PowerShell 버전이 7.4 이상이면 그냥 작동합니다. |
| Zsh | Posix | 🟠 사소한 문제 | 논의에 따른 수정이 있습니다. |
| Fish | Posix | 🟠 사소한 문제 | 이슈 #55 및 논의 #22를 참조하세요. |