.NET破解之太樂地圖下載器【非暴破】


不知不覺,接觸破解逆向已經三個月了,從當初的門外漢到現在的小白,這個過程只有經歷過才知道其中的苦與樂:

有無知、困惑、痛苦、驚喜、徹悟、欣慰……

有無助的軟件脫殼,茫然的代碼分析,有無趣的反復測試,

有人說破解不應該程序員干的事,我回復我不是程序員;

有人說.NET程序太沒難度了,去破安卓,后來我真開始研究起安卓來;

有人說能不能把支付寶破了,我沒回答。

雖然很忙,時間少,但每天晚上都要抽些時間來關注52論壇,關注破解;雖然並沒有從破解中獲取物質利益,但我願意將自己的心得或作品分享給大家。不管怎樣,它將作為我的一門興趣愛好繼續發展下去。

本文為詳細分析,新手們也可以嘗試着做一下。

一、對象描述

作為GIS及相關專業,想必都接觸過地圖下載吧,市面上地圖、影像下載工具都非常多,效果好的、差的,免費的、收費的……各有優劣。我曾經分析過一篇文章《那些年,我們用過的地圖下載器》,需要的朋友可以去看看,今天我們的目標就是一個地圖下載器——太樂地圖下載器。

其官方網址是:http://www.arctiler.com/ ,最新版本是:太樂地圖下載器 V4.9

還是那句老話:嚴格意義上來說,地圖下載器已經觸犯了地圖服務商的權益(只不過別人沒有來找你),有關方面不要給我留言或發郵件,如果這樣,相當於干着侵權的事的人來指責其他侵犯自己的權益。

《計算機保護條例》中說了,單方面的免費聲明是無效的,我還是要像地圖下載器開發者一樣做一個免責聲明:本教程僅供研究學習,請在24小時之內刪除相關軟件、忘記相關內容

二、環境說明

操作系統:Win7 64位原版(由於筆記本配置太差,虛擬機都沒裝)

使用工具:de4dot 3.1(脫殼).NET Reflector8.5(主要分析工具)、Reflexil1.9(修改,它是.NET Reflector插件)、ILSpy2.1(調試)、VS(輔助分析、寫注冊機)。這些工具網上都能找到,這里就不用提供了吧。

三、詳細流程

還是以前的原則,破解程序的大致流程是:試用脫殼-尋找關鍵-分析修改-調試發布。

01.安裝試用

去官網下載安裝,在幫助文檔的說明中,分別說明了共了免費版、標准版、專業版和企業版,其功能與價格成正比。

試用,它是試用版,有功能限制,比如下載的等級、數據量大小等,這是一般的商業軟件模式。在試用過程中,發現其限制的地方,為后面的尋找關鍵作好准備。

02.軟件脫殼

脫殼是實際上一個有難度的活,由於我現在還是工具黨,還用的脫.NET殼神器de4dot,(大家還有沒有好的脫殼神器或手動脫殼的教程,分享一下唄)它的成功率應該有60%-70%左右。如果把它放在.NET Reflector8.5中出現"索引超出了數組界限"

它的脫殼結果有三種:一是脫殼后代碼沒有了混淆,程序可以運行,這是最好的結果;二是脫殼后代碼還是有部分混淆,程序可以運行,這個結果也勉強能接受;三是脫殼后代碼沒有混淆,程序不可以運行,這也是最痛苦的(這多半要手動來脫殼)。

本程序遇到了兩個關鍵文件AZMap.exe屬於第一種, AZMap.Core.dll屬於第三種,也就是說我不能對AZMap.Core.dl修改,只能修改AZMap.exe。

03.尋找關鍵

將脫殼后的AZMap.exe加到.NET Reflector,使用go to entry point和search是兩種常用的入手方法,前者可以到達程序的main函數,然后可一步一步往下分析,后者找出與限制相關的地方,然后可一 一分析。

Main函數中,沒有什么異樣,直接進入即可。想一下,下一步,程序主界面出現后就知道你是試用版還是其他版本,這說明在主界面出現的過程中必定有函數或方面來檢測版本問題,而MainForm里只有兩個函數存在這樣的效果:構造函數和Load函數(其他程序破解也有類似規律)

