tujia民宿X-TJH及請求數據unidbg逆向

tujia民宿X-TJH及請求數據unidbg逆向

X-TJH逆向

ida跳轉0x36a9

image-20220114150250593
image-20220114150319100
image-20220115091717394

由于X-TJH的長度是40,猜測是SHA1。可以看出v58就是最后的加密結果,而v70[57]就是它原始的字節數組結果。往前看到j_tjget,很自然猜測它是類似于SHA1的Final操作,進而猜測j_tjreset是Update,j_tjcreate是Init。

先看看j_tjcreate

image-20220115092337653

可以看到SHA1的常量和K值。

再看看j_tjreset

image-20220115093812958

unidbg下斷點看看。

emulator.attach().addBreakPoint(module.base + 0x2c94+1);
image-20220115094322702

看看r1的數據

image-20220115094428950

嘗試作為SHA1的輸入,在cyberchef看看結果

image-20220115094613695

和結果對不上。

image-20220115094708445

看到tjreset函數里有個異或操作,補上看看

image-20220115095008961

和正確結果對上了。

看看異或0x21之后的結果

image-20220115095110918

似乎是base64,加個base64解密看看

image-20220115095244535

看到了原始數據的樣子,只不過是逆序的,將數據再逆序看看

image-20220115095417920

只有兩個部分不太好直接確定,多次更換輸入后,發現第一個字符串dGpoY2hr是固定的。而第二個字符串則有個很明顯的特點,那就是它的字符串是按字符排序的,而這個字符的輸入很顯然是POST的body。

實現
def calc_tjh(params, ua, app_client, body, ts):
    if isinstance(params, (dict, list, tuple)):
        if hasattr(params, "items"):
            params = params.items()
        params = urlencode(params)
    params = params or ''
    if isinstance(body, dict):
        body = json.dumps(body, separators=(',', ':'))
    body = re.sub('[^a-zA-Z0-9]', '', body)

    body = ''.join(sorted(body))
    print(body)
    data = '#'.join((params, ua, str(ts), 'dGpoY2hr', app_client, body))
    print(data)
    data2 = bytes(reversed(data.encode()))
    print(data2)
    data3 = base64.b64encode(data2)
    print(data3)
    tjh = hashlib.sha1(data3).hexdigest()
    return tjh

def test_tjh():
    params = 'key=nodeApiConfig'
    ua = 'Mozilla/5.0'
    app_client = 'LON=null;LAT=null;'
    body = '{\"code\":\"hello everhu\"}'
    ts = 1642084337
    tjh = calc_tjh(params, ua, app_client, body, ts)
    print(tjh)
image-20220115100351765

請求數據逆向

image-20220115100607758

j_tj_crypt應該是加密的地方,j_tjtxtutf8應該是base64編碼的地方。

下個斷點看看j_tjtxtutf8的輸入

emulator.attach().addBreakPoint(module.base + 0x291c+1);
image-20220115101216301
image-20220115101237773

在cyberchef驗證一下

image-20220115101401581

和unidbg的加密結果一致。

base64的輸入就是j_tj_crypt的輸出,接下來就是看看j_tj_crypt

image-20220115102023721

可以看到第1個參數有3種取值,代表著不同的加密模式,取值不同時,加密的結果也不同。

ver = "1"

image-20220115102320479

ver = "2"

image-20220115102441469

ver = "3"

image-20220115102525150
加密模式1
image-20220115103114058

ver = "1"時,v27 = v18 = 4, v19 = 0, v13 = a8 = <body length> = 23

hook看看j_CCCrypt的輸入

emulator.attach().addBreakPoint(module.base + 0x4480+1);
image-20220115104414211
image-20220115104437991

根據ARM ATPCS調用約定,當參數個數小于等于4個的時候,子程序間通過R0~R3來傳遞參數(即R0-R3代表參數1-參數4),如果參數個數大于4個,余下的參數通過sp所指向的數據棧進行參數傳遞。而函數的返回值總是通過R0傳遞回來。

msp看看其余參數的數據棧

image-20220115104957086

看看a7

