ARM의 코드 실행 모드는 x86과는 다르게 2 or 4바이트씩 처리하는 thumb모드, 4바이트씩 처리하는 arm모드, 두 종류로 나뉩니다.

안드로이드에서 동작하는 대부분의 프로그램(executable)들은 arm과 thumb이 결합된 모드로 컴파일 되어 있습니다.

한 프로그램 내에서 함수에 따라 모드가 달라질 수 있습니다.

아래의 예시 그림은 동일한 파일임에도 각각 다른 모드로 컴파일 되었음을 보여줍니다

THUMB MODE

ARM MODE


x86 후킹과 달리 ARM 후킹은 코드 재배치를 고려해야 하는 경우가 많습니다.

그 이유는 x86은 점프에 필요한 바이트가 단지 5바이트이고,  함수의 프롤로그가 아래와 유사한 경우가 많습니다.

8B FF MOV EDI, EDI

55     PUSH EBP

88 EC MOV EBP, ESP

위의 5바이트는 매우 단순하며, 점프 주소에 복사되어 실행되어도 아무런 문제가 없습니다.


반면, THUMB모드 후킹의 경우는 8바이트가 필요하며(명령어 4바이트, 점프 주소 4바이트), 함수의 프롤로그가 아래와 유사한 경우가 많습니다. (arm 모드 후킹은 추후 다루겠습니다.)

F0 B5 PUSH { R4-R7, LR }

위의 2바이트는 점프 주소에 복사되어 실행되어도 문제없어 보이지만, 이어지는 6바이트가 실행될 때 crash가 야기될 수 있습니다.

아래와 같은 함수가 있다고 가정합니다.

F0 B5 PUSH { R4-R7, LR}

1F 1C MOVS R7, R3

A8 23 MOVS R3, #168

78 4C LDR R4, [PC, #480]

... 후략


7, 8바이트에서 PC + 480 의 값을 참조합니다. 만약 해당 코드가 점프 주소에 그대로 복사되어 실행된다면 엉뚱한 값을 참조하게 되어 결국 의도하지 않은 지점에서 crash가 발생하게 될 것입니다. 

이해를 돕기 위한 그림을 첨부합니다.



이러한 문제들이 발생할 수 있는 가능성이 있기 때문에, 코드 재배치를 해주어야 합니다.

재배치가 고려되어야 할 코드들은 아래와 같습니다.

 THUMB16

 THUMB32

ARM 

 B <label>

 심심할때 작성..

심심할때 작성.. 

 BX PC

   

 ADD <Rdn>, PC

   

 MOV Rd, PC

   

 ADR Rd, <label>

   

 LDR Rt, <label>

   

 CB{N}Z <Rn>, <label>

 


오리지널 코드 4번째 줄에 LDR이 보이므로, 코드 재배치를 해야 합니다.

우선 특정 주소에서 값을 가져와서 r4에 저장할 코드의 일반적인 형태는 다음과 같습니다.

00 4C  ldr r4, [PC] <-- 0xdeadbeef를 취함

01 E0  B PC, #2 <-- 값을 취했으므로 다음 코드를 실행 (B PC, #2)

ef be ad de  <-- 원래의 함수에서 의도된 값

0xdeadbeef는 아래와 같은 수식으로 취할 수 있습니다.

value = (unsigned int*) (Align(pc) + (앞 1바이트 * 2));


구체적으로 풀어보면,

0x78 0x4c에서 0x78만 취한다음, *2를 합니다. (=480) 그 다음 pc를 4바이트 단위로 align합니다.


align 예시)

 PC 

 PC align

 0x87F0 

 0x87F0 

 0x87F2

 0x87F0 

 0x87F4

 0x87F4 

 0x87F6 

 0x87F4 

 0x87F8

 0x87F8


0x87F2:  ldr     r4, [pc, #480]  일 경우, pc는 0x87F4가 됩니다.


0x87F4(align 하였음) + 480(0x1e0) = 0x89d4 를 살펴보면..

0x89D4: C4 FA FF FF     가 보이네요.  

0xFFFFFAC4를 취하면 됩니다. (위의 그림 참조)


아래와 같이 재배치를 할 수 있습니다. 재배치가 필요 없는 코드는 그대로 복사하면 됩니다. (malloc 후에 재배치)


0x789e0001:  f0 b5   push    {r4, r5, r6, r7, lr}

0x789e0003:  1f 1c   adds    r7, r3, #0

0x789e0005:  a8 23   movs    r3, #168     

0x789e0007:  00 4c   ldr     r4, [pc, #0]    ; (0x789e0008) 

0x789e0009:  01 e0   b.n     0x789e000e 

0x789e000b:  c4 fa ff ff   


그런데 뭔가 이상하군요. 0x789e0007 를 보시면 pc + 0x0라인 지점이 영 좋지 않은 곳을 가리켜서 crash가 발생합니다. 의도한대로라면 0x789e000b을 가리켜야 하는데 말이죠. 

이는 2바이트씩 align이 되어 발생한 문제입니다. 이를 해결하려면 thumb16 코드(4바이트 코드는 thumb32라 합니다.) 중간중간에 nop를 삽입시켜주면 됩니다.

0x78dfc001:  f0 b5   push    {r4, r5, r6, r7, lr}

0x78dfc003:  00 bf   nop

0x78dfc005:  1f 1c   adds    r7, r3, #0

0x78dfc007:  00 bf   nop

0x78dfc009:  a8 23   movs    r3, #168        ; 0xa8

0x78dfc00b:  00 bf   nop

0x78dfc00d:  00 4c   ldr     r4, [pc, #0]    ; (0x78dfc010) // (thumb모드이므로 해석된 주소에서 +1씩 더해서 계산해야 합니다.)

0x78dfc00f:  01 e0   b.n     0x78dfc014

0x78dfc011:  c4 fa ff ff         


이제 잘 동작합니다. GOOD! 



[TIP]

gdb 사용시 align을 맞추기 위해 아래 명령어를 실행해 주세요. 한 바이트씩 밀리는 것을 방지하여 줍니다.

(gdb) set arm fallback-mode thumb 

특정 주소를 보는 방법은 dias /r 0xStart 0xEnd

+ Recent posts