Ghidra 화면 왼쪽엔 어셈블리, 오른쪽엔 C 코드. 뭘 봐야 하나?
디스어셈블리 (Listing)
바이너리를 어셈블리 명령어로 변환한 것.
08002800 10 b5 push { r4, lr }
08002802 04 4c ldr r4, [DAT_08002814]
08002804 20 46 mov r0, r4
08002806 00 f0 4d f8 bl FUN_080028a4
08002810 10 bd pop { r4, pc }
장점: 정확하다. CPU가 실행하는 그대로. 단점: 읽기 어렵다. C보다 10배 길다.
디컴파일 (Decompile)
어셈블리를 C 유사 코드로 변환한 것. Ghidra의 킬러 기능.
void FUN_08002800(void)
{
FUN_080028a4(DAT_08002814);
FUN_080028f4(DAT_08002814);
return;
}
장점: 읽기 쉽다. 단점: 100% 정확하지 않다. 최적화된 코드는 이상하게 나온다.
뭘 써야 하나
시작은 디컴파일, 이상하면 디스어셈블리 확인.
디컴파일 결과가 이상할 때:
- 변수 타입이 틀림
- 반복문 구조가 깨짐
- 조건문이 뒤집힘
그럴 때 어셈블리 보면 진짜 동작이 보인다.
ARM 기본 명령어
자주 보이는 것들:
push { r4, lr } ; 레지스터 저장
pop { r4, pc } ; 복원 후 리턴
bl FUN_xxx ; 함수 호출
ldr r0, [r1] ; 메모리 읽기
str r0, [r1] ; 메모리 쓰기
mov r0, r1 ; 복사
cmp r0, #0 ; 비교
beq label ; 같으면 점프
실전 예시
디컴파일:
if (*(int *)0x40006400 == 0) {
// ...
}
뭔지 모르겠다. 어셈블리 보면:
ldr r0, =0x40006400 ; CAN1 base address
ldr r1, [r0, #0x0] ; CAN_MCR 읽기
cmp r1, #0
beq somewhere
0x40006400이 CAN1 베이스 주소라는 걸 알면 이해된다.
다음 글에서 Vector Table 분석.