들어가기 앞서
이전 "라이브러리 모듈 검사를 통한 Frida 탐지 방안과 우회 기법"에 이어서 이번 포스팅에서는 안드로이드 앱 단에서 중간자 공격을 대응하고자 적용하는 피닝(Pinning)이 무엇인지 알아보고 해당 기법이 적용된 앱에서는 이를 어떻게 우회할 수 있는지 다뤄보도록 하겠다.
※ 실습 진행에 사용되는 ANDITER 앱은 아래의 GitHub에서 다운로드 가능하다.
개요
SSL/TLS는 암호화 기반 인터넷 보안 프로토콜로 웹에서 전달되는 모든 데이터를 암호화해 스니핑(Sniffing), 스누핑(Snooping)의 위협을 방지한다. 다만, 클라이언트 측에서 전송되는 패킷을 중간에 가로채 조작하는 중간자 공격(Man in the Middle)에 취약하다는 특징을 가지고 있어 전송되는 SSL/TLS 패킷 내용 획득 시, 애플리케이션이 서버와 통신 시 전달되는 데이터 유출 및 패킷 조작을 통한 데이터를 위·변조하거나 인증을 무력화 시킬 수 있게 된다. 이를 대응하고자 SSL/TLS Pinning(이하 피닝)으로 클라이언트에 신뢰성이 보장된 인증서들을 사전에 등록해서 서버와 통신할 때 등록된 인증서가 아니면 통신을 하지 못하도록 막아버리는 기술을 적용한다.
피닝은 보편적으로 두 가지 방법을 통해 구현된다. 첫 번째는 서버와 통신 시 반환되는 인증서가 디바이스의 Root CA에 의해 신뢰할 수 있는 인증서인지 검증하는 방법으로 정상 사용자의 경우 문제가 없으나 패킷 위·변조 목적으로 프록시(Proxy) 서버를 경유해 데이터를 주고 받는 사용자의 경우 인증서 관련 오류(NET_ERR_CERT_AUTHORITY_INVALID)로 인해 서버와 통신을 하지 못하게 된다.
정상적인 경우에는 [그림 32]와 같이 디바이스에 등록된 Root CA를 사용해 통신하지만, [그림 3] 같이 Root CA에 등록되지 않은 인증서로 통신을 시도하는 경우 오류가 발생한다. 이 외에도 서버 인증서의 유효기간 만료, 폐기된 인증서 사용 시 관련 오류가 발생하기도 한다.
두 번째 방법은 클라이언트(디바이스) 단에서 인증서를 고정해 사용하는 방법으로 고정되어 있는 인증서 외의 인증서를 서버가 반환하는 경우 통신을 하지 못하도록 하는 방법이다. 이 경우 허용하는 서버의 인증서 값이 애플리케이션 코드 단에 하드 코딩 되어있거나 별도의 방법을 통해 값을 불러와 인증서 값을 비교하게 된다.
사전 준비
원활한 실습 진행을 위해 탐지 되고자 하는 경우 서버 인증서 변조를 위해 직접 중간자 공격을 수행해야 한다. 다만, SSL/TLS 중간자 공격을 하기 위해서는 별도의 프록시 서버를 구성하거나 도구를 사용해야 한다. 일반적으로 중간자 공격 시 많이 사용되는 도구로는 PortSwigger에서 제작한 Burp Suite, OWASP 프로젝트 중 하나인 OWASP ZAP이 있으며 해당 챕터에서는 Burp Suite를 사용해 중간자 공격을 수행하도록 하겠다.
Burp Suite 도구는 무료 버전인 Community와 유료 버전인 Professional으로 구분되며 무료 버전을 다운로드(https://portswigger.net) 받아 설치하면 된다.
Burp Suite를 실행 후 [그림 6]과 같이 ① 설정(Settings) 탭을 클릭해 ② Tools – Proxy 카테고리를 선택하고 ③ Add 버튼을 클릭한다.
[그림 7]의 ① Proxy 서버의 통신 포트를 지정하고 ② 리스닝 할 IP 대역을 All interfaces로 설정한다.
사용 중인 컴퓨터와 모바일 디바이스를 공유기 또는 핫스팟(Hot Spot) 기능을 이용해 동일 네트워크 대역을 사용할 수 있도록 설정 한다. 그 후 연결된 네트워크 설정에서 [그림 8]과 같이 ① 프록시 모드를 수동으로 변경하고 ② 모바일 디바이스와 같은 대역에 연결된 PC의 IP 주소와 [그림 8]에서 설정한 포트 번호를 입력해준다.
설정이 잘 되었는지 확인을 위해 통신 패킷을 직접 인터셉트 해보겠다. [그림 9] 같이 ① Burp Suite 상단의 Proxy 설정 탭을 클릭 후 ② Intercept를 클릭하고 ③ Intercept is off 버튼을 클릭해 Intercept is on으로 변경해주면 패킷을 인터셉트 할 준비가 된 것이다.
모바일 디바이스에서 브라우저를 통해 구글(www.google.co.kr)에 접근 시 [그림 10]과 같이 연결 관련 오류가 발생한다. 이때, 브라우저 하단의 고급 탭을 클릭 후 사이트 이동 버튼을 클릭해준다.
[그림 11]과 같이 Burp Suite에서 구글 사이트와의 통신 패킷이 인터셉트 되었다면 정상적으로 설정이 된 것으로 인증서 변조 과정을 위한 중간자 공격 준비가 완료되었다.
분석
앞서 피닝은 보편적으로 두 가지 방법을 통해 구현된다고 설명했었다. 그 중 첫 번째인 SSL/TLS 인증서가 모바일 OS에 내장되어 있는 Root CA에 의해 신뢰할 수 있는 인증서인지 확인하는 방법을 살펴보겠다. SSL/TLS 인증서는 서버가 클라이언트에게 반환하는 것으로 중간자 공격 시 [그림 12]와 같이 프록시 서버를 거쳐 패킷이 전송되기 때문에 요청한 서버의 인증서가 아닌 프록시 서버의 인증서가 클라이언트에게 반환된다. 때문에 디바이스에서는 프록시 서버의 인증서와 Root CA를 비교해 인증서 유효성을 검증하게 되고 등록된 CA의 공개키가 존재하지 않을 경우 신뢰할 수 없는 인증서로 판별하게 된다.
Bypass Pinning(Root CA) 탐지 항목은 위와 같은 특징을 이용해 통신을 시도하는 서버에서 반환하는 인증서가 디바이스 Root CA에 의해 신뢰할 수 있는 인증서인지 확인하고 신뢰할 수 없는 인증서인 경우 중간자 공격을 시도 중인 것으로 판단해 탐지하게 된다.
[그림 14]의 isCheckRootCA() 함수는 Bypass Pinning(Root CA) 탐지 결과를 반환해주는 역할을 한다. 코드를 보면 PinningDetector.isCheckRootCA.sendRequestJob.1 클래스가 사용된 것을 볼 수 있으며 코루틴(Coroutine) 관련 로직을 관리하는 클래스이다. 코루틴으로 작성된 코드는 이와 같이 컴파일 과정에서 스레드 관련 작업을 별도의 클래스 파일로 생성해 관리하고 필요 시 호출해 사용하게 된다.
[그림 15]는 PinningDetector.isCheckRootCA.sendRequestJob.1 클래스 코드 중 서버와 통신 작업을 수행하는 invokeSuspend() 함수의 코드이다. 코루틴에서 suspend 함수의 경우 네트워크 관련 비동기 작업 시 사용되는 함수로 호출 시 수행 중이던 작업을 일시 중지하고 suspend 함수 작업이 끝날 때 까지 대기하게 된다. 코드를 보면 ① 서버와의 통신을 위해 OkHttp 라이브러리가 사용되었으며 이때, 서버의 주소를 ④에서 받아와 요청 패킷을 보낸다. 요청에 성공할 경우 ③ onResponse() 함수가 동작하게 되고 요청 실패 시 ② onFailure() 함수가 실행된다.
그런데 해당 함수의 코드에서는 인증서가 신뢰할 수 있는 인증서인지 검증하는 구문이 존재하지 않는다. 그 이유는 인증서 신뢰 유무를 OkHttp 라이브러리에서 확인하기 때문이다. OkHttp의 경우 SSL/TLS 통신 시 내부적으로 HandShake 과정을 거쳐 서버의 인증서가 디바이스에 내장되어 있는 Root CA에 의해 신뢰할 수 있는 인증서인지 검증하게 된다. 따라서, 인증서 검증을 우회하기 위해서는 Root CA를 통해 인증서 신뢰 유무 검사 시 사용되는 함수를 후킹해 신뢰할 수 있는 인증서로 변조해야 한다.
[ trustmanagerImpl 클래스 소스코드 자료 ]
https://github.com/google/conscrypt/blob/master/common/src/main/java/org/conscrypt/TrustManagerImpl.java#L521
trustmanagerImpl 클래스는 Android OS에서 통신을 시도하는 서버가 반환하는 인증서를 Root CA를 통해 인증서 유효성 검증 시 사용되며 이 중 checkTrustedRecursive() 함수에서 인증서 체인을 검증하게 된다. 해당 함수의 경우 호출 시 제공된 인증서 체인을 기반으로 Root CA를 통해 신뢰할 수 있는 인증서를 찾아 반환하게 되는데 이때, 빈 리스트를 반환하게 되면 인증서 검증을 수행하지 않고 모든 인증서를 신뢰할 수 있는 것으로 처리한다. 따라서, 해당 함수를 후킹해 항상 빈 리스트를 반환하게 만들면 인증서 검증 과정 우회가 가능해진다.
이 외에도 동일 클래스의 getTrustedChainForServer() 함수, verifyChain() 함수 조작을 통한 우회 방법과 문서에서는 언급하지 않았지만 인증서 자격 증명 시 사용되는 TrustManageer 인터페이스를 이용한 Burp Suite 인증서를 추가하는 방법을 이용한 우회 방안도 존재한다.
Bypass Pinning(Root CA) 탐지 우회를 위해 여기서는 checkTrustedRecursive() 함수를 후킹해 항상 빈 리스트 값을 반환하도록 만들어 우회해 보도록 하겠으며 추가로 디바이스 설정을 통한 간단한 우회 방안도 살펴보도록 하겠다.
[그림 17] TrustManagerImpl 클래스의 checkTrustedRecursive() 함수를 후킹하기 위해 필자가 작성한 Frida 스크립트이다. 코드를 보면 ①에서 TrustManagerImpl 클래스 객체를 생성하고 인증서 체인 검사를 위해 checkTrustedRecursive() 함수 호출 시 전달되는 인자의 데이터 타입에 맞춰 checkTrustedRecursive() 함수를 오버로딩으로 구현했다. ②에서는 checkTrustedRecursive() 함수 호출 시 사용할 리스트를 생성하기 위해 ArrayList 클래스의 객체를 생성했으며 ③ 함수가 호출되면 ②에서 생성한 ArrayList 객체를 이용해 빈 리스트를 만들어 반환한다.
[그림 18]과 같이 Frida를 통해 작성한 스크립트를 ADITER 애플리케이션에 어태치 한다. 그 후 프록시가 연결된 상태에서 Bypass Module 탐지 항목을 체크하면 Success! 가 출력되며 탐지가 우회된 것을 볼 수 있다.
마무리
지금까지 안드로이드 앱에서 API 서버 통신 시 중간자 공격에 대응하고자 적용하는 피닝(Pinning) 기법이 무엇인지 알아보고 또, 이를 공격자의 관점에서 어떻게 우회할 수 있는지 살펴봤다. 다음 포스팅에서는 Frida를 사용하지 않고 디바이스에 인증서를 직접 추가하는 방식을 통해 우회하는 방법을 다뤄보도록 하겠다.
'안드로이드 탐지 및 우회 > 피닝 탐지 및 우회' 카테고리의 다른 글
디바이스에 인증서 설치를 통한 Root CA 기반의 피닝 우회 방법 (2) | 2023.10.06 |
---|---|
고정 인증서 방식의 피닝(Pinning) 우회 방법 (1) | 2023.10.06 |