위협 행위자처럼 생각하기: iOS 바이너리 수정

이진 수정 소개: 이 기사의 동기

이 글에서는 iOS 사이버 범죄 시리즈의 두 번째 부분을 소개합니다. 첫 번째 글은 여기에서 확인하세요iOS IPA 패키지 포맷팅과 애플리케이션 사이드로딩에 대해 설명합니다. 이 글을 읽기 전에 이 배경 정보를 숙지하는 것이 좋습니다.

애플리케이션을 공격하는 위협 행위자는 일반적으로 공격 대상 바이너리를 수정하려고 합니다. 그러나 바이너리를 수정하기 전에 리버스 엔지니어링 및 디버깅하는 다양한 단계가 존재합니다. 위협 행위자는 바이너리에 대해 알아내기 위해 다양한 기법을 사용할 수 있으며, 기능을 우회하거나 악의적인 동작을 추가하는 교묘한 수정을 만들기 위해 더 많은 기법을 사용할 수 있습니다. 애플리케이션의 몇 바이트만 변경하는 간단한 수정조차도 상당한 영향을 미칠 수 있습니다.

이 글에서는 iOS 애플리케이션에서 간단한 4바이트 명령어를 찾아 수정하여 인증 검사를 완전히 우회하는 방법을 단계별로 설명합니다. 다양한 공격 벡터의 기본 사항을 숙지하고 위협 행위자의 입장에서 생각하는 것이 좋습니다. 위협 행위자를 이해하면 방어가 훨씬 쉬워집니다. 이러한 기법을 사용하여 다음을 확인할 수 있습니다. Digital.ai 이러한 공격으로부터 보호하기 위해 애플리케이션 보안이 필요한 이유를 알아보고 이해합니다.

위협 행위자의 동기

위협 행위자는 여러 가지 이유로 애플리케이션을 수정할 수 있으며, 이는 하나의 예시에 불과합니다. 다음은 현실적인 동기 목록입니다.

  • DRM(디지털 권리 관리) 또는 라이선스 검사를 우회합니다.
  • 기능을 맬웨어, 스파이웨어, 비트코인 ​​채굴 또는 기타 악의적인 활동으로 대체합니다.
  • 스레드를 비활성화하거나 감시 메커니즘을 비활성화합니다.
  • 애플리케이션 보안 제품에 삽입된 보호 기능을 비활성화하려고 시도했습니다.

이 가이드를 따르는 데 필요한 도구

  • 분해 도구: 이 가이드에서는 Ghidra 11.1.2를 사용합니다. IDA, Hopper 또는 Binary Ninja를 대체할 수 있습니다.
  • 엑스코드: 이 가이드에서는 Xcode 15.4를 사용하지만 더 광범위한 버전이 지원됩니다.
  • 힘내 : iOS 애플리케이션에 사용된 소스 코드는 다음과 같습니다. 여기를 클릭해 문의해주세요.
  • 파이썬 3 : iOS 바이너리의 압축 해제 및 수정 스크립트를 작성하는 데 사용됩니다.
  • iOS 애플리케이션 서명 또는 사이드로딩 설정.
  • 애플리케이션을 테스트하기 위한 iPhone: 이 가이드에서는 실제 iPhone을 사용하지만 PlayCover가 있는 iPad나 Apple 실리콘 Mac을 사용할 수도 있고, 가이드를 조정하여 Apple 실리콘 macOS 애플리케이션을 빌드하고 공격할 수도 있습니다.
  • 선택 사항 : 작업을 검사하는 XXD 명령줄 도구입니다.

구인 디스패처 샘플 신청서

Job Dispatcher는 iOS 애플리케이션에 대한 공격을 시연하고 애플리케이션 보안 메커니즘을 테스트하는 데 유용한 iOS 애플리케이션의 한 예입니다. Job Dispatcher는 기술자가 작업을 수신하고 완료하는 데 사용할 수 있는 애플리케이션입니다. 이 가이드에서는 모든 비밀번호가 허용되도록 인증 검사를 우회하려는 위협 행위자를 가정합니다.

첫 번째 단계는 위의 GitHub 링크에서 샘플 애플리케이션을 다운로드하고 Xcode를 사용하여 IPA를 빌드하여 샘플 애플리케이션을 빌드하는 것입니다.

작업 파견

수정할 대상 찾기

