naroSEC
article thumbnail

개요

네이티브 코드는 안드로이드 애플리케이션에서 C/C++ 언어로 작성된 코드를 말한다. 안드로이드 애플리케이션은 주로 Java Kotlin 같은 고수준 프로그래밍 언어로 개발되지만, 일부 기능이나 성능 향상, 하드웨어 접근, 호환성을 이유로 네이티브 코드를 활용한다. 특히 게임분야에서 네이티브 코드가 가장 많이 활용되며, 이는 그래픽 처리, 음성 처리 등과 같은 작업에서 Java 코드 대비 높은 효율을 보이기 때문이다.

또한, C/C++과 같이 저수준 언어로 작성된 코드는 기계어 또는 어셈블리어로 컴파일되기에 가독성이 낮아 코드 분석 및 리버스 엔지니어링에 어려움을 준다. 때문에, 앱에 적용되는 대 다수의 보안 솔루션들은 네이티브 코드를 활용하여 루팅과 같은 모바일 보안 위협을 탐지한다. 그리고 이러한 보안 솔루션들은 탐지 시 사용되는 문자들을 평문이 아닌 16진수 값으로 저장하고 있다가 검사 함수 호출 시 별도의 변환 과정(시프팅 연산, 아스키 변환 등)을 거쳐 문자열로 변환된다.

해당 포스팅은 네이티브 함수 후킹 시 함수에서 사용되는 지역 변수 값에 어떠한 값들이 저장되어 있는지 확인할 때 사용하는 방법을 기술하고자 한다.


설명

아래의 [그림 1]은 A사의 보안 솔루션에서 사용되는 모듈의 코드 중 일부로 루팅 탐지 시 사용되며, 루팅 디바이스로 탐지 될 경우 [그림 1]의 sub_7CA4() 함수가 호출된다. 코드를 살펴보면 v0 변수에 28(1C)을 할당하고 해당 변수 값이 0이 될 때까지 do-while이 동작되며, 반복문 종료 시 앱을 강제 종료 시키는 sub_7B98() 함수를 호출하게 된다.

[그림 1]

아래의 [그림 2]는 [그림 1]의 코드를 순서도로 표현한 것으로, [그림 1]에서 할당된 v0 변수 값을 "loc_7CD0" 레이블에서 1 감소시키고, 그 결과가 0일 경우 "loc_7C98" 레이블로 점프하게 된다. 그리고 점프된 "loc_7C98" 레이블에서는 앱을 강제 종료시키는 sub_7B98() 함수를 호출하게 된다. 따라서, 해당 로직에서 "loc_7C98" 레이블로만 점프되지 않는다면 루팅에 탐지되더라도 앱은 강제 종료되지 않을 것이다.

[그림 2]

이제 Frida를 이용하여 "loc_7CD0" 레이블에서 사용되는 W19([그림 1]의 v0 변수) 레지스터 값이 0이 되어 "loc_7C98" 레이블로 점프되지 않도록 해당 값을 변조할 것이다. 그러기 위해서는 "loc_7CD0" 레이블이 위치한 주소를 먼저 알아야 한다. [그림 3]을 보면 "loc_7CD0" 레이블의 위치는 0x7CD0이다.

[그림 3]

[그림 4]는 [그림 3]에서 살펴본 "loc_7CD0" 레이블의 주소를 기반으로 후킹하는 Frida 스크립트이며, W19 레지스터 값이 2일 경우 [그림 1]에서 초기 설정한 28(16진수로 1C)로 다시 재할당 해주는 기능을 수행한다. 그리고 함수 내에서 사용되는 지역 변수(레지스터)에 저장된 값을 참조할 때는 [그림 4]의 "this.context"와 같이 해당 함수 객체의 컨텍스트 정보를 통해서 접근할 수 있다. 참고로 레지스터에 W 접두사가 붙은 경우 32비트 아키텍쳐에서 사용되고, X 접두사가 붙은 경우 64비트 아키텍쳐에서 사용된다. 필자의 디바이스 경우 ARM 64비트 아키텍쳐이기 때문에 x19로 명시해줬다.

[그림 4]

Frida 스크립트를 실행시키면 [그림 5]와 같이 W19 레지스터 값이 2가 될 때 1C로 재할당 되는 것을 볼 수 있다.

[그림 5]


마무리

이번 포스팅에서는 함수 내에서 사용되는 지역 변수(레지스터) 값을 참조하는 방법과 이를 또 변조하는 방법을 알아봤다. 모바일 앱 분석을 하다 보면 생각보다 보안 솔루션이 적용된 앱을 많이 접하게 되는데 대다수 탐지 시 사용하는 검사 문자열이 평문으로 저장되어 있지 않아 분석에 어려움을 준다. 물론 이 경우 원초적으로 open(), fopen(), strcmp() 등 문자열과 관련된 함수를 모두 후킹한 다음 인자로 전달되는 수상해보이는 문자가 발견되면 조건문을 통해 BackTrace 정보를 확인하여 하나씩 역추적해가는 방법이 있다. 다만, 이와 같은 방법은 결국 전달되는 인자만 확인할 수 있을 뿐 특정 함수 내에서 사용되는 레지스터 값은 확인할 수 없다. 따라서, 해당 포스팅에서 언급한 방법을 사용한다면 직접 레지스터에 저장된 값을 확인하여 조금 더 수월한 분석을 진행할 수 있고 또, 값 변조를 통해 우회도 가능해진다.

다음 포스팅에서는 앱에 적용되는 보안 솔루션의 모듈 분석에 도움이 되는 코드를 소개하겠다.

profile

naroSEC

@naroSEC

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

profile on loading

Loading...