果然,MainForm的構造函數中調用了InitLicense方法,趕快標記起來,可能有用。

分析InitLicense方法中的代碼,其中RegisterProduct枚舉有Control,Desktop,Server三個。

//根據經驗,這個字符串是經過加密函數加密后的
//最后有個=號,你可能見過這樣的,猜測這是“Desktop”的含義
SN.RegisterProductCode = "wfg783X8Joo=";
//檢查權限類型,這個整個分析的關鍵
LicenseType lic = SN.Registered(1);
//刷新窗體顯示的權限版本
this.RefreshFormTitle(lic);
//啟動加密狗監聽
this.StartDogListener();

  OK,我們顯然要進入SN.Registered看看它具體是什么。等一下,想一下,以我以前的脾氣,我會直接讓這個方法返回想要的類型值,直接暴破,但這里SN類是AZMap.Core.dll中,我們不能修改,所以本程序破解的方式是分析它的算法,SN類的Registered是我們要分析的關鍵。(這句話說起來簡單,這也是我嘗試過好幾次得到的結論和方面,平時處理過程中,新手應該不會這么快就得出這樣的結果,可能也找了很久才找到關鍵,而且還不一定是真正的關鍵,所以要有耐心!)

04.代碼分析

新手入門都是暴破,這樣來得快,我這次也是迫不得已(不會脫殼,高手勿噴)才來分析它的算法的。下面這個過程可能有點讓人痛苦:

SN.Registered函數是讀取許可文件的,其關鍵還在於調用的另外一個函數Register

//返回的LicenseType(枚舉值,有Enterprise, Professional, Standerd, Trial, Free, Given),我們想要的當然是枚舉值=0的企業版。
//輸入參數是lic許可文件路徑和要注冊的類型(1代表Desktop,這個我們可以不管,在這個程序注冊的都是它)
public static LicenseType Register(string licPath, RegisterProduct regType = 1)
{
    LicenseType type2;
    //初始化為Free版,我就是就想讓free的值賦成Enterprise
    LicenseType free = LicenseType.Free;
    if (!File.Exists(licPath)) return free;
    //讀取許可文件中的許可碼
    string sn = ReadSN(licPath);
    //如果是Desktop,許可文件路徑為C:\Users\Administrator\AppData\Local\AZMap
    string path = GetLocalAZMapPath(RegisterProduct.Desktop) + "azmap_4";
    // 如果是Server,許可文件路徑為X:\Program Files (x86)\ArcTiler\Desktop\4.9\AZMap
    string str3 = GetLocalAZMapPath(RegisterProduct.Server) + "azmap_4";
    try
    {
        string r = "";
        bool flag = false;
        //規律一:SN長度為175-250個
        // IsSNLegality函數檢查SN是否非法,其具體要求是SN長度為175-250個
        if (IsSNLegality(sn))
        {
            //規律二:SN的72位到104位與機器碼加密后的字符串必須相等
            //我們想要flag的值返回為ture,就需要看SetSN函數返回值。獲取機器碼加密后的值是否與r相等。根據它提供的計算函數,我在VS把它生成出來了
            //如果SN從72位開始截取32位(即r值)與MCToR()函數返回值(獲取機器碼加密后的值)相當,則flag為真。
            r = sn.Substring(72, 32);
            flag = R.Instance.SetSN(r);
        }
        if (!flag) goto Label_02B4;
                
        //規律三:SN中至少有兩個#號
        //將SN用#進行分割,從下文看,使用了strArray[1],strArray[2],說明至少有兩個#號
        string[]strArray = sn.Split(new char[] { '#' });
        int result = 0;
        //規律四:#號分割后的SN,strArray[1]為版本類型代碼加密后的值
        // 用Decode函數將strArray[1]解密后轉為int類型,out給result(是LicenseType的代碼),我們的目的是想將Enterprise(當Version =0,1,2時,其代碼-1,-11,-21),根據調試,本程序當Version =0,所以,我們是想把-1out給result
        //如果Decode解密后的值不是數字,則TryParse轉換失敗,將返回為False值,那就完蛋了。
        if (!int.TryParse(EncAndDec.Decode(strArray[1], EncAndDec.ToMD5("KEY_64"), EncAndDec.ToMD5("IV_64")), out result))
        {
            free = LicenseType.Trial;
            return LicenseType.Trial;
        }
        //經測試,其他Version為0,所以,Version=1,2的代碼忽略掉了
        if (ApplicationConfig.Version == 0)
        {
            //在VS測試,能夠得到 result=-1,達到了free的值賦成Enterprise的目的
            switch (result)
            {
                case -1:
                    free = LicenseType.Enterprise;
                    // 轉換到Label_0170,這應該是脫殼沒干凈的原因,不然不會有跳轉
                    goto Label_0170;
                case -3:
                    free = LicenseType.Professional;
                    goto Label_0170;
                case -5:
                    free = LicenseType.Standerd;
                    goto Label_0170;
                case -7:
                    free = LicenseType.Free;
                    goto Label_0170;
                case -9:
                    free = LicenseType.Given;
                    goto Label_0170;
            }
            //如果沒有跳轉,那又完蛋了
            if (result <= 0)
            {
                free = LicenseType.Free;
                return LicenseType.Free;
            }
            free = LicenseType.Trial;
        }
        else if (ApplicationConfig.Version == 1){……}
        else if (ApplicationConfig.Version == 2){……}
    
    //假設大家都成功跳轉過來了,在這里見到大家表示很高興,如果沒有來到這里,在VS里面多調試一下代碼。
    Label_0170:
        // 這個程序不是Server,所以不管
        if (regType == RegisterProduct.Server) path = str3;
        //因為我們是LicenseType.Enterprise,所以要執行下面的語句
        if (free != LicenseType.Trial)
        {
            //設置許可類型為free,此時free已為LicenseType.Enterprise
            R.Instance.SetLicenseType(free);
            //將這個有效的SN寫入到注冊文件,以便下次檢測
            File.WriteAllText(path, sn);
            //已經return了,后面不管了
            return free;
        }
        ……
    }
    catch
    {
        type2 = LicenseType.Free;
    }
    finally
    {
        ……
    }
    return type2;
}

  05.修改調試

