캣프리 소설 인증코드 요청 프로토콜 분석 및 오프라인 실행과의 안드로이드 역실전

머리말

이 글을 마치는 데 오랜 시간이 걸렸고, 연휴가 거의 끝나자마자 끝냈어요 일부 부분이 명확하지 않을 수 있으며 앞으로 천천히 글쓰기 실력을 향상시키겠습니다. 이 기사는 주로 새로운 소프트웨어의 검증 코드 요청 프로토콜을 분석하고 오프라인 시뮬레이션을 실현합니다. 자세한 내용은 아래를 참조하십시오.

환경

픽셀 6 안드로이드 12

프리다 15.2.2

일곱 고양이 무료 소설 v7.2.3

프리다 감지

먼저 frida 감지를 통과해야 합니다.여기에서 Hluda Frida 서버를 수동으로 컴파일 하고 기능을 제거한 후 frida-server를 사용하여 대부분의 감지를 통과할 수 있습니다. 컴파일 프로세스에서 내 편집 프로세스 문제 기록: frida 컴파일 hluda 컴파일 문제 레코드를 참조할 수도 있습니다 . 컴파일 후 첨부 모드에서 직접 첨부하여 감지를 통과합니다.

포격

MT 관리자를 통해 그림과 같이 응용 프로그램에 셸이 있음을 알 수 있습니다.

여기에 이미지 설명 삽입

나는 frida-dexdump를 사용하여 압축을 풀고 프로젝트 주소는 frida-dexdump 이며 작업 효과는 다음과 같습니다.

여기에 이미지 설명 삽입

부호 알고리즘 분석

소프트웨어는 에이전트를 감지하고 justTrustMe를 사용하여 감지, 프로젝트 주소를 전달할 수 있습니다. justTrustMe , 감지를 통과한 후 HttpCanary를 사용하여 패킷을 캡처하고 두 개의 패킷을 캡처하면 결과는 다음과 같습니다.

POST /api/v1/init/is-open-sm-code h2
Host: xiaoshuo.wtzw.com
net-env: 1
channel: qm-tengxun_lf
is-white: 0
platform: android
app-version: 70203
reg: 
application-id: com.kmxs.reader
authorization: 
qm-params: cLGUuq2-HTZ5gI9wgI9wgI9QNI0rp5U5pI4Mth9wgI9QgI9wgI9wgI9wgI9wH5w5pyRlmqN2tq2-HTZ5gT9LgT9EgTHnNhfEgIfn4THLgTKnNh4w4hKnpho2Aq0r4hNxpy0npqgMphOrNI9ngIoxgqkTpz0nNlFrphK5taGQ4qg5A5GIgTZekTZYNTZUNe1sgeZwN3HjHSNDuCGTpCR1paHWH-kRgSuwFq27mCo-3TkkO_FQqfrUmSoQtC2MFEsoufkRFljMgRGyRC2-gMGa4ROUOyNCf-QAk-pEp0gnq2kVRSoTResMpRx3kyNoRTo3k2p04C1kcygLmI05taGecCgQuzRLHTZ5ghH5taGMOSReuyR-tq2-HTZ5kofLuEssmqY1OqkiNoowuaUphTRVOqMQcCkIO0RUkoRImeFnf-pRcqFeF-GxReRw4Uu33MYykSu-FeomRy1qOqNCg_k2qoG04MRqgRGyR-kxc2or4eGZg3HjHz2Qpq-5A5H5taGQBlk2BaHWH2s1cyRjHI45taGEByHQmqU2m3HWH5HjHSuj45UUmqF5A5G0RhGEO0o1Bz2np0ZMfCsMtR2ANq1nB3UYu0NwkCR0RfNvNIo3k2RYpINaFzoCNCsTRUGth-pyulkIgR1fm2pn4UOwuyR4f-kTkR4nf-pqkyoWfCxTgzKnH5w54ln1pqYMtq2-HTZ5NhHENhFYAyFrNhG-pI-Lp5HjHzGL4qY-HTZ5plJDpln2H5w5Blo1paHWH5GJ
sign: c42882c83550414161a30aa63dbcdbce
qm-it: 1658476625
qm-ii: 1780595992
no-permiss: 3
user-agent: webviewversion/0
content-type: application/x-www-form-urlencoded
content-length: 84
accept-encoding: gzip

