우리가 생활 속에서 쓰는 애플리케이션들은 단순히 저같은 Proxy-충만한 사람에게 자사 트래픽이 노출되지 않도록 몇 가지 방법을 사용하고는 합니다. 그리고 몇몇 iOS 앱은 비교적 새로운 방식인 Swift로 개발되고 있습니다. 그리고 이번에는 제가 어떻게 그동안 애플리케이션을 다루어왔는지 보여줍니다. 또 간단히 어떻게 막을 수 있는지 몇 가지 팁을 드립니다.

이번 글에서는 정적 분석에 대해서는 다루지 않습니다.

여러분의 애플리케이션에서 정보가 갈취되는 과정

다들 MITM 공격에 대해서는 자주 들어보셨을 겁니다. 하지만 대부분의 애플리케이션/웹 개발자들은 스스로가 타겟이 되는 상황을 가정하지 못합니다. 왜냐하면 사용자가 스스로 프록시 뒤에 있어야 한다고 생각하기 때문입니다. 그럼에도 불구하고 MITM은 저에게 이제 어떻게 애플리케이션이 동작하는지 보는데에 아주 쉬운 지표가 되었습니다.

1단계, 가장 단순한 HTTP

기본적으로 HTTPS는 단순히 모든 정보를 평문으로 전송하는 HTTP와는 다르게 CONNECT 요청을 먼저 보내 서버와 SSL Handshake를 거쳐서 그 다음부터 내용이 암호화된 상태로 통신하는 유형의 HTTP 통신을 말합니다. 즉, 여러분의 서버로 보내는 파라매터는 기본적으로 암호화되어 있다는 뜻입니다. 그래도 그 HTTP 패킷이 얼마나 평문이겠어 한다는 사람이 있다면 아래의 요청 HTTP 패킷을 한 번 보시기 바랍니다.

<Method> <URL> HTTP/<Version>
[
  <Header Key>: <Header Value>
]

Body

위와 같이 단순히 HTTP 패킷을 전송하는 경우에는 정말로 쉽게 그 내용을 볼 수 있음을 의미합니다. 여러분이 JSON으로 전송하면 아래와 같이 Body가 바로 노출되게 됩니다.

