위에서 언급한 도구를 마스터한 후에는 플러그인 개발을 시작할 수 있습니다. 먼저 플러그인이 구현해야 하는 요구 사항을 생각하고 관련 뷰와 컨트롤러를 찾아야 합니다. 첫 번째 단계는 클래스 덤프를 사용하여 쉘화된 APP를 분석하는 것입니다. 클래스 덤프는 Mach-O 파일의 기호 테이블을 기반으로 모든 클래스 이름과 메서드 선언을 분석할 수 있습니다.
~ » ./class-dump -H -o /header/path WeChat
헤더 파일을 내보낸 후 이러한 헤더 파일을 XCode 프로젝트로 끌어와 후속 검색을 용이하게 합니다.
헤더 파일을 확보한 후에는 관련 인터페이스의 컨트롤러 클래스에 이를 잠가야 합니다. OpenSSH를 사용하여 Mac에서 탈옥된 iPhone에 연결하고, Cycript를 사용하여 프로세스를 삽입하고, [[UIApp keyWindow] recursiveDescription]
메서드를 호출하여 현재 보기의 계층 구조를 가져오고, 보기의 주소를 가져오고, [#0x2b2b2b00 nextResponder]
을 반복적으로 호출합니다. [#0x2b2b2b00 nextResponder]
메서드를 사용하면 마침내 인터페이스 컨트롤러 클래스를 얻게 됩니다.
이 시점에서 역분석을 계속하려면 일반적으로 두 개의 진입점이 있습니다.
인터페이스 뷰의 터치 이벤트에 의해 후크 작업이 실행되도록 하려면 일반적으로 작업을 찾는 두 가지 방법이 있습니다.
[button allTargets]
호출하여 Targets 주소를 가져오고, [button actionsForTarget: Targets forControlEvent: [button allControlEvents]]
호출하여 해당 작업 메서드를 가져옵니다.컨트롤러의 데이터 소스는 플러그인 작성 중에 사용될 수 있습니다. 데이터 소스는 컨트롤러 클래스의 멤버 변수 목록을 분석하거나 일부 뷰(예: TableView)의 데이터 소스 프록시 메서드를 분석하여 얻을 수 있습니다. 데이터 소스를 사용하면 컨트롤러에 대한 더 풍부한 정보를 얻을 수 있습니다.
간단한 플러그인은 Cycript만 사용하여 대상을 잠그고 연결할 수 있지만 대부분의 경우 대상 함수에는 여러 매개변수가 있고 논리가 상대적으로 복잡합니다. 내부 구현의 세부 사항을 알고 싶다면 다음을 수행해야 합니다. 정적 분석 아티팩트 IDA 사용 ---- Objective-C로 작성된 코드를 어셈블리 코드로 디컴파일할 수 있으며, 함수의 구현 세부 사항이 한눈에 명확해집니다. 디스어셈블리 결과를 분석해 보면 Objective-C의 메시지 전달 특성으로 인해 일반 메시지 전송에서는 실제로 objc_msgSend
함수를 호출합니다. objc_msgSend
의 각 매개변수의 의미와 ARM 아키텍처의 호출 규칙만 기억하면 어셈블리 코드에서 함수 호출을 성공적으로 복원할 수 있습니다. 호출 규칙은 함수의 처음 4개 매개변수가 R0-R3 일반 레지스터를 사용하여 전달되고, 더 많은 매개변수가 스택에 푸시되고, 반환 값이 R0 레지스터에 저장된다는 것입니다. 그런 다음 [aObject aMessage: arg1];
은 objc_msgSend(aObject, aMessage, arg1);
에 해당하며 R0은 메시지의 수신자 주소를 저장하고 R1은 선택기를 저장하며 R2는 첫 번째 매개 변수 주소를 저장합니다. 추가 매개변수에 대한 메시지 형식은 다음과 같습니다.
objc_msgSend(R0, R1, R2, R3, *SP, *(SP + sizeOfLastArg), …)
위의 형식을 통해 특정 함수의 논리를 단계별로 분석할 수 있습니다. 여기에 LLDB의 동적 단일 단계 디버깅과 특정 어셈블리 문의 주소에 대한 중단점 추적 디버깅을 추가하면 함수 구현의 세부 사항을 이해하는 데 도움이 될 것입니다.
Hook 대상 기능에 대한 솔루션은 많지만 원칙적으로 이들은 모두 Objective-C의 동적 특성을 기반으로 하며 원래 구현을 대체하기 위해 Method Swizzling을 사용합니다. 이번에는 CaptainHook 라이브러리의 사용법을 자세히 소개하겠습니다. 이 라이브러리는 Cydia Substrate의 MSHookMessageEx()
기반으로 구현됩니다.
void MSHookMessageEx(Class _class, SEL message, IMP hook, IMP *old);
iOSOpenDev가 설치된 후 CaptainHook Tweak 템플릿을 사용하여 프로젝트를 생성할 수 있습니다. CaptainHook 라이브러리에는 Hook 함수 작성을 위한 새로운 구문이 도입되었습니다. 먼저 CHLoadLateClass(UIView)
와 같이 CHConstructor()
에서 Hooking할 함수가 있는 클래스를 로드합니다. 그런 다음 CHHook(argNumber, className, arg1, arg2)
함수를 Hooked로 등록합니다. CHConstructor
의 매크로 정의는 다음과 같습니다.
#define CHConcat(a, b) CHConcat_(a, b)
#define CHConstructor static __attribute__((constructor)) void CHConcat(CHConstructor, __LINE__)()
__attribute__((constructor))
뒤의 콘텐츠는 dylib가 로드될 때, 일반적으로 프로그램이 시작될 때 실행되도록 보장됩니다. 마찬가지로 다른 기호는 매크로 정의를 통해 도입됩니다.
CaptainHook을 사용하여 Hook 함수를 선언하고 코드에 직접 구현하는 방법을 소개하겠습니다.
CHDeclareClass ( BXViewController );
CHOptimizedMethod ( 0 , self , void , BXViewController , viewDidLoad ) {
CHSuper ( 0 , BXViewController , viewDidLoad );
/* HERE TO WRITE YOUR CODE */
}
CHDeclareMethod0 ( void , BXViewController , addFriends ) {
/* HERE TO WRITE YOUR CODE */
}
작성이 완료된 후 컴파일을 위해 손에 있는 iPhone에 연결하여 아키텍처에 해당하는 동적 라이브러리가 생성되는지 확인합니다.
yololib 도구를 사용하여 컴파일된 dylib 파일을 Mach-O 실행 파일의 명령 로드 목록에 삽입합니다.
~ » ./yololib [binary] [dylib file]
Mach-O 파일의 구조는 주로 세 부분으로 구성됩니다. 프런트 엔드 부분은 Mach-O의 플랫폼 유형, 파일 유형, LoadCommands 수 및 기타 정보를 저장하는 헤더 구조입니다. 헤더는 이 부분을 구문 분석하여 파일의 논리적 구조와 가상 파일에서의 위치는 메모리 내 레이아웃을 결정할 수 있습니다. yololib 도구는 Load Commands 섹션의 정보를 변경하여 dylib를 로드합니다. 구체적인 구현 과정은 다음과 같습니다.
Load Commands 정보가 변경되므로 해당 헤더 구조의 ncmds 및 sizeofcmds가 모두 변경되므로 헤더를 먼저 수정해야 합니다.
// 取出 Header
fseek(newFile, top, SEEK_SET);
struct mach_header mach;
fread(&mach, sizeof(struct mach_header), 1, newFile);
NSData* data = [DYLIB_PATH dataUsingEncoding:NSUTF8StringEncoding];
// 计算 dylib 的大小
uint32_t dylib_size = (uint32_t)[data length] + sizeof(struct dylib_command);
dylib_size += sizeof(long) - (dylib_size % sizeof(long));
// 修改 cmds 和 sizeofcmds
mach.ncmds += 1;
uint32_t sizeofcmds = mach.sizeofcmds;
mach.sizeofcmds += dylib_size;
// 写回修改后的 Header
fseek(newFile, -sizeof(struct mach_header), SEEK_CUR);
fwrite(&mach, sizeof(struct mach_header), 1, newFile);
그런 다음 Load Commands 섹션을 변경하고 dylib 로딩 정보를 추가합니다.
fseek(newFile, sizeofcmds, SEEK_CUR);
// 创建一个 dylib 类型的 command
struct dylib_command dyld;
fread(&dyld, sizeof(struct dylib_command), 1, newFile);
// 修改 dyld 结构体数据
dyld.cmd = LC_LOAD_DYLIB;
dyld.cmdsize = dylib_size;
dyld.dylib.compatibility_version = DYLIB_COMPATIBILITY_VERSION;
dyld.dylib.current_version = DYLIB_CURRENT_VER;
dyld.dylib.timestamp = 2;
dyld.dylib.name.offset = sizeof(struct dylib_command);
// 写回修改
fseek(newFile, -sizeof(struct dylib_command), SEEK_CUR);
fwrite(&dyld, sizeof(struct dylib_command), 1, newFile);
dylib에 기록된 마지막 데이터입니다.
fwrite([data bytes], [data length], 1, newFile);
codesign
명령을 사용하여 생성된 동적 라이브러리와 APP의 모든 실행 파일(플러그인 폴더의 APP 확장 포함)을 다시 서명합니다. xcrun -sdk iphoneos PackageApplication -v
명령을 사용하여 동적 라이브러리와 모든 파일을 함께 패키지합니다. 당신은 전체 과정을 이해하게 될 것입니다. 기업 인증서가 있는 경우 서명 및 패키지된 애플리케이션은 인증서를 신뢰하는 탈옥되지 않은 iPhone에 설치할 수 있습니다. 조정 스타!