cancell_check=1&encrypt_phone=ghKrgeKUNh-rgI9=&sign=17f86fc9135531b6d3d8117a52914799
POST /api/v1/login/send-code h2
Host: xiaoshuo.wtzw.com
net-env: 1
channel: qm-tengxun_lf
is-white: 0
platform: android
app-version: 70203
reg: 
application-id: com.kmxs.reader
authorization: 
qm-params: cLGUuq2-HTZ5gI9wgI9wgI9QNI0rp5U5pI4Mth9wgI9QgI9wgI9wgI9wgI9wH5w5pyRlmqN2tq2-HTZ5gT9LgT9EgTHnNhfEgIfn4THLgTKnNh4w4hKnpho2Aq0r4hNxpy0npqgMphOrNI9ngIoxgqkTpz0nNlFrphK5taGQ4qg5A5GIgTZekTZYNTZUNe1sgeZwN3HjHSNDuCGTpCR1paHWH-kRgSuwFq27mCo-3TkkO_FQqfrUmSoQtC2MFEsoufkRFljMgRGyRC2-gMGa4ROUOyNCf-QAk-pEp0gnq2kVRSoTResMpRx3kyNoRTo3k2p04C1kcygLmI05taGecCgQuzRLHTZ5ghH5taGMOSReuyR-tq2-HTZ5kofLuEssmqY1OqkiNoowuaUphTRVOqMQcCkIO0RUkoRImeFnf-pRcqFeF-GxReRw4Uu33MYykSu-FeomRy1qOqNCg_k2qoG04MRqgRGyR-kxc2or4eGZg3HjHz2Qpq-5A5H5taGQBlk2BaHWH2s1cyRjHI45taGEByHQmqU2m3HWH5HjHSuj45UUmqF5A5G0RhGEO0o1Bz2np0ZMfCsMtR2ANq1nB3UYu0NwkCR0RfNvNIo3k2RYpINaFzoCNCsTRUGth-pyulkIgR1fm2pn4UOwuyR4f-kTkR4nf-pqkyoWfCxTgzKnH5w54ln1pqYMtq2-HTZ5NhHENhFYAyFrNhG-pI-Lp5HjHzGL4qY-HTZ5plJDpln2H5w5Blo1paHWH5GJ
sign: c42882c83550414161a30aa63dbcdbce
qm-it: 1658476625
qm-ii: 1780595992
no-permiss: 3
user-agent: webviewversion/0
content-type: application/x-www-form-urlencoded
content-length: 112
accept-encoding: gzip

encrypt_phone=ghKrgeKUNh-rgI9=&rid=20180817160958967672365a4t3bf655&type=0&sign=50b41c8edd846e92a4bdd0fdd6cabc6f

먼저 첫 번째 패키지를 분석하고 URL에 따라 /api/v1/init/is-open-sm-code를 찾습니다.

여기에 이미지 설명 삽입

주석의 특징으로 보면 retrofit2 프레임워크를 사용하고 있는 것으로 판단되며, @Body로 표시된 클래스를 입력하여 검색하고 "sign"을 찾는다.

여기에 이미지 설명 삽입

확인을 위해 frida 스크립트 작성

function hook_2() {
    
    
    var is_sign = false
    let Buffer = Java.use("okio.Buffer");
    Buffer["writeUtf8"].overload('java.lang.String').implementation = function (str) {
    
    
        if (str == "sign") {
    
    
            is_sign = true
        }
        let ret = this.writeUtf8(str);
        return ret;
    };

    let Encryption = Java.use("com.qimao.qmsdk.tools.encryption.Encryption");
    Encryption["sign"].implementation = function (str) {
    
    
        let ret = this.sign(str);
        if (is_sign) {
    
    
            console.log("Encryption.sign arg-->" + str)
            console.log("sign=" + ret)
            is_sign = false
        }
        return ret;
    };
}

function main() {
    
    
    Java.perform(function () {
    
    
        hook_2()
    })
}
setImmediate(main)

스크립트를 실행한 결과는 다음과 같습니다.

여기에 이미지 설명 삽입

비교 후 Encryption.sign의 반환 값은 두 패키지의 본문에 있는 sign 값입니다.Encryption.sign을 분석해 보겠습니다.

여기에 이미지 설명 삽입

네이티브 방식이다.우선 so 파일을 찾아야 한다.정적으로 등록되었는지 여부를 먼저 판단한다.나는 frida-trace를 사용한다.여기서 정적 메소드(접두사 문자열 "Java" 및 "_" 밑줄 표시) 패키지 이름, 클래스 이름 및 네이티브 메서드 이름)