image-20220115105027534

輸入大概知道了,接下來分析函數本身

image-20220115105537507

主要有4個函數,不過有3個函數是動態的

image-20220115110430223

鼠標移到左括號前,按Tab切換到匯編代碼

image-20220115110522934

0x44ca下個斷點,然后看看r6的值

emulator.attach().addBreakPoint(module.base + 0x44ca);
image-20220115110707782

跳轉到0x4eeb看看

image-20220115110808622

看看j_CCCryptorCreate

image-20220115111619907

先看第一個

image-20220115112022134

0x42ac下斷點

image-20220115112132559

跳轉到0x4e7d

image-20220115112214118

第二個

image-20220115112341102
image-20220115112444703

跳轉到0x4e89

image-20220115112551870

終于來了個有東西的函數了,由此推測ver = "1"時使用了RC4加密。

進去看看

image-20220115113044318
image-20220115113102702

參數個數對不上

返回F5一下

image-20220115113200294
image-20220115113144471

hook一下CC_RC4_set_key

emulator.attach().addBreakPoint(module.base + 0xc244+1);
image-20220115113330175

r1應該是長度

image-20220115113441695

這個其實就是j_CCCrypta4的前16個字節

RC4的輸入則是POST的body,cyberchef驗證一下

image-20220115113819761

和unidbg一致,接下來就是看key是怎么來的。

image-20220115114018014

已經知道它是sub_302C的輸出,接下來分析這個函數

image-20220115114111655

emm,有個Hmac映入眼簾,先hook看看

emulator.attach().addBreakPoint(module.base + 0x47b4+1);
image-20220115114829728
image-20220115114847743
image-20220115114903591
image-20220115114922879

blr在函數返回處下斷點,c執行到函數返回處,看看0x4020d030的值

image-20220115115027935

看長度應該是HMAC-SHA1,cyberchef驗證一下

image-20220115115327488

接下來就是sub_33E4這個函數

image-20220115115424095

一個字符替換,主要功能就是把字節數組的前半部分和后半部分的逆序進行一個穿插,重寫一下即可。

def sub_33E4(data, a2=20):
    """
    trans byte
    """
    half = a2 // 2
    s1 = b''.join(bytes(x) for x in zip(data[:half], data[half:][::-1]))
    s2 = b''.join(bytes(x) for x in zip(s1[half:][::-1], s1[:half]))
    return s2
加密模式2
image-20220115151550676

ver = "2"時,v27 = v18 = 0, v19 = 0, v13 = (23 + 16) & 0xFFFFFFF0 = 32

和模式1一樣,在0x4304下斷點

image-20220115153929707
image-20220115154000325

跳轉到0x4829

image-20220115155430643

sub_4DE0

image-20220115154539766

sub_4E0C

image-20220115154610960

又調了一個函數,hook看看函數地址

image-20220115154702740
image-20220115154756748

跳轉到0x11a51

image-20220115154859190

函數名很明顯了,這里設置了AES加密的iv,hook看看iv。

image-20220115155114445

所以iv是'\x00' * 16

回過頭看看sub_4828的動態函數

image-20220115155529844
image-20220115155556811
image-20220115155652637

跳轉到0x119e9

image-20220115155733078

hook看看

image-20220115160127343
image-20220115160003749

就是之前sub_302C的結果

要素齊全了,cyberchef驗證一下。

image-20220115160338382
加密模式3
image-20220115162657527

ver = "3"時,v27=0, a13 = (23 + 16) & 0xFFFFFFF0 = 32v19則是sub_302C的結果。這個函數在模式1中已經分析了,它的輸入也知道了,a5 = salt, v30 = "dGpjcnlwdG8K",hook看看結果。

image-20220115164354301

和之前一樣,在0x4304下斷點

image-20220115153929707
image-20220115163243130

和模式2一樣,也是跳轉到0x4829,那說明也是AES-CBC,同樣在設置key和iv的地方下斷點。

key -> 0x119e9

image-20220115163633509
image-20220115163719284

熟悉的字節數組

iv -> 0x11a51

