0. 학습
모바일 보안, 특히 안드로이드 네이티브 레벨의 보안(리버싱 방지, 취약점 분석, 악성코드 분석 등)을 공부하기 위해서는 NDK는 필수적인 관문이다. 안드로이드 앱의 핵심 로직이나 보안 모듈은 주로 C/C++로 작성되며 '. so' 파일 형태로 존재하기 때문이다.
1. NDK 기초와 JNI 메커니즘
먼저 안드로이드 스튜디오 설치 후
경로: File > Settings (macOS는 Android Studio > Settings) > Languages & Frameworks > Android SDK.

학습에 필요한 필수 항목들을 체크하여 설치 해준다.
- NDK (Side by side): 네이티브 코드를 빌드하는 핵심 도구.
- CMake: 빌드 스크립트를 관리하는 도구.
- LLDB: 네이티브 코드 전용 디버거 (보안 분석 시 필수) => 최신 안드로이드 스튜디오에서는 이미 깔려있음.
네이티브 코드 : 컴퓨터 프로세서(CPU) 나 운영체제(OS)에서 직접 실행되도록 컴파일된 기계어 형태의 코드
(ex. C, C++ , Swift, Kotlin)
2. 프로젝트 생성
경로: New Project → Native C++ 선택 → 기본 설정으로 완료.

