Frida와 Go 통합 경험담
Frida와 Go 통합 경험담
Android 앱을 동적으로 분석하는 도구를 만들면서, Frida를 Go에서 네이티브로 사용하게 됐다. CLI subprocess 방식에서 frida-go 바인딩으로 전환한 과정을 기록한다.
왜 Go + Frida인가
Python이 Frida의 주력 언어지만, 배포 편의성 때문에 Go를 선택했다.
- 단일 바이너리 배포: Python 환경 설정 불필요
- 크로스 플랫폼: macOS, Linux, Windows 동시 지원
- 성능: 네이티브 바이너리
처음에는 Go에서 Frida CLI를 subprocess로 호출했다. 작동은 했지만 문제가 많았다.
// 초기 방식 - subprocess
cmd := exec.Command("frida", "-U", "-l", "hook.js", "-f", packageName)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Run()
문제점:
- Frida CLI 별도 설치 필요
- 에러 핸들링이 어려움
- 메시지 파싱이 불편
- 프로세스 관리 복잡
frida-go 바인딩
frida-go는 Frida의 공식 Go 바인딩이다. CGO를 사용해서 frida-core C 라이브러리를 직접 호출한다.
import "github.com/frida/frida-go/frida"
// 디바이스 연결
mgr := frida.NewDeviceManager()
device, err := mgr.DeviceByType(frida.DeviceTypeUsb)
// 프로세스에 attach
session, err := device.Attach(pid, nil)
// 스크립트 주입
script, err := session.CreateScript(hookScript)
script.On("message", func(message string) {
// Go 함수로 메시지 처리
})
script.Load()
훨씬 깔끔하다. 에러 핸들링도 Go 방식으로 할 수 있고, 메시지도 콜백으로 받을 수 있다.
CGO 빌드 설정
frida-go는 CGO를 사용하므로 빌드가 복잡하다. Frida devkit을 다운로드해서 링크해야 한다.
1. Frida Devkit 다운로드
# macOS arm64 예시
FRIDA_VERSION="17.0.5"
curl -LO "https://github.com/frida/frida/releases/download/${FRIDA_VERSION}/frida-core-devkit-${FRIDA_VERSION}-macos-arm64.tar.xz"
tar xf frida-core-devkit-*.tar.xz -C ~/.kyobo/frida-devkit/
2. Makefile 설정
FRIDA_DEVKIT_DIR := $(HOME)/.kyobo/frida-devkit
build:
CGO_ENABLED=1 \
CGO_CFLAGS="-I$(FRIDA_DEVKIT_DIR)" \
CGO_LDFLAGS="-L$(FRIDA_DEVKIT_DIR) -lfrida-core -lm -ldl -lpthread" \
go build -o bin/app main.go
3. macOS 링커 경고 억제
macOS에서 중복 라이브러리 경고가 발생한다.
CGO_LDFLAGS="... -Wl,-no_warn_duplicate_libraries"
Spawn vs Attach
Frida에는 두 가지 연결 방식이 있다.
Spawn 모드
앱을 Frida가 직접 실행하고 attach.
pid, err := device.Spawn(packageName, nil)
session, err := device.Attach(pid, nil)
device.Resume(pid)
문제: jailed Android에서는 Gadget이 필요하다.
Error: Need Gadget to attach on jailed Android
Launch + Attach 모드
ADB로 앱을 먼저 실행하고, 실행 중인 프로세스에 attach.
// 1. ADB로 앱 실행
exec.Command("adb", "shell", "am", "start", "-n",
"com.example.app/.MainActivity").Run()
// 2. PID 획득
output, _ := exec.Command("adb", "shell", "pidof", packageName).Output()
pid, _ := strconv.Atoi(strings.TrimSpace(string(output)))
// 3. Attach
session, err := device.Attach(pid, nil)
Rooted 에뮬레이터에서는 이 방식이 Gadget 없이 작동한다.
스크립트 임베딩
Go의 //go:embed를 사용해서 Frida 스크립트를 바이너리에 포함시켰다.
//go:embed hook.js
var hookScript string
//go:embed java.js
var javaBridge string
func injectHook(session *frida.Session) error {
// java.js는 frida-tools의 Java 브릿지
fullScript := javaBridge + "\n" + hookScript
script, err := session.CreateScript(fullScript)
if err != nil {
return err
}
return script.Load()
}
java.js는 frida-tools/bridges에서 빌드한 Java API 브릿지다. Java.perform() 등의 API를 제공한다.
메시지 핸들링
Frida 스크립트에서 send()로 보낸 메시지를 Go에서 받는다.
// hook.js
send({type: 'file_created', path: '/sdcard/Download/output.pdf'});
script.On("message", func(message string) {
var msg struct {
Type string `json:"type"`
Payload struct {
Type string `json:"type"`
Path string `json:"path"`
} `json:"payload"`
}
json.Unmarshal([]byte(message), &msg)
if msg.Payload.Type == "file_created" {
// 파일 다운로드 처리
downloadFile(msg.Payload.Path)
}
})
Cleanup 최적화
종료 시 cleanup이 5-10초 이상 걸리는 문제가 있었다.
// 문제 코드
func (m *Manager) cleanup() {
m.script.Unload() // 느림
m.session.Detach() // 느림
}
해결: 타임아웃 + 병렬 실행
func (m *Manager) cleanup() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
m.script.Unload()
}()
go func() {
defer wg.Done()
m.session.Detach()
}()
done := make(chan struct{})
go func() {
wg.Wait()
close(done)
}()
select {
case <-done:
// 정상 완료
case <-ctx.Done():
// 타임아웃
}
}
이제 최대 2초 내 종료.
바이너리 크기
최종 바이너리는 약 122MB.
총 122 MB
├── 108 MB - frida-core 정적 라이브러리 + 의존성
├── 8 MB - Go 런타임 + 코드
└── 6 MB - Java 브릿지 + 후킹 스크립트
크긴 하지만, Python + Frida 환경 설정하는 것보다 낫다.
결론
frida-go 전환 후:
- 배포 단순화: 단일 바이너리만 배포
- 에러 핸들링 개선: Go 방식으로 처리
- 메시지 처리: 콜백으로 직접 수신
- 빌드 복잡성 증가: CGO + devkit 관리 필요
CGO 빌드가 복잡하지만, 배포 편의성이 이를 상쇄한다.
Frida를 Go에서 쓰고 싶다면 frida-go를 추천한다. 다만 CGO 빌드에 대한 이해가 필요하다.