이 가이드에서는 Job Dispatcher 애플리케이션의 로그인 화면을 우회하는 방법을 보여줍니다. 더 구체적으로, 비밀번호 확인을 우회하여 모든 비밀번호를 허용합니다.

시작하려면 컴파일된 바이너리 파일을 이해하는 데 도움이 되는 도구가 필요합니다. Ghidra는 컴파일된 애플리케이션을 사람이 읽을 수 있는 어셈블리로 역어셈블하고 C와 유사한 프로그래밍 언어로 디컴파일할 수 있습니다. 이 가이드에서는 Ghidra를 사용하여 바이너리 내에서 로그인 화면과 관련된 코드 섹션을 찾고, 로그인 화면을 우회하기 위해 어떤 어셈블리 명령어를 변경할 수 있는지 분석합니다.

Job Dispatcher의 로그인 기능은 현실적이지 않습니다. 이 기능은 확인을 위해 하드코딩된 사용자 이름과 비밀번호를 사용합니다. 이 로그인 방식은 현실적이지 않지만, 많은 함수가 이와 동일한 설계를 따르며, 개방형 입력을 알려진 제약 조건과 비교하여 확인합니다. 이 가이드의 핵심은 함수 확인(function check)을 우회하는 방법을 배우는 것입니다. 함수 확인은 효과가 가시적으로 나타나는 함수이기 때문에 이 가이드에서는 이 기능을 사용합니다.

위협 행위자는 애플리케이션의 소스 코드를 가지고 있지 않을 것입니다. 하지만 우리가 다루고 있는 것이 무엇인지 파악하고 단계를 단순화하기 위해 어쨌든 간단히 살펴보겠습니다.

위협 행위자처럼 생각하다 사진

바이너리 리버스 엔지니어링을 통해 대상을 찾는 것은 시간이 많이 걸릴 수 있으며, 특히 애플리케이션이 난독화된 경우 더욱 그렇습니다. 이 경우 대상 함수는 "tech"와 "secret" 문자열 근처에 있어야 하므로, 컴파일된 바이너리에서 해당 문자열을 찾아 보겠습니다. 많은 애플리케이션에서 리버스 엔지니어는 오류 메시지나 로깅 문자열을 사용하여 원하는 코드 부분을 찾을 수 있습니다. 또한 "잘못된 사용자 이름 또는 비밀번호"라는 문자열도 있는데, 이는 공격자가 관련 코드 세그먼트를 찾는 데 실제로 사용할 수 있는 문자열입니다. Swift가 경고 호출을 처리하는 방식 때문에, "잘못된 사용자 이름 또는 비밀번호" 문자열은 정적 분석 방식보다 동적 분석 기법에 더 적합합니다.

리버스 엔지니어링 작업 디스패처

이제 Ghidra에서 Job Dispatcher 바이너리 Mach-O 파일을 열어 보겠습니다. 공유되지 않는 새로운 Ghidra 프로젝트를 만들어야 합니다. CodeBrowser 화면에 도달하면 Job Dispatcher 파일을 가져와서 모든 기본 분석 옵션을 사용할 수 있습니다. 실행하는 데 몇 분 정도 걸립니다.

미리 컴파일된 애플리케이션을 제공하지 않았다는 점을 강조하고 싶습니다. 다른 Xcode 버전, 빌드 설정 또는 소스 코드 수정을 사용하여 애플리케이션을 빌드하면 주소, 레지스터, 심지어 어셈블리 자체의 값이 달라질 수 있습니다. 따라서 따라 하기 위해 사용된 주소와 레지스터를 조정해야 할 수도 있습니다.

원하는 코드가 "tech"와 "secret" 문자열 근처에 있다는 것을 알고 있습니다. 해당 문자열을 검색해 보겠습니다. "검색" -> "문자열..." 메뉴 옵션을 사용하세요.

일반적인 위협 행위자는 조사할 가치가 있는 문자열을 찾을 때까지 잠재적으로 흥미로운 문자열을 추측하기 시작할 수 있습니다. 여기서는 추측은 생략하고 "secret" 문자열만 검색해 보겠습니다.

문자열을 클릭하면 코드 브라우저가 해당 문자열 위치로 이동합니다. 관련 코드를 찾으려면 몇 단계가 필요합니다. 이제 문자열 참조를 따라가며 코드를 찾아 보겠습니다.