생성을 하면 주요 파일 목록이 뜬다.
| app/src/main/cpp | C/C++ 소스 코드가 위치하는 곳 |
| app/src/main/cpp/CMakeLists.txt | 빌드 규칙 파일이다. 어떤 C++파일을 .so 라이브러리로 만들지 정의한다. |
| app/src/main/java | 자바/코틀린 소스 코드 위치이다. C++ 함수를 호출하는 '입구' 같은 역할 |
| app/build.gradle | NDK 버전을 지정하고 CMake와 연결 설정을 하는 곳이다. |
3. JNI (Java Native Interface)
자바(상위 레이어)와 C++(하위 레이어)는 서로 언어가 다르기 때문에 '통역사' 같은 존재가 필요하다 그 규칙이 JNI이다.
=> 안드로이드 앱(Java, Kotlin)과 C++(Native)를 연결하는 통로(Bridge)이다.
3-1.MainActivity
package com.example.myapplication
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
import com.example.myapplication.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Example of a call to a native method
binding.sampleText.text = stringFromJNI()
}
/**
* A native method that is implemented by the 'myapplication' native library,
* which is packaged with this application.
*/
external fun stringFromJNI(): String
companion object {
// Used to load the 'myapplication' library on application startup.
init {
System.loadLibrary("myapplication")
}
}
}
3-2.native-lib.cpp => JNI의 실체
- Java ↔ C++ 연결선
- 후킹 포인트
- 보안 로직 진입점
//native-lib.cpp 발췌
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
std::string security_msg = "보안 검사 완료 : " + std::to_string(2025);
return env->NewStringUTF(hello.c_str());
}
3-3. 라이브러리 로드 : 연결 통로 개방
#CMakeLists.txt 발췌
companion object {
init {
System.loadLibrary("myapplication")
}
}
- JNI 연계: 안드로이드 시스템에 "내가 만든 libmyapplication.so라는 바이너리 파일을 메모리에 올려줘"라고 요청하는 것
- 보안 포인트: 앱이 실행되자마자 이 코드가 실행함 따라서 보안 설루션은 바로 이 시점에 메모리에 올라오는. so 파일이 변조되었는지 검사하거나, 디버깅 중인지 확인하는 로직을 작동시키곤 한다.
3-4. 함수 선언과 이름 규칙 : 주소지 매핑
- Kotlin (호출자): external fun stringFromJNI(): String
- C++ (구현자): Java_com_example_myapplication_MainActivity_stringFromJNI
#MainActivity.kt 발췌 | #native-lib.cpp 발췌
external fun stringFromJNI(): String | Java_com_example_myapplication_MainActivity_stringFromJNI
만약 소스 코드가 없는 상태에서. so 파일만 가졌을 때 이름만 보고 MainActivity에서 어떤 함수명을 가지는지 확인이 가능하다.
그래서 따로 난독화를 사용한다.
3-5. 데이터 타입 변환 : 포장과 해독
C++에서는 std::string 은 자바가 읽지 못하는 구문이다.
따라서 env->NewStringUTF()라는 JNI 함수를 호출하여 자바가 이해할 수 있는 jstring 객체로 변환 한 뒤 넘겨준다.
//native-lib.cpp
std::string hello = "Hello from C++";
std::string security_msg = "보안 검사 완료 : " + std::to_string(2025);
return env->NewStringUTF(hello.c_str());
4. 빌드
Starting Gradle Daemon...
Gradle Daemon started in 10 s 59 ms
> Task :app:preBuild UP-TO-DATE
> Task :app:preDebugBuild UP-TO-DATE
> Task :app:mergeDebugNativeDebugMetadata NO-SOURCE
> Task :app:checkKotlinGradlePluginConfigurationErrors SKIPPED
> Task :app:dataBindingMergeDependencyArtifactsDebug UP-TO-DATE
> Task :app:generateDebugResValues UP-TO-DATE
> Task :app:generateDebugResources UP-TO-DATE
> Task :app:mergeDebugResources UP-TO-DATE
> Task :app:packageDebugResources UP-TO-DATE
> Task :app:parseDebugLocalResources UP-TO-DATE
> Task :app:dataBindingGenBaseClassesDebug UP-TO-DATE
> Task :app:checkDebugAarMetadata UP-TO-DATE
> Task :app:mapDebugSourceSetPaths UP-TO-DATE
> Task :app:createDebugCompatibleScreenManifests UP-TO-DATE
> Task :app:extractDeepLinksDebug UP-TO-DATE
> Task :app:processDebugMainManifest UP-TO-DATE
> Task :app:processDebugManifest UP-TO-DATE
> Task :app:processDebugManifestForPackage UP-TO-DATE
> Task :app:processDebugResources UP-TO-DATE
> Task :app:compileDebugKotlin UP-TO-DATE
> Task :app:javaPreCompileDebug UP-TO-DATE
> Task :app:compileDebugJavaWithJavac
> Task :app:mergeDebugShaders UP-TO-DATE
> Task :app:compileDebugShaders NO-SOURCE
> Task :app:generateDebugAssets UP-TO-DATE
> Task :app:mergeDebugAssets UP-TO-DATE
> Task :app:compressDebugAssets UP-TO-DATE
> Task :app:processDebugJavaRes
> Task :app:checkDebugDuplicateClasses UP-TO-DATE
> Task :app:desugarDebugFileDependencies UP-TO-DATE
> Task :app:mergeLibDexDebug
> Task :app:mergeDebugJavaResource
> Task :app:dexBuilderDebug
> Task :app:mergeProjectDexDebug
> Task :app:mergeExtDexDebug
> Task :app:configureCMakeDebug[x86_64]
> Task :app:buildCMakeDebug[x86_64]
> Task :app:mergeDebugJniLibFolders
> Task :app:mergeDebugNativeLibs
> Task :app:validateSigningDebug
> Task :app:writeDebugAppMetadata
> Task :app:writeDebugSigningConfigVersions
> Task :app:stripDebugDebugSymbols
> Task :app:packageDebug
> Task :app:createDebugApkListingFileRedirect
> Task :app:assembleDebug
BUILD SUCCESSFUL in 2m 30s
39 actionable tasks: 17 executed, 22 up-to-date
빌드 후 결과를 살펴보면
> Task :app:configureCMakeDebug[x86_64]
> Task :app:buildCMakeDebug[x86_64]
- CMake 실제로 실행됨
- Native C++ 코드 컴파일됨
- x86_64 ABI용. so 생성됨
> Task :app:assembleDebug
- APK 패키징 완료
- Debug APK 생성 완료
4-1. 빌드 후 apk => zip으로 확장자 변경

apk 파일을 zip으로 변환하여 압축을 푼다.
4-2.. so 파일 찾기

x86_64(에물레이터) 폴더 안에 so 파일이 있는 걸 확인할 수 있다.
| ABI | 해당 대상 |
| arm64-v8a | 스마트폰 |
| armeabi-v7a | 오래된 ARM |
| x86_64 | 에물레이터 |
| x86 | 구형 에물레이터 |
'Mobile_security' 카테고리의 다른 글
| Android reversing - Plan (0) | 2026.02.03 |
|---|---|
| 0. Android - 아키텍처 (0) | 2025.12.17 |
| 0. Android,IOS - 모바일 사전 지식 정리 (0) | 2025.12.11 |
| 0. Android - AVD, ADB, Rooting, Pm (1) | 2025.07.22 |
| 0. Android Components (글 주의) (0) | 2025.02.01 |
