사이드프로젝트 slecs

결제 키를 네이티브 영역으로 옮겨 APK 노출 차단

목차

왜 NDK까지 끌고 갔나

전엔 빌드 상수에 API Key 박아놨는데, APK 뜯어보면 그대로 노출됨. 디컴파일러 한 번이면 끝나는 구조라 v3.1에서 손봤음.

  • 정적 문자열 리소스 → 키 추출 30초 컷
  • 빌드 상수도 마찬가지, 난독화 무관하게 readable
  • 결제 플랫폼 호출 키라 그냥 두기 부담스러웠음

native 쪽으로 옮긴 방식

JNI 통해서 C++ 함수가 키를 돌려주게 함. .so 파일은 디컴파일 난이도가 다른 차원이고, 거기에 XOR 마스킹까지 얹어서 strings 명령으로도 바로 못 뽑게 했음.

// 런타임에 마스킹 풀어서 반환
JNIEXPORT jstring JNICALL
nativeGetKey(JNIEnv* env, jclass) {
    unsigned char buf[KEY_LEN];
    for (int i = 0; i < KEY_LEN; ++i)
        buf[i] = ENC[i] ^ MASK[i % MASK_LEN];
    return env->NewStringUTF((const char*)buf);
}

CMakeLists.txt에 타겟 추가하고, gradle 쪽 externalNativeBuild 블록만 연결하면 끝남. 생각보다 붙이는 건 어렵지 않았음.

ProGuard로 바깥쪽 정리

  • JNI 진입 함수명은 -keep으로 보존. 안 그러면 native에서 못 찾고 죽음
  • 그 외 클래스/메서드명 전부 난독화
  • 디버그 빌드는 풀어두고 릴리즈만 적용

무결성 검증 같이 넣음

이커머스 앱 재서명해서 광고 박아 재배포하는 사례 본 적 있어서, 서명 해시 화이트리스트 비교 로직을 native 쪽에 같이 넣었음. 위변조면 키 반환 자체를 안 함.

항목 이전 이후
키 위치 빌드 상수 native .so + XOR
난독화 없음 릴리즈만 적용
재서명 탐지 없음 서명 해시 검증
빌드 시간 30초 1분 10초

한계는 솔직히 인정

  • 메모리 덤프로 native에서 키 뽑는 시도는 못 막음
  • frida 같은 후킹 툴 앞에선 한 단계 늦추는 정도
  • 진짜 중요한 결제대행사 키는 서버 경유 단기 토큰으로 가는 게 정답

이번 작업은 "캐주얼한 공격자 한 명 더 거르기" 가 목표였고 그 정도는 됐다고 봄. 빌드 느려진 건 ccache 붙여서 회복했고, 로컬은 그냥 감수하는 중. 결제 진입점 쪽 키도 동일 방식으로 옮길 예정.

다음

댓글 0

첫 댓글 달아줘.