POST /api/get_profile HTTP/1.1
Host: domain.tld
Accept: */*
Accept-Encoding: br, gzip, deflate
Connection: keep-alive
User-Agent: MyApplication/1.0

{
  "id": "myID"
}

하지만 HTTPS를 사용하는 경우에는 SSL Handshake부터 모든 값들이 암호화되어 전송되어 가장 단순한 HTTP Proxy로부터 데이터가 갈취되는 것을 방지해줍니다. 그리고 대부분의 애플리케이션은 이 정도까지의 보안은 곁들이고 있습니다.

2단계, 한 단계 더 나아간 SSL Pinning

하지만 HTTPS Proxy의 경우에는 클라이언트에 인증서를 설치하여 SSL 트래픽을 특정 인증서를 거치도록 하여 그 요청과 결과를 해석하는 경우가 있습니다. Fiddler Classic과 몇 가지 Extension을 사용하거나 Proxyman을 사용하는 경우에는 모바일 디바이스에 인증서를 설치할 수 있습니다.

인증서를 설치한 뒤에 설정에서 일반 항목, 인증서 신뢰 설정에서 인증서를 신뢰하도록 해야 합니다.

그리고 실제로 Safari에서 제가 네이버에서 '네이버'를 검색했다는 것을 알 수 있게 되었습니다.

그렇다면 SSL Pinning은 어떻게 동작하는 것일까요? 여러분의 애플리케이션에 서버 인증서를 미리 저장해놓는 것입니다. 인증서가 애플리케이션 내부에 지정되어 있으니 서버에 요청할 때 애플리케이션 입장에서 '서버의 인증서처럼 보이는 것'이 저장된 인증서와 같은지 검증하게 됩니다. 그러므로 '서버의 인증서처럼 보이는 것'이 실제 서버의 인증서가 아니라 프록시의 인증서라면 그대로 트래픽이 노출되는 것을 미리 알 수 있습니다.

그러나 이 경우에는 치명적인 단점이 있습니다. 서버에 Let's Encrypt와 같이 짧은 기간의 인증서를 사용하면 앱을 주기적으로 인증서를 교체하기 위해 업데이트를 해주어야 합니다. 그래서 우리는 사람이기 때문에 비교적으로 기간이 긴 인증서를 사용해야 특별히 리마인더를 설정하지 않아도 되겠죠.

또 iOS에는 애플리케이션 업데이트를 실행하는 주기가 상당히 느리다보니 여전히 구 버전을 사용하는 사용자가 있을 수 있습니다. 그래서 업데이트가 필수적이라면 따로 HTTP 서버를 구현해서 최소 버전을 지정하는 식으로 동작하게 하는 것도 괜찮은 옵션입니다.

SSL Pinning을 적용하셔도 여전히 많은 애플리케이션은 iOS 시스템의 SSL 메서드를 후킹하여 트래픽이 노출됩니다. 물론, 여기까지만 하셔도 수고하셨습니다! 충분해요. 혹시 더 알아보고 싶으시다면 SSL Kill Switch 2에 대해서 검색해보세요.
그래도 커스텀 프로토콜 만드는건 너무하잖아요.
제발.

조금 더 쉽게 Proxy-like하게 후킹해보자

사실 프록시는 상당히 귀찮은 방법입니다. 그래서 탈옥을 하고 THEOS 등의 개발 환경이 설정이 되어 있다면 조금 더 쉽게 이를 수행할 수 있는 방법도 있습니다.

3단계, 조금 더 나아간 Swift HTTP Client Class 후킹

iOS는 기존에는 Objective-C로 개발이 되었는데 그래서 Mach-O 바이너리를 해석하여 애플리케이션 헤더를 가져올 수 있습니다. 그리고 THEOS의 logify를 사용하면 손쉽게 어떤 인자가 필요하고 어떤 값을 리턴하는지도 알 수 있죠. 물론, 제가 iOS 개발을 배운 것이 아니기 때문에 어떤 프레임워크, 어떤 라이브러리를 주로 사용하는지까지는 모릅니다. 하지만 우리는 단순히 요청을 하기보다는 조금 더 HTTP 요청을 하게 되는 코드를 분할하는 경우가 있습니다. 대표적으로 다음과 같은 경우가 있습니다.

Alamofire/Alamofire
Elegant HTTP Networking in Swift. Contribute to Alamofire/Alamofire development by creating an account on GitHub.
AFNetworking/AFNetworking
A delightful networking framework for iOS, macOS, watchOS, and tvOS. - AFNetworking/AFNetworking

하지만 이렇게 요청을 모아두는 경우에는 조금 더 쉽게 분석을 하는 경우도 있습니다. 아래와 같이 nm으로 Mach-O 파일을 분석하여 Request 부분을 따로 후킹한다면 가능합니다.

지금 당장 탈옥을 할 여력이 되지 않아 아래는 macOS의 카카오톡에 포함된 AFNetworking.framework의 Mach-O 바이너리입니다.

그리고 자체 프로토콜인 경우에도 그 프로토콜을 쉽게 대하기 위한 인스턴스를 만들어두는 경우가 대부분이기 때문에 조금 더 쉽게 접근을 할 수도 있겠죠. 또한 꼭 정적이 아니라 동적으로 애플리케이션이 실행되면 State를 Pause 시키거나 하는 등 정지시키고 그 때 당시의 Heap 탐색도 좋은 방법이고요. 자세한 내용은 제가 아직 정리해서 문서화시키기에는 실력이 부족해 그만두겠습니다.

읽어주셔서 고마워요.
HTTPS 정도는 그래도 써주세요. 제가 항상 제가 쓰는 애플리케이션을 뜯어보지만 제 개인정보가 위험해보여요.