여기에 이미지 설명 삽입

그것은 정적 등록으로 보이며 libcommon-encryption.so에 위치하고 IDA로 열고 검색한 다음 매우 이상한 문제를 발견했습니다. Java_com_km_encryption_api_Security_sign은 아래와 같이 메서드에서 찾을 수 없습니다.

여기에 이미지 설명 삽입

이러한 메서드 이름은 없지만 오프셋으로 찾을 수도 있습니다. frida 스크립트는 다음과 같습니다.

var module = Process.findModuleByName("libcommon-encryption.so")
var funcs = module.enumerateExports()
funcs.forEach(function(func){
    
    
    if(func.name.indexOf("Java_com_km_encryption_api_Security_sign")>=0){
    
    
        console.log("sign offset-->0x" + (func.address-module.base).toString(16))
    }
})

여기에 이미지 설명 삽입

오프셋 0x15620을 얻었습니다. 살펴보기 위해 건너뛰십시오.

여기에 이미지 설명 삽입

토큰이 여기에 표시되는 이유를 모르겠습니다. 동일한 주소에 두 개의 기능을 등록할 수 있습니까? 그러나 무슨 일이 있어도 이것이 기호의 논리임을 확인할 수 있습니다.

여기에 이미지 설명 삽입

이 함수는 최종적으로 jstring을 반환하므로 frida를 구성하는 코드는 다음과 같습니다.

var env = Java.vm.tryGetEnv()
Interceptor.attach(module.base.add(0x15620), {
    
    
    onLeave:function(retval){
    
    
        var env = Java.vm.tryGetEnv()
        console.log(env.getStringUtfChars(retval,0).readCString())
    }
})

자바 레이어의 서명 기능과 토큰 기능을 함께 후킹하면 결과는 다음과 같습니다.

여기에 이미지 설명 삽입

함수 이름은 Java_com_km_encryption_api_Security_token이지만 Java_com_km_encryption_api_Security_sign의 논리이기도 한 것으로 나타났습니다.다음은 논리를 직접 분석합니다.

여기에 이미지 설명 삽입

이 함수의 논리는 매우 간단합니다.먼저 KeyData를 MD5의 소금으로 생성한 다음 문자열의 뒤쪽에 연결하고 마지막으로 md5 계산을 수행하고 frida 스크립트를 사용하여 다음과 같이 확인합니다.

function hook_2() {
    
    
    var is_sign = false
    let Buffer = Java.use("okio.Buffer");
    Buffer["writeUtf8"].overload('java.lang.String').implementation = function (str) {
    
    
        if (str == "sign") {
    
    
            is_sign = true
        }
        let ret = this.writeUtf8(str);
        return ret;
    };

    let Encryption = Java.use("com.qimao.qmsdk.tools.encryption.Encryption");
    Encryption["sign"].implementation = function (str) {
    
    
        let ret = this.sign(str);
        if (is_sign) {
    
    
            console.log("Encryption.sign arg-->" + str)
            console.log("sign=" + ret)
            is_sign = false
        }
        return ret;
    };
}

function hook_so() {
    
    
    var libcommon = Process.findModuleByName("libcommon-encryption.so")
	Interceptor.attach(libcommon.base.add(0x1574C), {
    
    
        onEnter: function (args) {
    
    
            console.log("---keydata---");
            console.log(hexdump(this.context.x1, {
    
    
                offset: 0,
                length: 128,
                header: true,
                ansi: true
            }));

            console.log("---keydataSize---");
            console.log("size-->" + this.context.x2);
        }
    })

    var data = null
    Interceptor.attach(libcommon.base.add(0x15730), {
    
    
        onEnter: function (args) {
    
    
            data = this.context.x0
        }
    })
    Interceptor.attach(libcommon.base.add(0x1575C), {
    
    
        onEnter: function (args) {
    
    
            if (data != null) {
    
    
                console.log("---data---");
                console.log(hexdump(data, {
    
    
                    offset: 0,
                    length: 128,
                    header: true,
                    ansi: true
                }));
            }
        }
    })
}
function main() {
    
    
    Java.perform(function () {
    
    
        hook_2()
    })
    hook_so()
}
setImmediate(main)

작업 결과는 다음과 같습니다.

여기에 이미지 설명 삽입