"tech"와 "secret"이 바이너리의 상수 문자열 섹션에 나란히 저장되어 있는 것 같습니다. 이제 제공된 다른 데이터 위치로의 교차 참조(XREF)를 따라가세요.

상수 문자열은 CFString 객체로 래핑되어 있으며, 여기에서 확인할 수 있습니다. 아래에서 CFString 객체의 XREF를 계속 따라가며 코드에서 이 문자열을 사용하는 위치를 살펴보겠습니다.

이게 우리가 찾고 있는 코드일 것 같습니다. 스트립트된 바이너리에는 내부 함수의 이름이 없습니다. Ghidra는 이 함수에 FUN_10004000이라는 이름을 붙여 주소 10004000에 있는 함수를 나타내도록 했습니다. 이 디스어셈블리 및 디컴파일을 자세히 살펴보고 이전의 훨씬 간단한 소스 코드와 일치하는지 확인해야 합니다. 어셈블리는 읽기 어려울 수 있으므로 먼저 오른쪽 디컴파일을 분석해 보세요. 두 개의 인수를 받은 후 FUN_10001e2ec를 호출하여 몇 가지 비교를 수행하는 함수가 있습니다. 다음으로(아래) 이 지원 함수가 어떤 기능을 하는지 살펴보겠습니다.

문자열 비교 함수입니다. 바로 우리가 찾던 함수입니다. 이 함수들은 Ghidra에서 소스 코드와 약간 다르게 보입니다. Git Hub에서 소스를 확인하면 리버스 엔지니어링 시 어떤 정보가 제거되는지 더 잘 이해할 수 있습니다.

문서에 따르면문자열이 일치하면 YES를 반환하고, 그렇지 않으면 NO를 반환합니다. 모든 비밀번호를 허용하려면 항상 YES를 반환해야 합니다.

어셈블리의 관련 부분입니다. "password" 문자열은 레지스터 x2를 사용하여 참조됩니다. 그런 다음 "isEqualToString" 함수를 호출합니다. ARM64v8 호출 규칙을 사용하는 함수 호출의 반환값은 레지스터 x0에 저장됩니다. 문자열 비교 함수 호출 바로 다음 줄에서 문자열 비교의 반환값이 처리되는 것을 볼 수 있습니다. "mov x20, x0"은 x0 레지스터의 값을 x20 레지스터에 복사합니다. 제공된 암호와 관계없이 x20에 항상 YES를 저장하도록 이 MOV 어셈블리 명령어를 쉽게 수정할 수 있습니다.

이를 위해 바이너리 파일에서 다음 어셈블리 줄을 바꿔야 합니다.

MOV x20, x0

16진법으로는 "f4 03 00 aa"로 표현됩니다.

YES를 나타내는 상수 값을 사용하고 문자열 비교에서 반환된 내용을 완전히 무시하는 이 어셈블리 줄을 사용하면 다음과 같습니다.

MOV x20, #01

온라인 ARM64v8 어셈블리-바이너리 변환기를 사용하면 Mov x20, #01이 16진수 값으로 표현된다는 것을 확인할 수 있습니다.

34 00 80 D2

Ghidra는 이 조립 라인이 다음 위치에 저장되어 있음을 보여줍니다.

0 X 100004040

이 경우, 0x10000000은 바이너리 명령어가 저장되는 TEXT 섹션의 이미지 오프셋입니다. 애플리케이션 로더는 이 오프셋을 사용하지만, 디스크에 있는 파일의 내용 주소는 0x0에서 시작합니다.

따라서 교체하려는 바이너리 명령어의 오프셋은 Job Dispatcher 바이너리 파일에서 0x4040에 위치합니다. 이제 무엇을 변경해야 하는지, 그리고 파일에서 어디를 변경해야 하는지 알게 되었습니다.

해킹을 시작해 보자

헥스 편집기에서 바이너리 파일을 수동으로 열어 바이트를 바꿀 수도 있지만, 다소 비실용적입니다. 다음은 압축을 풀고 어셈블리 코드를 수정한 후 수정된 바이너리를 다시 압축하는 편리한 Python 스크립트입니다. 선택한 디렉터리에 맞게 경로를 수정해야 할 수도 있습니다.

