Why should I know this?

Linux custom packer analysis 본문

Knowledge/Linux

Linux custom packer analysis

die4taoam 2018. 2. 20. 09:17


Linux Custom Packer 분석




libandroid.so 를 분석하기 위해 IDA에서 열려고 하면, out of memory를 에러 메시지 알림창을 띄우고 열지 못합니다. 이유는 libandroid.so의 ELF Header에 기록된 메모리 상의 크기보다 파일 크기가 작기 때문에, 부족한 부분을 찾지 못해 발생시키는 에러입니다. 마찬가지의 이유로 readelf와 objdump 모두 libandroid.so를 정상적으로 분석하지 못합니다.





readelf 등의 도구를 통해 section 정보와 segment 정보를 확인하고,






실제 파일 사이즈를 확인해보면 크기 차이가 크다는 것을 알 수 있습니다.





이런 형식의 SO파일이 메모리에 로드되면 segment 정보에 따라 메모리는 할당되지만, 
실제 파일에는 내용이 없기 때문에 빈 공간이 자리잡게 됩니다.

이것은 일반적으로 컴파일되어 생성된 ELF 파일에는 존재하지 않을 것이므로 조작된 해더임을 추측할 수 있습니다. libandroid.so에서 읽을 수 정보들을 읽다보면 relocation이 굉장히 많이 존재하고, 마찬가지로 일반적으로 존재할 수 없는 relocation 내역들임을 알 수 있습니다.


relocation을 사용하여 복호화 하는 스크립트를 작성하는 것도 한 방법이지만, 이번 경우에는 파일에 ELF해더에 기록 내용보다 은닉된 곳이 많으므로 사용할 수 없는 방법입니다. 우선 은닉된 데이터를 찾아야 합니다. relocation을 사용하여 packing을 했을 경우, relocation 작업이 SO가 로드되는 타이밍에 이뤄지므로, 이보다 우선적으로 은닉된 데이터를 메모리에 풀어놓아야 시스템의 relocation을 정상적으로 수행할 수 있으므로 _libc_start나 constructor와 같은 수단을 통해 데이터를 풀 것입니다.

이번 경우에는 dlopen을 통해 SO를 열어 은닉된 데이터를 dump할 수 있었습니다. 
(매우 운이 좋은 경우로, 보통은 데이터를 복구하기 전에 안티디버깅, 환경체크 등을 하기 때문에 사용할 수 없는 경우가 많습니다. )




위는 dlopen을 수행하는 코드이며, 동시에 JNI_OnLoad를 수동으로 호출하는 로직이 추가되어 있습니다. 이를 통해

libandroid.so가 은닉된 데이터를 풀고 JNI_OnLoad에서 수행하는 동작을 추적할 수 있게 될 겁니다.




위의 프로그램으로 libandroid.so 영역을 메모리에서 dump하면 IDA에서 열어 분석을 할 수 있습니다.




JNI_OnLoad를 찬찬히 훑어보다 보면, 특정 함수들의 주소를 참조하는 것을 확인할 수 있습니다. 실제로 호출하지는 않고 주소를 보관하는 것은 무슨 의도가 있기 때문일 겁니다. 나머지 부분은 필요한 함수들을 찾아 자체 function address table을 구축하는 과정으로 추측합니다. 굉장히 지루한 분석이므로 생략했습니다. 


참고로 cacheflush는 굉장히 중요한 키워드가 될 수 있는게, cacheflush 명령어의 이름처럼 해당 명령어는 cache의 내용을 메모리에 flush를 강제하는 명령어기 때문입니다. 이런 명령어는 자주 사용되지 않습니다. 보통은 CPU의 cache를 다룰 일이 없으니까요. custom packing은 본래 존재하는 명령어들을 은닉시키고 특정 시점에 메모리에 푼 뒤에 실행하는 구조를 취합니다. 


그런데 이 시점에 메모리에 복사해 놓은 instruction들을 cpu가 cache에 보관하고 메모리에 반영하지 않을 경우. cpu의 실행엔진이 해당 메모리를 fetch할 때는 복사한 instruction이 아닌 값들을 읽어와 illegal instruction 을 발생시킬 확률이 있습니다. 때문에 cacheflush와 같은 명령을 통해 특정 메모리 영역을 CPU의 CACHE와 MEMORY의 공간이 일치하도록 강제하는 겁니다. 


즉, cacheflush 명령을 통해 cache와 memory의 동일성을 강제하는 공간은 unpacking된 실행코드들이 위치하는 공간일 가능성이 높습니다.

이 점을 주지하시면 좋습니다!








해당 과정이 마무리 되고 나면, 제작한 function address table을 참조하여 본격적인 주 작업을 착수하는 낌새를 차릴 수 있습니다. 





별 필요 없지만, indirect call들을 특정 table을 통해 호출하는 분석해주는 IDA의 아름다운 자테를 첨부해봤습니다.




실제로 해당 indirect call들을 추적해보면 주소가 libandroid.so 영역을 벗어나는 것을 확인할 수 있습니다.





보세요. libc.so의 주소를 참조하고 있죠? 

레지스터의 내용을 보십시오.  0x1209744 부터 0x100만큼 0x0으로 set하는 memset을 호출하는 코드였습니다. 







또한 이 커스텀 패커는 arm 기반 환경에서 system call을 직접 호출하고 있다...


ARM에서 R7이 Table number를 인자로 취하게 되는데

R7 = Table number


이처럼 하드코딩된 테이블 넘버로 시스템 콜을 하는 방식은 매우 불안정하고 특정 환경 종속적이므로 개발자라면 절대 사용할 수 있는 방식이 아니다.


다음은 Android의 시스템 콜 테이블이다.


Android System call table (AOSP 4.3_r1.1 for grouper) : 

android-tegra3-grouper-3.1-jb-mr1.1

path : kernel/arch/arm/include/asm/unistd.h


https://android.googlesource.com/kernel/tegra/+/android-tegra3-grouper-3.1-jb-mr1.1/arch/arm/include/asm/unistd.h


#define __NR_mmap2 (__NR_SYSCALL_BASE+192)


0xc0 = 192 = MMAP으로 메모리 매핑을 하는 시스템 콜이다.

즉 이 커스텀 패커라는 녀석은 Import Table을 재구성하기 위해서 시스템 콜을 직접 호출하여 메모리를 매핑하고 있습니다. 또한 더 골때리는 것은 이 녀석은 특정 flag를 참조하여 조건이 일치하지 않으면 직접 SO파일을 변경하여 다시 실행할 수 없게 만든다. -_-;;;




위처럼 헤더 일부를 변경하여 다음 실행부터는 정상 unpacking이 되지 않도록 만든다.



다음처럼 헤더 일부를 변경하여 다음 실행부터는 unpacking과정이 정상 수행되지 못하도록 만든다.



출처: https://die4taoam.tistory.com/20 [Why should I know this?]

다음처럼 헤더 일부를 변경하여 다음 실행부터는 unpacking과정이 정상 수행되지 못하도록 만든다.



출처: https://die4taoam.tistory.com/20 [Why should I know this?]


Comments