下面是在VS中生成示例注冊碼用到的函數,給大家參考一下:

//獲取加密后的機器碼
internal string MCToR()
{
    string s = string.Format("{0}{1}{2}{3}", new object[] { "a71z", this.GetCpuId(), "a91z", this.GetHDid() });
    using (MD5 md = new MD5CryptoServiceProvider())
    {
        byte[] buffer = md.ComputeHash(Encoding.UTF8.GetBytes(s));
        StringBuilder builder = new StringBuilder(0x20);
        for (int i = 0; i < buffer.Length; i++)
        {
            builder.Append(buffer[i].ToString("x").PadLeft(2, '0'));
        }
        return builder.ToString();
    }
}

//獲取CUP序列號
internal string GetCpuId()
{
    try
    {
        string str = string.Empty;
        using (ManagementClass class2 = new ManagementClass("Win32_Processor"))
        {
            foreach (ManagementObject obj2 in class2.GetInstances())
            {
                if (obj2.Properties["ProcessorId"] != null)
                {
                    str = obj2.Properties["ProcessorId"].Value.ToString();
                }
                obj2.Dispose();
            }
        }
        return str;
    }
    catch
    {
        return this.GetSystemName();
    }
}

//獲取系統名字
internal string GetSystemName()
{
    try
    {
        string str = string.Empty;
        using (ManagementClass class2 = new ManagementClass("Win32_Processor"))
        {
            foreach (ManagementObject obj2 in class2.GetInstances())
            {
                if (obj2.Properties["SystemName"] != null)
                {
                    str = obj2.Properties["SystemName"].Value.ToString();
                }
                obj2.Dispose();
            }
        }
        return str;
    }
    catch (Exception)
    {
        return "SystemName";
    }
}

//獲取硬盤序列號
internal string GetHDid()
{
    string str = string.Empty;
    using (ManagementClass class2 = new ManagementClass("Win32_DiskDrive"))
    {
        using (ManagementObjectCollection.ManagementObjectEnumerator enumerator = class2.GetInstances().GetEnumerator())
        {
            if (enumerator.MoveNext())
            {
                ManagementObject current = (ManagementObject)enumerator.Current;
                if (current.Properties["Model"] != null)
                {
                    str = (string)current.Properties["Model"].Value;
                }
                current.Dispose();
            }
        }
    }
    if (str != null)
    {
        str.ToString();
    }
    return str;
}

