
스크립트 가능한 바이너리 에뮬레이션 프레임워크로, IDA Pro/Radare2와 Unicorn 엔진을 통합하여 x86, ARM, ARM64 아키텍처에서 자동화된 악성코드 분석, 문자열 복호화, 코드 경로 탐색을 수행합니다.
flare-emu는 IDA Pro 또는 Radare2와 같은 지원되는 바이너리 분석 프레임워크를 Unicorn의 에뮬레이션 프레임워크와 결합하여 사용자가 에뮬레이션 작업을 스크립팅할 수 있는 사용하기 쉽고 유연한 인터페이스를 제공합니다. 지원되는 아키텍처에 대해 유연하고 강력한 에뮬레이터를 설정하는 모든 관리 작업을 처리하도록 설계되어 코드 분석 문제 해결에 집중할 수 있습니다. 현재 flare-emu는 x86, x86_64, ARM 및 ARM64 아키텍처를 지원합니다.
현재 다수의 관련 헬퍼 및 유틸리티 함수와 함께 에뮬레이션 요구를 충족시키기 위한 다섯 가지 인터페이스를 제공합니다.
emulateRange – 이 API는 사용자가 지정한 컨텍스트 내에서 명령어 범위 또는 함수를 에뮬레이션하는 데 사용됩니다. 개별 명령어 및 "call" 명령어가 발견될 때 사용자 정의 훅을 위한 옵션을 제공합니다. 사용자는 에뮬레이터가 함수 호출을 건너뛸지 또는 호출할지를 결정할 수 있습니다. 이 인터페이스는 사용자가 특정 레지스터 및 스택 인수에 값을 쉽게 지정할 수 있는 방법을 제공합니다. 바이트 문자열이 지정되면 에뮬레이터의 메모리에 기록되고 포인터는 레지스터 또는 스택 변수에 기록됩니다. 에뮬레이션 후 사용자는 flare-emu의 유틸리티 함수를 사용하여 에뮬레이션된 메모리나 레지스터에서 데이터를 읽거나 반환된 Unicorn 에뮬레이션 객체를 직접 조사할 수 있습니다. emulateRange의 작은 래퍼 함수인 emulateSelection을 사용하여 IDA Pro에서 현재 강조 표시된 명령어 범위를 에뮬레이션할 수 있습니다.
iterate - 이 API는 주어진 대상에 도달하기 위해 함수 내 특정 분기를 따라 에뮬레이션을 강제하는 데 사용됩니다. 사용자는 대상 주소 목록 또는 함수의 주소를 지정할 수 있으며, 해당 함수에 대한 상호 참조 목록이 대상으로 사용됩니다. 대상에 도달하면 호출되는 콜백도 제공됩니다. 에뮬레이션 중 다른 분기를 유발할 수 있는 조건에 관계없이 대상에 도달합니다. emulateRange API와 마찬가지로 개별 명령어 및 "call" 명령어가 발견될 때 사용자 정의 훅을 위한 옵션이 제공됩니다. iterate API의 사용 예는 argtracker 도구가 수행하는 것과 유사한 작업을 달성하는 것입니다.
iterateAllPaths - 이 API는 iterate와 매우 유사하지만, 대상 주소를 제공하는 대신 모든 경로를 찾아 에뮬레이션할 대상 함수를 제공합니다. 이는 함수의 모든 기본 블록에 도달하려는 코드 분석을 수행할 때 유용합니다.
emulateBytes – 이 API는 간단히 외부 셸코드 블롭을 에뮬레이션하는 방법을 제공합니다. 제공된 바이트는 IDB에 추가되지 않고 그대로 에뮬레이션됩니다. 이는 에뮬레이션 환경을 준비하는 데 유용할 수 있습니다. 예를 들어, flare-emu 자체는 Unicorn에서 노출되지 않는 ARM64 CPU의 Model Specific Register (MSR)를 조작하여 Vector Floating Point (VFP) 명령어 및 레지스터 액세스를 활성화하기 위해 이 API를 사용합니다. Unicorn 에뮬레이션 객체가 반환되어 사용자가 추가로 조사할 수 있습니다.
emulateFrom - 이 API는 난독화된 바이너리나 셸코드에서 종종 발생하는 것처럼 함수 경계가 명확하게 정의되지 않은 경우에 유용합니다. 시작 주소를 제공하면 에뮬레이션할 것이 남지 않거나 훅 중 하나에서 에뮬레이션을 중지할 때까지 에뮬레이션합니다. IDA Pro를 사용하는 경우 strict 매개변수를 False로 설정하여 호출하면 동적 코드 발견이 활성화됩니다. flare-emu는 에뮬레이션 중에 명령어가 발견될 때마다 IDA Pro가 명령어를 생성하도록 합니다.
flare-emu를 IDA Pro에 설치하려면 flare_emu.py, flare_emu_ida.py, flare_emu_hooks.py 파일을 IDA Pro의 python 디렉토리에 넣고 IDAPython 스크립트에서 모듈로 임포트하기만 하면 됩니다.
flare-emu를 Rizin에 설치하려면 flare_emu.py, flare_emu_rizin.py, flare_emu_hooks.py 파일이 Python의 모듈 검색 경로에 있는지 확인하기만 하면 됩니다. flare-emu의 바이너리 분석 구성 요소로 Rizin을 사용할 때는 rzpipe가 필요합니다.
flare-emu를 Radare2에 설치하려면 flare_emu.py, flare_emu_radare.py, flare_emu_hooks.py 파일이 Python의 모듈 검색 경로에 있는지 확인하기만 하면 됩니다. flare-emu의 바이너리 분석 구성 요소로 Radare2를 사용할 때는 r2pipe가 필요합니다.
어쨌든 flare-emu는 Unicorn과 그 Python 바인딩에 의존합니다.
중요 참고 사항
flare-emu는 새로운 IDA Pro 7x API를 사용하여 작성되었으며, 이전 버전의 IDA Pro와 역호환되지 않습니다.
flare-emu는 다양한 코드 분석 문제를 해결하는 데 사용될 수 있지만, 가장 일반적인 용도 중 하나는 악성코드 바이너리에서 문자열을 복호화하는 데 도움을 주는 것입니다. FLOSS는 문자열 복호화 함수를 식별하고 각 상호 참조에서 전달된 문자열을 에뮬레이션을 통해 복호화하여 자동으로 이를 수행할 수 있는 훌륭한 도구입니다. 그러나 FLOSS가 항상 이러한 함수를 식별하고 일반적인 접근 방식으로 제대로 에뮬레이션할 수 있는 것은 아닙니다. 때로는 조금 더 작업을 해야 하며, 이때 flare-emu에 익숙해지면 많은 시간을 절약할 수 있습니다. 암호화된 문자열을 다룰 때 악성코드 분석가가 직면하는 일반적인 시나리오를 살펴보겠습니다.
x86_64 바이너리에서 모든 문자열을 복호화하는 함수를 식별했습니다. 이 함수는 곳곳에서 호출되며 다양한 문자열을 복호화합니다. IDA Pro에서 이 함수의 이름을 decryptString으로 지정했습니다. 다음은 모든 문자열을 복호화하고 각 함수 호출 지점에 복호화된 문자열을 주석으로 추가하고 각 복호화된 문자열과 복호화된 주소를 로깅하는 flare-emu 스크립트입니다.```
from future import print_function
import flare_emu
def decrypt(argv): myEH = flare_emu.EmuHelper() myEH.emulateRange(myEH.analysisHelper.getNameAddr("decryptString"), registers = {"arg1":argv[0], "arg2":argv[1], "arg3":argv[2], "arg4":argv[3]}) return myEH.getEmuString(argv[0])
def iterateCallback(eh, address, argv, userData): s = decrypt(argv) print("%s: %s" % (eh.hexString(address), s)) eh.analysisHelper.setComment(address, s, False)
if name == 'main':
eh = flare_emu.EmuHelper()
eh.iterate(eh.analysisHelper.getNameAddr("decryptString"), iterateCallback)
`__main__`에서 `flare-emu`의 `EmuHelper` 클래스 인스턴스를 생성하는 것으로 시작합니다. 이 클래스를 사용하여 `flare-emu`로 모든 작업을 수행합니다. 그런 다음 `iterate` API를 사용하여 `decryptString` 함수의 주소와 `EmuHelper`가 에뮬레이션된 각 교차 참조에 대해 호출할 콜백 함수의 이름을 전달합니다.
`iterateCallback` 함수는 `eh`라는 이름의 EmuHelper 인스턴스, 교차 참조의 주소, 이 특정 호출에 전달된 인수, 그리고 여기서 `userData`라는 특수 사전을 받습니다. `userData`는 이 간단한 예제에서는 사용되지 않지만, 에뮬레이터에 대한 지속적인 컨텍스트로 생각하여 사용자 정의 데이터를 저장할 수 있습니다. 하지만 주의하세요. `flare-emu` 자체도 이 사전을 사용하여 작업 수행에 필요한 중요한 정보를 저장합니다. 그러한 데이터 중 하나는 "EmuHelper" 키에 저장된 `EmuHelper` 인스턴스 자체입니다. 관심이 있다면 소스 코드를 검색하여 이 사전에 대해 더 알아보세요. 이 콜백 함수는 단순히 `decrypt` 함수를 호출하고, 복호화된 문자열을 출력하며, `decryptString` 호출 주소에 주석을 추가합니다.
`decrypt`는 두 번째 `EmuHelper` 인스턴스를 생성하여 `decryptString` 함수 자체를 에뮬레이션하며, 이를 통해 문자열을 복호화합니다. 이 `decryptString` 함수의 프로토타입은 다음과 같습니다: `char * decryptString(char *text, int textLength, char *key, int keyLength)`. 이 함수는 문자열을 제자리에서 복호화합니다. 우리의 `decrypt` 함수는 `iterateCallback` 함수에서 받은 인수를 `EmuHelper`의 `emulateRange` API 호출에 전달합니다. 이 바이너리는 `x86_64`이므로 호출 규약이 스택이 아닌 레지스터를 사용하여 인수를 전달합니다. `flare-emu`는 IDA Pro가 결정한 바이너리의 아키텍처와 파일 형식을 기반으로 어떤 레지스터가 어떤 인수를 나타내는지 자동으로 결정하므로, 어느 정도 아키텍처에 독립적인 코드를 작성할 수 있습니다. 만약 32비트 `x86`이라면 `stack` 인수를 사용하여 인수를 전달해야 합니다. 예를 들어: `myEH.emulateRange(myEH.analysisHelper.getNameAddr("decryptString"), stack = [0, argv[0], argv[1], argv[2], argv[3]])`. 첫 번째 스택 값은 `x86`에서 반환 주소이므로 여기서는 자리 표시자로 `0`을 사용합니다. 에뮬레이션이 완료되면 `getEmuString` API를 호출하여 함수에 전달된 첫 번째 인수가 가리키는 메모리 위치에 저장된 null 종료 문자열을 검색합니다.
### flare-emu and idalib
* IDA Pro 설치
* Hex-Rays 사용자 가이드에 따라 idalib 설치
* (가상 환경 활성화)
* pip install /path/to/IDA/installation/idalib/python
* python /path/to/IDA/installation/idalib/python/py-activate-idalib.py [-d /path/to/active/IDA/installation]
* idapro를 임포트하고 스크립트 작성
* 예제는 tests/test_flare_emu_idalib.py 참조
### Rizin을 사용한 쉬운 문자열 복호화 시나리오
위의 동일한 예제를 사용할 때, IDA Pro 대신 Rizin으로 작업해도 크게 달라지지 않습니다. 한 가지 차이점은 `flare-emu`가 현재 Rizin으로 작업할 때 명령줄 스크립트 또는 Python 셸 내에서 실행되도록 설계되었다는 것입니다. Python 셸은 임시 문제 해결에 적합하며, 명령줄 스크립트는 일괄 처리에 적합합니다. 위 스크립트의 Rizin 버전은 다음과 같습니다 (rizin 내에서 실행하려면 샘플 경로를 생략할 수 있습니다):```
from __future__ import print_function
import sys
import flare_emu
def decrypt(argv, eh):
myEH = flare_emu.EmuHelper(samplePath=sys.argv[1], emuHelper=eh, isRizin=True)
myEH.emulateRange(
myEH.analysisHelper.getNameAddr("decryptString"),
registers={
"arg1": argv[0],
"arg2": argv[1],
"arg3": argv[2],
"arg4": argv[3],
},
)
return myEH.getEmuString(argv[0])
def iterateCallback(eh, address, argv, userData):
s = decrypt(argv, eh)
print("%s: %s" % (eh.hexString(address), s))
eh.analysisHelper.setComment(address, s, False)
if __name__ == "__main__":
eh = flare_emu.EmuHelper(samplePath=sys.argv[1], isRizin=True)
rz = eh.analysisHelper.r
eh.analysisHelper.setName(0x100000D60, "decryptString")
eh.iterate(eh.analysisHelper.getNameAddr("decryptString"), iterateCallback)
위와 동일한 예제를 사용할 때, IDA Pro 대신 Radare2로 작업해도 큰 변화는 없습니다. 한 가지 차이점은 flare-emu가 현재 Radare2와 함께 사용될 때 명령줄 스크립트 또는 Python 셸 내에서 실행되도록 설계되었다는 것입니다. Python 셸은 임시 문제 해결에 적합한 반면, 명령줄 스크립트는 배치 처리에 적합합니다. 위 스크립트의 Radare2 버전은 다음과 같습니다:```
from future import print_function
import flare_emu