위의 그림에서 keydata는 md5의 솔트이고 값은 d3dGiJc651gSQ8w1이며 data 아래의 내용은 문자열 접합 결과이고 sign은 이 함수의 반환 값입니다. CyberChef로 확인하십시오.

encrypt_phone=ghfYAIOMNhKYNTf=rid=20180817160958967672365a4t3bf655유형=0d3dGiJc651gSQ8w1

여기에 이미지 설명 삽입

함수의 반환 값에 따라 부호 알고리즘 분석 종료

encrypt_phone 암호화 분석

이제 상황을 하나씩 분석해 보겠습니다 서명 암호화 기능이 분석되었으므로 요청 헤더와 요청 본문 모두에 서명 필드가 나타납니다.우리가 모르는 것은 두 서명이 동일한 암호화로 암호화되었는지 여부입니다. sign 함수에 의해 전달된 평문이 어떻게 생성되는지 모르겠습니다. 후크 사인 코드는 다음과 같습니다.

var security = Java.use("com.km.encryption.api.Security")
security.sign.implementation = function(){
    
    
	console.log("++++++++++++++++++++++");
	var str = Java.use("java.lang.String").$new(arguments[0])
	console.log("arguments -->",str);
	var ret = this.sign.apply(this,arguments)
	console.log("result -->",ret)
	console.log("++++++++++++++++++++++");
	return ret
}

실행 결과 분석:

요청 헤더 :

여기에 이미지 설명 삽입

여기에 이미지 설명 삽입

요청 본문(패키지 1):

여기에 이미지 설명 삽입

여기에 이미지 설명 삽입

요청 본문(패키지 2):

여기에 이미지 설명 삽입

여기에 이미지 설명 삽입

위의 정렬 후 두 가지 포인트를 얻을 수 있습니다. 1. 요청 헤더의 서명과 요청 본문이 모두 동일한 암호화 기능으로 암호화됩니다. 2. 암호화 기능에 전달된 문자열을 얻습니다.

많은 패킷 캡처 후 요청 헤더의 부호 및 qm-params가 변경되지 않으므로 분석을 건너뛰고 요청 본문의 부호가 암호화될 때 전달된 매개변수를 직접 분석합니다.

그 중 encrypt_phone만 변경된 것으로 base64처럼 보입니다. CyberChef를 사용하여 base64를 디코딩해 보십시오.

여기에 이미지 설명 삽입

전화번호로 복호화되지 않아 역으로만 분석이 가능합니다.처음에 요청 본문에서 기호의 접합 위치를 찾았습니다.encrypt_phone이 서로 옆에 있습니다.코드는 다음과 같습니다.

여기에 이미지 설명 삽입

encrypt_phone의 위치가 틀리지 않았는지 확인하기 위해서는 this.f14735a의 값을 살펴봐야 하며 스크립트와 실행 결과는 다음과 같다.

var is_sign = false
let Buffer = Java.use("okio.Buffer");
Buffer["writeUtf8"].overload('java.lang.String').implementation = function (str) {
    
    
    if (str == "sign") {
    
    
        is_sign = true
        Java.choose("yh0", {
    
    
            onMatch: function (instance) {
    
    
                console.log(instance._a.value);
            },
            onComplete: function () {
    
    

            }
        })
    }
    let ret = this.writeUtf8(str);
    return ret;
};

let Encryption = Java.use("com.qimao.qmsdk.tools.encryption.Encryption");
Encryption["sign"].implementation = function (str) {
    
    
    let ret = this.sign(str);
    if (is_sign) {
    
    
        console.log("Encryption.sign arg-->" + str)
        console.log("sign=" + ret)
        is_sign = false
    }
    return ret;
};

여기에 이미지 설명 삽입

결과에 encrypt_phone이 있어 올바른 위치를 찾았음을 나타내며 this.f14735a의 할당 위치를 찾아야 합니다.

여기에 이미지 설명 삽입

여기에 이미지 설명 삽입

빨간색 상자는 this.f14735a의 할당 위치이자 encrypt_phone의 할당 위치입니다. 위 코드의 논리는 a 메소드로 전달된 t를 json으로 변환한 다음 할당을 위해 각 요소를 꺼내는 것입니다. t가 무엇인지 알아야 합니다. 스크립트는 다음과 같습니다.

let can_hook = false
let yh0 = Java.use("yh0");
yh0["a"].implementation = function (t) {
    
    
	can_hook = true
	let ret = this.a(t);
	return ret;
};