1 zipfile에서 ZipFile 가져오기 2 os 가져오기 3 shutil 가져오기 4 5 def hack_and_repack(payloadDir, hackedDir, inputIpa, movOffset, storeTrue): 6 7 # IPA 압축을 풀어 대상 바이너리에 액세스합니다.8 with ZipFile(inputIpa, 'r') as zObject: 9 zObject.extractall(path=payloadDir) 10 11 target = os.path.join(payloadDir, "Payload", "Job Dispatcher.app", "Job Dispatcher") 12 13 # 실제 hackzorz 부분 14 with open(target, "r+b") as targetFile: 15 targetFile.seek(movOffset) 16 targetFile.write(storeTrue) 17 18 # 압축하고 ipa를 다시 작성합니다.19 hackedIpa = os.path.join(hackedDir, "Job Dispatcher.ipa") 20 shutil.make_archive(os.path.join(hackedDir, "Job Dispatcher.ipa"), 'zip', payloadDir) 21 os.rename(os.path.join(hackedDir, "Job Dispatcher.ipa.zip"), hackedIpa) 22 23 hack_and_repack("payload", "hacked_dir", "JobDispatcher/Job Dispatcher.ipa", int(b"4040", 16), b'\x34\x00\x80\xD2')

 

다음을 사용하여 이 스크립트를 실행합니다. python3 해킹 스크립트.py

스크립트가 완료되면 새로운 IPA를 추가해야 합니다. 이 IPA는 새로운 MOV 어셈블리 명령어로 수정되어야 하며, 이를 통해 모든 비밀번호가 허용됩니다. 또한 Ghidra에 다시 로드하거나 XXD를 사용하여 간단히 확인하여 바이너리가 제대로 편집되었는지 확인할 수 있습니다.

% xxd -s 0x4034 -c 4 -g 1 -l 16 페이로드/작업\ 디스패처.앱/작업\ 디스패처 00004034: 02 09 46 f9 ..F. 00004038: e0 03 13 aa .... 0000403c: ac 68 00 94 .h.. 00004040: 34 00 80 d2 4...

새로운 이동 명령어가 위치 0x4040에 삽입된 것을 볼 수 있습니다.

수정된 iOS 애플리케이션을 실행하고 테스트하려면 애플리케이션을 제대로 다시 서명하거나 iOS 기기에 사이드로드해야 합니다. 이에 대한 설명은 이 시리즈의 첫 번째 기사애플리케이션을 실행하고 사용자 이름에는 "tech"를 사용하고 비밀번호에는 원하는 내용을 입력하여 테스트해 보세요. 모든 정보가 승인되어야 하며, 다음 화면으로 넘어가야 합니다.

테이크아웃

보호되지 않은 애플리케이션은 중요한 비즈니스 로직을 잠재적 위협에 노출시켜 비즈니스 위험을 증가시키고, 위협 행위자가 애플리케이션의 바이너리 코드를 무단으로 수정하고 라이선스 확인, 인증 확인 또는 기타 중요 기능과 같은 중요한 보안 기능을 우회할 가능성이 있습니다.

게임의 경우, 위협 행위자는 해킹이나 DRM이 없는 게임 버전을 만들어 다른 사용자에게 제공하는 경우가 많습니다. 만약 위협 행위자가 바이너리를 수정할 수 있다면, 앱에 크립토재킹이나 맬웨어를 삽입한 후, 해킹된 애플리케이션을 의심하지 않는 최종 사용자에게 재배포하려고 시도할 수도 있습니다. Digital.ai 이러한 위험을 줄이고 애플리케이션 해킹을 매우 어렵게 만들고 시간도 많이 소모하게 만들어, 결연한 위협 행위자조차도 공격하기 쉬운 다른 방법을 선택하게 됩니다. 이 시리즈의 다음 부분에서는 iOS 바이너리 수정 방어에 대해 논의합니다.

이미 당사 제품을 사용하고 있다면 Job Dispatcher를 보호하여 인증 기능의 변경 사항을 감지하고 사용자가 로그인을 시도하기도 전에 애플리케이션을 종료하도록 하는 것이 좋습니다. 이 기능을 사용하려고 할 경우 난독화 기능을 비활성화하는 것이 좋습니다. 그렇지 않으면 거의 불가능할 수 있습니다.

 

우리 페이지를 확인하세요 이진 수정을 막는 방법에 대한 자세한 내용은 여기를 참조하세요.

당신은 또한 좋아할 거라