Intel 계열이라면 질리도록 봐서 금방 하겠지만 ARM은 그리 만만하지 않았다.
마음같아서는 IDA의 Hex-rays 기능을 이용하고 싶지만 그래도 공부하는 게 좋겠다고 생각해서 이렇게 시작하게 되었다.
ARM의 레지스터 구조 등은 이미 BoB 등에서 공부했기에, 중요한 부분만 간략히 서술하고 들어간다.
분석할 "Hello, World!\n"를 출력하는 프로그램이다.
프로그래밍 처음 배우면서 누구나 접해 볼 프로그램이고, 리버싱을 처음 접할 때 분석하게 되는 프로그램이기도 한다.
ARM 서버 32비트에서 gcc로 컴파일했다.
(Thanks to 5unKn0wn)
명령어들은 다음과 같았다.
분석하면서 하나하나 정리해 보자.
ARM의 명령 실행 구조는 총 세 단계의 구조를 가지는데, 이를 Instruction Pipeline이라고도 한다.
이 단계는 명령어를 메모리에 적재하는 Fetch,
적재된 명령어를 해석하는 Decode,
해석된 명령어를 실행하는 Execute 로 나뉜다.
ARM에서는 이 세 가지의 과정이 한 번에 실행된다.
즉, 하나의 명령이 실행되는 도중(Execute)이라면 그 다음 실행될 명령어가 해석(Decode)되고 있으며,
또 그 다음에 실행될 명령어가 적재(Fetch)되고 있다는 의미이다.
ARM에서 레지스터는 r0 ~ r15까지 총 16개가 존재하는데, 이중 r13은 Stack Pointer로 사용되며,
r14는 리턴 어드레스 용도로 함수 이후 실행될 주소가 저장된다. 이를 lr(Link Register)라고 부른다.
또한 r15가 가장 중요한 Program Counter로, 현재 fetch 되고 있는 명령어의 주소를 의미한다.
push {r11, lr}
// push 명령은 stack에 값을 적재하는 역할을 하는데, 직접 실행해 본 결과 lr, r11 순서로 push하는 것을 알 수 있었다.
// main 마지막에 pop {r11, pc} 를 하는데, 여기에 다시 들어갈 값을 저장하는 함수 프롤로그 부분이라고 볼 수 있다.
add r11, sp, #4
// add 명령은 Rd에 Rn + Op2를 더해서 저장하는 역할을 한다. 여기서는 r11에 sp+4를 저장한다. (ARM에서 상수 표기는 앞에 #을 붙인다.)
// 즉 r11에 sp+4를 넣는다.
sub sp, sp, #8
// add와 형식은 같다. sp에서 8을 빼서 sp에 저장한다. sp -= 8 이라고 볼 수 있겠다.
// 스택 사용 공간을 할당하는 것이 아닐까. 쓸 일은 없겠지만.
str r0, [r11, #-8]
// 갑자기 방향이 바뀌어서 헷갈리겠지만 STR 명령은 *(Rn + Op2)에 Rd를 집어넣는 명령이다. 갑자기 방향이 바뀌었다. 이게 ARM에서 제일 짜증나는 부분이라고 한다.
str r1, [r11, #-12]
// 마찬가지로 *(r11 - 12) 에 r1을 집어넣는다.
ldr r0, [pc, #16] ; 0x10448 <main+44>
// ldr 명령은 Rd에 *(Rn + Op2)를 집어넣는다. 여기서는 r0에 *(pc+16)을 넣는다.
// 세미콜론 뒤에 0x10448이라고 씌여 있는데 이 값은 pc+16의 값이므로 이 내부에 있는 값을 확인하면 문자열의 주소가 들어 있는 것을 확인할 수 있다.
// 이 값을 확인해 보면 "Hello World!\n"가 들어 있다.
// 즉, 이 명령은 r0이라는 레지스터에 문자열의 주소를 집어넣는 명령이다.
bl 0x102c4
// bl은 Branch with Link의 약자이다. Branch는 Intel계열 Assembly에서 JMP와 비슷한 명령으로, 주소 분기 명령어이다. 이 명령을 수행하면서 BL은 자동으로 lr에 다음 명령의 주소를 저장한다.
mov r3, #0
// r3에 0을 집어넣는다.
mov r0, r3
// r0에 r3의 값을 넣는다. 즉, r0에도 0을 집어넣는다.
// return 0; 을 했었으므로 main의 리턴값을 r0에 넣은 것이라고 볼 수 있다.
sub sp, r11, #4
// sp에 r11-4를 집어넣는다.
pop {r11, pc}
// pc와 r11에 각각 pop을 한다.
// 직접 디버깅해보며 실행해 보니 이 명령을 수행한 직후 바로 __libc_start_main으로 진입한다. 즉 여기서 함수가 끝난다고 볼 수 있겠다.
andeq r0, r1, r0, asr #9
// 이 명령에 대한 정보는 아무리 찾아봐도 제대로 나와있지 않아서 잘 모르겠다.
// 혹시 아는 분이 있다면 알려주시면 감사하겠습니다.
실행시킨 결과로 아름다운 Hello, World! 가 출력된다.
이래서 언제 ARM을 다 공부할지 걱정이지만 그래도 계속 공부하자.
'Reversing' 카테고리의 다른 글
IsDebuggerPresent와 바이너리 패치 (0) | 2017.05.24 |
---|---|
64비트 Windows에서 32비트 악성코드 분석 시 유의할 점 (0) | 2017.01.04 |
[ARM] Reversing.kr HateIntel 분석 (0) | 2016.04.22 |
[ARM] 간단한 프로그램 분석 (0) | 2016.04.22 |
간단한 프로그램 분석 (0) | 2015.10.13 |