let NBSGsonInstrumentation = Java.use("com.networkbench.agent.impl.instrumentation.NBSGsonInstrumentation");
NBSGsonInstrumentation["toJson"].overload('com.google.gson.Gson', 'java.lang.Object').implementation = function (gson, obj) {
    
    
	let ret = this.toJson(gson, obj);
	if (can_hook) {
    
    
		console.log("t value is " + obj);
		console.log('toJson ret value is ' + ret);
	}
	can_hook = false
	return ret;
};

여기에 이미지 설명 삽입

t는 클래스의 개체입니다. 먼저 com.qimao.qmuser.model.entity.CaptchaEntity 클래스를 살펴보겠습니다.

여기에 이미지 설명 삽입

내부에 encrypt_phone이 있습니다. 할당 위치를 계속 찾습니다.

여기에 이미지 설명 삽입

호출 스택을 직접 인쇄합니다. 스크립트는 다음과 같습니다.

여기에 이미지 설명 삽입

ag0.onNext를 찾기 위한 일련의 호출 후 코드는 다음과 같습니다.

여기에 이미지 설명 삽입

이것은 분명히 콜백 메서드이고 프레임워크는 높은 확률로 사용됩니다.이 클래스가 상속한 클래스를 살펴보겠습니다.

여기에 이미지 설명 삽입

Google it, 이 클래스는 RxJava 프레임워크를 사용합니다. http://www.jianshu.com/p/a406b94f3188 문서를 참조하십시오. 프레임워크의 핵심 코드는 다음과 같습니다.

Observable.create(new ObservableOnSubscribe<Integer>() {
    
    
        // 1. 创建被观察者 & 生产事件
            @Override
            public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
    
    
                emitter.onNext(1);
                emitter.onNext(2);
                emitter.onNext(3);
                emitter.onComplete();
            }
        }).subscribe(new Observer<Integer>() {
    
    
            // 2. 通过通过订阅(subscribe)连接观察者和被观察者
            // 3. 创建观察者 & 定义响应事件的行为
            @Override
            public void onSubscribe(Disposable d) {
    
    
                Log.d(TAG, "开始采用subscribe连接");
            }
            // 默认最先调用复写的 onSubscribe()

            @Override
            public void onNext(Integer value) {
    
    
                Log.d(TAG, "对Next事件"+ value +"作出响应"  );
            }

            @Override
            public void onError(Throwable e) {
    
    
                Log.d(TAG, "对Error事件作出响应");
            }

            @Override
            public void onComplete() {
    
    
                Log.d(TAG, "对Complete事件作出响应");
            }

        });

키 코드를 찾을 수 있도록 후크 Observable.subscribe(Observer) 메서드를 선택합니다. 스크립트는 다음과 같습니다.

let Observable = Java.use("io.reactivex.Observable")
Observable.subscribe.overload('io.reactivex.Observer').implementation = function(){
    
    
	console.log("+++++++++++++++++");
	console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
	let ret = this.subscribe.apply(this,arguments)
	return ret
}

여기에 이미지 설명 삽입

vo0.a의 코드는 다음과 같습니다.

여기에 이미지 설명 삽입

핵심은 new b(strArr)입니다.특정한 이유는 RxJava에 대해 알아볼 수 있습니다.클릭하여 확인하세요.

여기에 이미지 설명 삽입

이 클래스는 Callable을 상속받으며, Callable이 생성되면 자동으로 call() 메서드가 호출되며 call() 메서드의 로직은 암호화를 위해 this.fq4329a의 값을 꺼내서 encrypt(str) 메서드를 입력하는 것입니다. 보기 위해

여기에 이미지 설명 삽입

매우 간단합니다 즉, base64 인코딩을 먼저 수행한 다음 인코딩된 결과에 대해 문자 교체를 수행합니다.replaceChar©의 코드는 다음과 같습니다.

여기에 이미지 설명 삽입

오프라인

여기에서 분석해야 할 모든 필드를 분석한 다음 파이썬을 사용하여 호출을 시뮬레이션합니다.특정 코드는 게시되지 않으며 그림과 같이 최종 결과만 표시됩니다.

여기에 이미지 설명 삽입

와, 이걸 볼 수 있다니 대단하네요(´∀`)

Supongo que te gusta

Origin blog.csdn.net/weixin_56039202/article/details/126376536
Recomendado
Clasificación