image-20220115163950733
image-20220115163937486

同樣是sub_302C的前16個字節

cyberchef驗證一下

image-20220115170007288

實現

import base64
import hashlib
import hmac
import json
import re

from urllib.parse import urlencode

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

_KEY = b'dGpjcnlwdG8K'


def calc_tjh(params, ua, app_client, body, ts):
    if isinstance(params, (dict, list, tuple)):
        if hasattr(params, "items"):
            params = params.items()
        params = urlencode(params)
    params = params or ''
    if isinstance(body, dict):
        body = json.dumps(body, separators=(',', ':'))
    body = re.sub('[^a-zA-Z0-9]', '', body)

    body = ''.join(sorted(body))
    print(body)
    data = '#'.join((params, ua, str(ts), 'dGpoY2hr', app_client, body))
    print(data)
    data2 = bytes(reversed(data.encode()))
    print(data2)
    data3 = base64.b64encode(data2)
    print(data3)
    tjh = hashlib.sha1(data3).hexdigest()
    return tjh


def encrypt_body(body, salt, ts, ver):
    body = body.encode()
    data = ''.join((salt, str(ts))).encode()
    key = sub_302C(data, _KEY)
    print(key)
    if ver == '1':
        result = rc4_crypt(body, key)
    elif ver == '2':
        result = aes_encrypt(body, key, b'\x00' * 16)
    elif ver == '3':
        iv = sub_302C(salt.encode(), _KEY)
        result = aes_encrypt(body, key, iv)
    output = base64.b64encode(result).decode()
    return output

def sub_302C(data, key):
    """
    make length-of-16 key with data and key
    """
    data2 = hmac.new(key, data, hashlib.sha1).digest()
    data3 = sub_33E4(data2)
    key = data3[:16]
    return key

def sub_33E4(data, a2=20):
    """
    trans byte
    """
    half = a2 // 2
    s1 = b''.join(bytes(x) for x in zip(data[:half], data[half:][::-1]))
    s2 = b''.join(bytes(x) for x in zip(s1[half:][::-1], s1[:half]))
    return s2

def rc4_crypt(data, key):
    """
    encrypt/decrypt data with key
    Args:
        data: data to encrypt/decrypt
        key: rc4 key
    """
    S = list(range(256))
    j = 0
    for i in range(256):
        j = (j + S[i] + key[i % len(key)]) & 0xff
        S[i], S[j] = S[j], S[i]

    bucket = []
    i = 0
    j = 0
    for c in data:
        i = (i + 1) & 0xff
        j = (j + S[i]) & 0xff
        S[i], S[j] = S[j], S[i]
        k = c ^ S[(S[i] + S[j]) & 0xff]
        bucket.append(k)
    return bytes(bucket)

def aes_encrypt(data, key, iv):
    data = pad(data, AES.block_size)
    cryptor = AES.new(key, AES.MODE_CBC, iv)
    buf = cryptor.encrypt(data)
    return buf


def test_tjh():
    params = 'key=nodeApiConfig'
    ua = 'Mozilla/5.0'
    app_client = 'LON=null;LAT=null;'
    body = '{"code":"hello everhu"}'
    ts = 1642084337
    tjh = calc_tjh(params, ua, app_client, body, ts)
    print(tjh)


def test_body():
    datasets = [
        ('1', '4cFS+3YZRDW7K0P9uvgEEPDnbI9xkeE='),
        ('2', 'kQgG94ZE/OvGHN+wXvtMS95kdddjHDQjLx+Zf+qAMTY='),
        ('3', '2Knmhtkoe8UFpFbBZRvaPI6+GcwtC0Py4rP1U777nKk='),
    ]
    for ver, result in datasets:
        ts = 1642126567
        salt = 'everever'
        body = '{"code":"hello everhu"}'
        ret = encrypt_body(body, salt, ts, ver)
        print(ret)
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,698評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,202評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,742評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,580評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,297評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,688評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,693評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,875評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,438評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,183評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,384評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,931評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,612評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,022評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,297評論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,093評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,330評論 2 377

推薦閱讀更多精彩內容