//將字符串轉為整型
public static bool TryParse(string s, out int result)
{
    return Int32.TryParse(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result);
}

//轉MD5
public static string ToMD5(string KEY)
{
    byte[] bytes = Encoding.Default.GetBytes(KEY);
    MD5 md = new MD5CryptoServiceProvider();
    return BitConverter.ToString(md.ComputeHash(bytes)).Replace("-", "").Substring(0, 8);
}

//加密函數
public  string Encode(string data, string KEY_64, string IV_64)
{
    KEY_64 =ToMD5(KEY_64);
    IV_64 = ToMD5(IV_64);
    byte[] bytes = System.Text.Encoding.ASCII.GetBytes(KEY_64);
    byte[] bytes2 = System.Text.Encoding.ASCII.GetBytes(IV_64);
    string result;
    using (System.Security.Cryptography.DESCryptoServiceProvider dESCryptoServiceProvider = new System.Security.Cryptography.DESCryptoServiceProvider())
    {
        int arg_34_0 = dESCryptoServiceProvider.KeySize;
        using (System.IO.MemoryStream memoryStream = new System.IO.MemoryStream())
        {
            using (System.Security.Cryptography.CryptoStream cryptoStream = new System.Security.Cryptography.CryptoStream(memoryStream, dESCryptoServiceProvider.CreateEncryptor(bytes, bytes2), System.Security.Cryptography.CryptoStreamMode.Write))
            {
                System.IO.StreamWriter streamWriter = new System.IO.StreamWriter(cryptoStream);
                streamWriter.Write(data);
                streamWriter.Flush();
                cryptoStream.FlushFinalBlock();
                streamWriter.Flush();
                result = System.Convert.ToBase64String(memoryStream.GetBuffer(), 0, (int)memoryStream.Length);
            }
        }
    }
    return result;
}

//解密函數
public static string Decode(string data, string KEY_64, string IV_64)
{
    byte[] buffer3;
    string str;
    KEY_64 = ToMD5(KEY_64);
    IV_64 = ToMD5(IV_64);
    byte[] bytes = Encoding.ASCII.GetBytes(KEY_64);
    byte[] rgbIV = Encoding.ASCII.GetBytes(IV_64);
    try
    {
        buffer3 = Convert.FromBase64String(data);
    }
    catch
    {
        return null;
    }
    using (DESCryptoServiceProvider provider = new DESCryptoServiceProvider())
    {
        using (MemoryStream stream = new MemoryStream(buffer3))
        {
            using (CryptoStream stream2 = new CryptoStream(stream, provider.CreateDecryptor(bytes, rgbIV), CryptoStreamMode.Read))
            {
                str = new StreamReader(stream2).ReadToEnd();
            }
        }
    }
    return str;
}

  

用VS生成了一個注冊碼,進行注冊后,終於顯示成功,有點小激動!

06.測試程序

猜中了開頭,沒有猜中故事的結局,重啟后出現這個警告,並且企業版又變回了試用版,點擊確定后,程序關閉。

搜索"非法授權"關鍵字,找到了SN.OnlineCheck()函數(在不能修改的AZMap.Core.dll文件中),認識英文的都知道,它是聯網查詢數據庫檢查是否存在 這個SN,接下來怎么辦?黑數據庫?……

return MySQLHelper.CheckMCExisted(R.smethod_0());

開個玩笑,我們接下來分析一下,它出現的位置。

柳暗花明又一村,這是在AZMap.exe中調用的,這就好辦了,修改判斷啊,來個狠一點,這個timer的Tick事件一直會檢測SN是否合法,檢查加密狗是否插入、拔出、合法,直接全刪除!

終於寫完了,效果圖!

四、注意事項

01.本教程不提供最終成品,已分析了詳細過程,不要因此而留言;

02.在分析和調試時AZMap.Core.dll應使用脫殼后的,在檢查SN是否有效則使用原版AZMap.Core.dll;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM