가. 런타임 조작(Runtime Manipulation) 이란?
- 런타임 조작이란 디버깅 툴을 이용해 앱이 실행 중인 상태에서 흐름을 조작하여 보안 기능을 우회하는 것을 말한다.
- 인스턴스 변수를 수정하여 로그인(인증) 검사, 탈옥 탐지 기능 등을 우회할 수 있다.
- 인증 값이 맞을 때까지 무차별 대입 공격(brute-force attack)을 수행하는 것도 가능하다.
나. 대응방법
- 아래의 글을 참고하여 디버깅을 이용한 런타임 조작이 불가능하도록 디버깅 방지 기능을 적용한다.
[Mobile App 취약점 진단/iOS App 진단] - [iOS App 진단] 12강 - 탈옥 탐지 우회(이론 및 대응방법)
다. DVIA-v2 앱을 통한 실습
1. 분석 대상 앱 실행
- 실습을 위해 DVIA-v2 앱을 실행한다.
※ DVIA-v2 어플이 설치되어있지 않은 사람은 아래의 글을 참고하여 앱을 설치하길 바란다.
2022.07.01 - [Mobile App 취약점 진단/iOS App 진단] - [iOS App 진단] 04강 - 진단 대상 설치
- 메인메뉴 좌측 상단 메뉴 클릭 → 'Runtime Manipulation' 클릭 → 'START CHALLENGE' 클릭
- 핀코드(PIN Code) 입력 → 'Validate code' 클릭
- 입력한 코드가 올바르지 않다며 "Incorrect Code" 메시지가 출력됨
- 본글에서는 런타임 조작 공격을 통해 핀코드를 모르더라도 인증할 수 있도록 우회해 볼 것이다.
2. 바이너리 파일 추출
- 바이너리 파일을 변조하기 위해서는 앱내부에 존재하는 바이너리 파일을 추출해내야 한다.
- 3uTools Files에서 Applications(User) 클릭 또는 탐색창에 /var/Containers/Bundle/Application 입력한다.
- 분석 대상 앱 이름과 동일한 폴더를 더블클릭한다.
- Size 클릭하여 파일을 크기순으로 정렬한다.
- 폴더를 제외하고 크기가 제일 큰 파일이 바이너리 파일이다.
- 바이너리 파일을 우클릭한 뒤 Export 버튼을 눌러 PC로 추출한다.
3. ASLR 적용여부 확인
- 바이너리 파일을 분석하기 위해서는 ASLR이 적용되어 있지는 확인하여야 한다.
* ASLR(Address Space Layout Randomization, 주소 공간 배열 무작위화) : 메모리 손상 취약점 공격을 방지하기 위한 기술로, 프로그램이 실행될 때마다 메모리주소가 랜덤으로 변경된다.
- 아래의 글을 참고하여 앱에 ASLR이 적용되어 있는지 확인한다.
2023.10.03 - [Mobile App 취약점 진단/iOS App 진단] - 바이너리 ASLR 적용유무 확인방법 - otool
- 확인결과 DVIA-v2 바이너리 파일에는 ASLR이 적용되어 있는 것을 알 수 있다.
4. 바이너리 파일 분석
- 아래의 과정을 따라하여 추출한 바이너리 파일을 기드라(Ghidra)를 통해 분석한다.
※ 기드라가 설치되어 있지 않은 사람은 아래의 글을 참고하여 설치하길 바란다.
2022.06.28 - [Mobile App 취약점 진단/iOS App 진단] - [iOS App 진단] 03강 - 진단 도구 설치
- File 클릭 → New Project 클릭 → Non-Shared Project 선택 → Next 클릭
- Project Directory 경로지정 → Project Name 입력 → Finish 클릭 → File 클릭 → Import File 클릭
* Project Directory : 프로젝트 저장 경로.
* Project Name : 프로젝트 이름.
- 위에서 추출한 바이너리 파일 선택 → Select File To Import 버튼 클릭
- 설정내용을 아무것도 건드리지 않고 OK 버튼 클릭
- 추가된 바이너리 파일(DVIA-v2)을 더블클릭 → Yes 버튼 클릭
- 설정내용을 아무것도 건드리지 않고 Analyze 버튼 클릭 → 분석이 종료될 때까지 대기(우측 하단에 진행률이 표시됨).
※ 분석이 종료되면 알림창이 발생한다. 분석이 완료되지 않으면 결과가 달라질 수 있으므로 완료될 때까지 기다린다.
4. 소스코드 내 계정정보 노출 확인
- Search 클릭 → For Strings 클릭
- Login Method1 클릭 시 발생했던 오류문구를 Filter에 입력 → 검색결과 중에서 분석하고자 하는 행(아이템)을 더블클릭
- 검색창을 최소화하거나 닫으면 메인창의 커서가 위에서 더블클릭한 문구가 위치한 곳으로 이동되어 있다.
- 음영표시된 칸의 우측 "XREF[1]: 1003c9be8(*)"을 더블클릭한다.
- "Incorrect Code" 문자열이 참조되는 곳이 출력된다.
- "XREF[1]: validateCode:viewController::100..."을 더블클릭하여 문구가 위치한 곳으로 이동한다.
- 음영표시된 칸을 선택 → Window 클릭 → Function Graph 클릭
- 노란색으로 표시되면서 확대/축소를 반복하는 곳을 마우스 휠을 이용하여 확대한다.
- 내용을 분석해 보면 레지스터 x8과 x9의 값을 비교하여 같을 때와 아닐 때의 결과가 달라진다는 것을 알 수 있다.
* 10015e4f4 : 문구를 보았을 때 인증이 실패하였을 때 실행되는 로직인걸 알 수 있음.
* 10015e3e8 : 문구를 보았을때 인증이 성공하였을 때 실행되는 로직인걸 알 수 있음.
- 분기문 쪽을 좀더 자세히 분석하면 아래와 같이 설명할 수 있다.
① 레지스터 x8에 16진수 0x2290(10진수:8848)이 저장된다.(시스템이 원하는 인증코드)
* mov(Move) : 데이터 복사를 담당하는 명령어로 오른쪽에 있는 값을 왼쪽의 레지스터에 저장한다.
② 레지스터 x9에 사용자가 입력한 값(인증코드)을 불러온다.
* ldur(Load Memory to Register) : 메모리에 있는 값을 레지스터로 불러들인다.
③ 레지스터 x8과 x9의 값이 같은지 비교하여 다르면 1, 같으면 0으로 상태플래그를 세팅한다.
* cmp(Compare) : 레지스터 값을 비교하여 값이 다를 경우 1(참), 같을 경우 0(거짓)으로 eflags(상태플래그)를 업데이트 한다.
④ 상태프래그 값이 1일 경우 LAB_10015e4f4로, 0일 경우 LAB_10015e3e8로 점프한다.
* b.ne(Branch if Not Equal) : eflags의 값이 1(참)인지 0(거짓)인지에 따라 다른 오프셋의 레이블로 분기한다.
- 위에서 분석한 내용을 정리해 보면 시스템이 원하는 인증코드는 레지스터 x8에 저장되어 있고, 사용자가 입력한 코드는 x9에 저장되어 있음.
- 두 레지스터 값을 비교하여 동일하면 인증이 성공하므로, 핀코드 인증을 우회할 수 있는 방법은 아래 3가지 정도로 생각된다.
- 실무에서는 레지스터 x8의 값이 무엇인지 알 수 없으므로, 본글에서는 무차별 대입 공격을 통해 인증을 우회하는 '방법 3'으로 문제를 풀어보도록 하겠다.
핀코드 인증 우회 방법 | |
방법 1 | cmp x9, x8에서의 x9값과 x8의 값을 동일하게 변조하여 인증을 우회한다. |
방법 2 | x8에 저장된 16진수 0x2290을 10진수로 변환한뒤, 그 값을 핀코드 입력칸에 넣어 인증을 시도한다. |
방법 3 | 입력한 값이 x8에 저장된 값과 동일할때까지 무차별 대입 공격을 시도하여 인증을 우회한다. |
5. 바이너리 파일 변조
- 분기점이 되는 곳의 오프셋을 알기 위해 아래와 같이 적색박스로 되어 있는 곳을 클릭한다(더블클릭 X).
- Function Graph 창을 최소화하거나 닫으면 메인창의 커서가 위에서 클릭한 분기점의 위치로 변경되어 있을 것이다.
- 음영표시된 칸의 오프셋을 기억하도록 한다.
* 분기점의 오프셋 : 10015e3e0
- 레지스터 x9 값을 변조하기 위해서는 탈옥된 기기에 프리다 서버, PC에 프리다 클라이언트가 설치되어 있어야 한다.
- 아래의 글을 참고하여 프리다(Frida) 서버와 클라이언트를 설치하도록 한다.
2022.06.28 - [Mobile App 취약점 진단/iOS App 진단] - [iOS App 진단] 03강 - 진단 도구 설치
- PC의 프리다 클라이언트를 이용하여 탈옥탐지 기능을 우회하고자 하는 앱의 이름을 알아낸다.
- 명령어 : frida-ps -Ua
* 앱의 이름 : DVIA-v2
frida-ps -Ua
// U : USB로 연결된 기기를 대상으로 지정한다.
// a : 기기에 설치된 애플리케이션을 확인해준다.
- 위에서 확인한 정보를 바탕으로 아래의 자바스크립트(javascript) 코드를 PC에서 작성한다.
- 파일 명 : hagsig_runtime.js
* 메모장에 아래의 코드를 입력한 뒤 확장자명을 .js로 저장하면 자바스크립트 파일이 만들어진다.
var realbase_address = Module.findBaseAddress('DVIA-v2') //DVIA-v2 바이너리가 실행되어 메모리에 올라갔을때 할당된 주소를 realbase_address에 저장
console.log('realbase address : ' + realbase_address) //realbase_address에 저장된 주소를 화면에 출력
var runtime_address = realbase_address.add('0x15e3e0') //realbase_address에 분기점 오프셋을 더하면 실제 분기점 메모리 주소가 runtime_address에 저장됨
console.log('runtime address : ' + runtime_address) //runtime_address에 저장된 주소를 화면에 출력
Interceptor.attach(runtime_address, { //runtime_address에서 호출되는 값을 가로채기 위해 준비
onEnter: function(args){ //호출되는 레지스터 값을 받아옴
console.log(JSON.stringify(this.context)) //받아온 레지스터 값을 JSON 형태로 화면에 출력
}
})
※ 위 코드를 좀 더 자세히 공부하고 싶다면 아래의 게시글을 참고하여 프리다 문법을 공부하도록 하자.
2023.10.03 - [Mobile App 취약점 진단/iOS App 진단] - 프리다(Frida) 문법 정리
※ 오프셋(offset)이란?
- 오프셋은 상대 주소, 즉 기준이 되는 주소로부터 얼마나 떨어져 있는지를 나타내는 값을 말한다.
- DVIA-v2 바이너리 실행 시 부여되는 realbase_address에서 분기점의 오프셋을 더하면 분기점의 실제 메모리주소가 나온다.
* realbase_address + 분기점 offset = runtime_address
- PC의 프리다 클라이언트를 이용하여 작성한 코드를 실행한다.
- 명령어 : frida -U -l [코드경로] [앱이름]
frida -U -l hagsig_runtime.js DVIA-v2
- ASLR이 적용되어 있으므로, realbase address와 runtime address는 실행할 때마다 값이 달라진다.
- 위 화면에서 인증코드 "1234"를 입력한 뒤 'Validate code'를 클릭하면 아래와 같이 많은 값이 화면에 출력된다.
- 레지스터 x8의 값으로 0x2290, 레지스터 x9의 값으로 우리가 입력한 값이 0x4d2으로 16진수 형식으로 출력되고 있다.
- 레지스터 x8의 값과 x9의 값이 동일하지 않아 인증이 실패하였다는 "Incorrect Code"메시지가 앱에 출력된다.
- x9의 값이 x8과 동일할 때까지 무차별 대입 공격을 하기 위해,위에서 작성한 코드에 무차별 대입공격을 수행해 주는 코드를 추가하여 실행한다.
var realbase_address = Module.findBaseAddress('DVIA-v2')
console.log('realbase address : ' + realbase_address)
var runtime_address = realbase_address.add('0x15e3e0')
console.log('runtime address : ' + runtime_address)
function PaddingToZero(number){ //숫자 앞을 0으로 채워넣는 함수 생성
if(number <= 9999){
number = ("000" + number).slice(-4) //숫자 앞을 000으로 채운뒤, 뒤에서부터 4자리를 변수에 저장
return String(number) //변수에 저장된 숫자를 문자열로 변환하여 반환
}
}
Interceptor.attach(runtime_address, {
onEnter: function(args){
console.log('Answer Code x8 : ' + this.context.x8) //시스템이 원하는 인증값을 출력
console.log('Insert Code x9 : ' + this.context.x9) //사용자가 입력한 인증값을 출력
var answer_code = parseInt(this.context.x8, 16) //시스템이 원하는 인증값을 16진수에서 10진수로 변환하여 변수에 저장
answer_code = String(answer_code) //숫자를 문자열로 변환하여 저장
for(var i=0; i<9999; i++){
console.log(PaddingToZero(i)) //반복중인 현황을 화면에 출력
if(answer_code == PaddingToZero(i)){ //반복중인 숫자가 시스템이 원하는 인증값과 같은지 비교
this.context.x9 = PaddingToZero(i) //인증값과 같은 반복중인 숫자를 레지스터 x9에 저장
break //반복문 종료
}
}
console.log('Answer Code x8(Hex) : ' + this.context.x8) //시스템이 원하는 인증값을 16진수로 출력
console.log('Answer Code x8(Dec) : ' + answer_code) //시스템이 원하는 인증값을 10진수로 출력
console.log('Brute Force Code x9(Hex) : ' + this.context.x9) //무차별 대입공격을 통해 알아낸 인증값을 16진수로 출력
console.log('Brute Force Code x9(Dec) : ' + parseInt(this.context.x9, 16)) //무차별 대입공격을 통해 알아낸 인증값을 10진수로 출력
}
})
- 인증코드 "1234"를 입력한뒤 'Validate code'를 클릭한다.
- x9의 값이 x8과 동일할 때까지 무차별 대입공격이 수행되다가 값이 동일하면 공격이 종료된다.
- "Congratulations, you cracked the code!"라는 메시지가 출력되며 핀코드 인증 기능을 우회한 것을 확인할 수 있다.
'Mobile App 취약점 진단 · 모의해킹 > iOS App 취약점 진단 · 모의해킹' 카테고리의 다른 글
[iOS 취약점 진단] 15강 - 로컬 인증(Touch ID/Face ID) 우회 (0) | 2023.10.30 |
---|---|
[iOS 취약점 진단] 14강 - IPC 취약점(URL Schemes) (0) | 2023.10.27 |
[iOS App 진단] 13강 - 런타임 조작(로그인/인증 우회)_실습3 (0) | 2023.10.20 |
[iOS App 진단] 13강 - 런타임 조작(로그인/인증 우회)_실습2 (0) | 2023.10.18 |
[iOS App 진단] 13강 - 런타임 조작(로그인/인증 우회)_실습1 (0) | 2023.10.18 |