操屁眼的视频在线免费看,日本在线综合一区二区,久久在线观看免费视频,欧美日韩精品久久综

新聞資訊

    源:哈爾濱日報

    2022年8月30日0時至24時,哈爾濱市報告12例新冠病毒陽性感染者,其中10例確診病例,2例無癥狀感染者。根據流調結果,現將其主要活動軌跡涉及場所發布如下:

    新增確診病例主要情況:

    確診病例21:新冠病毒肺炎確診病例(普通型),現住香坊區公濱路307-1號。

    確診病例22-23:新冠病毒肺炎確診病例(普通型),現住香坊區新松茂樾山一期。

    確診病例24:新冠病毒肺炎確診病例(輕型),現住道里區鄉里街140號。

    確診病例25-27:新冠病毒肺炎確診病例(輕型),現住南崗區凡爾賽詩城小區。

    確診病例28:新冠病毒肺炎確診病例(普通型),現住道里區海富禧園小區。

    確診病例29:新冠病毒肺炎確診病例(普通型),現住道里區美晨家園小區。

    確診病例30:新冠病毒肺炎確診病例(普通型),現住道里區共樂小區。

    新增無癥狀感染者主要情況:

    無癥狀感染者6:新冠病毒無癥狀感染者,現住道里區新城小區。

    無癥狀感染者7:新冠病毒無癥狀感染者,現住巴彥縣天增鎮慶勝村。

    一、巴彥縣

    8月25日-28日

    8月25日07:00-11:00、13:00-17:00、18:00-20:00

    8月26日07:00-11:00、13:00-17:00

    8月27日07:00-10:30、18:00-21:30

    8月28日10:00-10:30

    巴彥縣維珍食雜店(天增鎮慶勝村徐家崗屯)

    8月27日、28日

    8月27日10:30-11:30

    8月28日08:00-10:00、10:30-11:00

    巴彥縣宴席廳(天增鎮慶勝村半步道屯)

    二、哈市其他城區

    8月24日

    06:30 道里區新發鎮五星村

    09:00-10:00 香坊區哈平路189-1號

    10:00 電信加油站(香坊區哈平路81號)

    8月26日

    00:00-09:50 哈爾濱太平國際機場T2航站樓

    06:45 網約車(黑AK03C2)

    07:20網約車(黑A7C7C7)

    09:00 84路公交車(民安街站至雨陽公園公交首末站)

    12:29 南崗區比優特超市(博物館店)

    12:40南崗區超麥蛋糕(建設街與花園街交口店)

    13:09-17:18 道里區金色海洋浴館(民安小區)

    14:00 84路公交車(雨陽公園公交首末站至民安街站)

    15:30 元申廣電(哈藥路店)

    16:41 地鐵1號線轉3號線(哈爾濱南站至工農大街站,醫大二院站換乘),129路公交車(工農大街麗江路口站至武警指揮學校站)

    17:00 地鐵2號線轉3號線(工人文化宮站至勞動公園站,珠江路站換乘)

    17:32-17:43 道里區耐克專賣店(凱德廣場埃德蒙頓路店)

    18:00 75路公交車(經緯三道街站至民安街站)

    18:00-21:30 香坊區黃牛村小鋁鍋烤肉(通鄉街與松木街交口)

    18:21 南崗區奈思母嬰館(凡爾賽詩城)

    20:43 南崗區可優生鮮超市(凡爾賽詩城小區)

    21:34-21:57 道里區烽禾影城(群力遠大店)

    8月26日、27日

    08:00 115路公交車(市第六醫院站至大方里小區站)

    09:00-16:00 九昱醫藥有限責任公司(道外區大方里街99號)

    8月27日

    08:00 84路公交車(民安街站至雨陽公園公交首末站)

    09:03 南崗區二子生鮮(凡爾賽詩城小區)

    10:00 南崗區盟科視界小區

    10:40 杭點局點心蔥油面(紅博中央公園店)

    11:30 金藝名剪燙染專業店(學院路濱才城尚品陽光H棟7號)

    12:50 妙記辣鴨(濱才店)

    12:59 得π超市(利民西六大街與學院路交叉口西240米)

    13:00 松北區阿三生煎(融創茂店)

    13:56-16:35 道里區碧水華清池(康安路108號)

    14:02-17:27 哈爾濱融創樂園(松北區世茂大道)

    14:49 道里區許大廚菜館(消防救援大隊民生尚都小型消防站東南側80米)

    15:44-17:27 松北區嗨嗨小豬兒童成長樂園(融創茂二樓)

    16:00-19:45 南崗區上和置地廣場銀座23樓(燎原街附58-2)

    19:45 網約車(黑AQ6C70)

    21:30 南崗區二子生鮮(凡爾賽詩城小區)

    21:32 南崗區貪吃鴨貨(凡爾賽詩城小區)

    對上述場所已嚴格落實了消毒和管控等措施。

    哈爾濱市應對新冠肺炎疫情工作指揮部要求廣大市民,在此期間與上述新冠病毒陽性感染者有過接觸史或活動軌跡有交集、重合的人員,迅速做好個人防護,立即撥打社區或屬地疫情防控部門電話,不要外出,配合工作人員開展流調排查、核酸檢測等疫情防控措施。近期所有外地市抵(返)哈人員,抵返前需提前2天向所在屬地社區(村屯) 和單位報備,抵哈后第一時間向所在屬地社區(村屯)和單位(或所住賓館)報告。高、中風險區人員建議暫緩來哈。同時請市民關注官方權威發布,進一步提高防范意識,不聚集、不聚會、不聚餐,養成出門佩戴口罩、勤洗手、常通風、保持社交距離等文明健康生活習慣。

    哈爾濱市及各區縣(市)疾控中心咨詢電話

    道里區疾控中心 51669665

    道外區疾控中心 57672096

    南崗區疾控中心 86243577

    香坊區疾控中心 55694618

    平房區疾控中心 86520920

    阿城區疾控中心 53722510

    松北區疾控中心 88105166

    呼蘭區疾控中心 57321893

    雙城區疾控中心 53163181

    依蘭縣疾控中心 57222573

    巴彥縣疾控中心 57502636

    賓 縣疾控中心 57914255

    方正縣疾控中心 57112343

    木蘭縣疾控中心 57083497

    尚志市疾控中心 53324184

    通河縣疾控中心 57435030

    五常市疾控中心 55802434

    延壽縣疾控中心 53067333

    哈爾濱市疾控中心 51012320

    哈爾濱市疾病預防控制中心

    2022年8月31日

    源:海口發布

    海口市新型冠狀病毒肺炎疫情防控工作指揮部關于新增確診病例的通報

    2022年11月8日0-16時,我市新增1例確診病例,在外省來瓊重點人群篩查中發現。11月7日以來,我市累計報告確診病例2例。

    一、基本情況

    病例2 居住于海樂花園4棟,11月5日21時由長沙乘坐AQ1148航班抵達海口美蘭機場,落地檢結果為陰性。

    二、活動軌跡

    11月5日

    21:00-21:30 海口美蘭機場;

    21:30-22:00 乘出租車返回住處。

    11月6日

    11:30-12:30 金墾路21號水箱王維修店;

    14:00-19:00 金墾路21號水箱王維修店;

    19:00-19:20 坡博農貿市場。

    11月7日

    11:30-12:30 金墾路21號水箱王維修店;

    14:00-16:30 金墾路21號水箱王維修店;

    16:35-17:00 金宇街道大規模采樣點;

    17:00-20:17 金墾路21號水箱王維修店;

    20:27-20:57 海墾路81號八一夜市。

    請在上述時間段、地點與該人員有接觸或有軌跡交集的人員第一時間主動向單位、社區(村委會)或所住宿賓館報告,配合做好流調及相關健康管理措施,如瞞報、遲報、謊報將依法追究相關責任。

    目前我市已迅速組織流調排查、場所管控、消毒、人員轉運等工作,并已對相關人員實施管控。請廣大市民不信謠、不傳謠,涉疫消息以官方發布為準,繼續增強防范意識,科學佩戴口罩、勤洗手、常通風、保持安全社交距離、不扎堆、不聚集,配合落實掃地點碼工作,盡早全程接種新冠病毒疫苗。

    海口市新型冠狀病毒肺炎

    疫情防控工作指揮部

    2022年11月8日

    文預警!!!

    UWP 程序有 .NET Native 可以將程序集編譯為本機代碼,逆向的難度會大很多;而基于 .NET Framework 和 .NET Core 的程序卻沒有 .NET Native 的支持。雖然有 Ngen.exe 可以編譯為本機代碼,但那只是在用戶計算機上編譯完后放入了緩存中,而不是在開發者端編譯。

    于是有很多款混淆工具來幫助混淆基于 .NET 的程序集,使其稍微難以逆向。本文介紹 Smart Assembly 各項混淆參數的作用以及其實際對程序集的影響。


    本文不會講 SmartAssembly 的用法,因為你只需打開它就能明白其基本的使用。

    感興趣可以先下載:.NET Obfuscator, Error Reporting, DLL Merging - SmartAssembly。

    準備

    我們先需要準備程序集來進行混淆試驗。這里,我使用 Whitman 來試驗。它在 GitHub 上開源,并且有兩個程序集可以試驗它們之間的相互影響。


    額外想吐槽一下,SmartAssembly 的公司 Red Gate 一定不喜歡這款軟件,因為界面做成下面這樣竟然還長期不更新:


    而且,如果要成功編譯,還得用上同為 Red Gate 家出品的 SQL Server,如果不裝,軟件到處彈窗報錯。只是報告錯誤而已,干嘛還要開發者裝一個那么重量級的 SQL Server 啊!詳見:Why is SQL Server required — Redgate forums。

    SmartAssembly

    SmartAssembly 本質上是保護應用程序不被逆向或惡意篡改。目前我使用的版本是 6,它提供了對 .NET Framework 程序的多種保護方式:

    • 強簽名 Strong Name Signing
    • 強簽名可以確保程序之間的依賴關系是嚴格確定的,如果對其中的一個依賴進行篡改,將導致無法加載正確的程序集。
    • 微軟提供了強簽名工具,所以可以無需使用 SmartAssembly 的:
    • Sn.exe (Strong Name Tool) - Microsoft Docs
    • How to: Sign an Assembly with a Strong Name - Microsoft Docs
    • 自動錯誤上報 Automated Error Reporting
    • SmartAssembly 會自動向 exe 程序注入異常捕獲與上報的邏輯。
    • 功能使用率上報 Feature Usage Reporting
    • SmartAssembly 會修改每個方法,記錄這些方法的調用次數并上報。
    • 依賴合并 Dependencies Merging
    • SmartAssembly 會將程序集中你勾選的的依賴與此程序集合并成一個整的程序集。
    • 依賴嵌入 Dependencies Embedding
    • SmartAssembly 會將依賴以加密并壓縮的方式嵌入到程序集中,運行時進行解壓縮與解密。
    • 其實這只是方便了部署(一個 exe 就能發給別人),并不能真正保護程序集,因為實際運行時還是解壓并解密出來了。
    • 裁剪 Pruning
    • SmartAssembly 會將沒有用到的字段、屬性、方法、事件等刪除。它聲稱刪除了這些就能讓程序逆向后代碼更難讀懂。
    • 名稱混淆 Obfuscation
    • 修改類型、字段、屬性、方法等的名稱。
    • 流程混淆 Control Flow Obfuscation
    • 修改方法內的執行邏輯,使其執行錯綜復雜。
    • 動態代理 References Dynamic Proxy
    • SmartAssembly 會將方法的調用轉到動態代理上。
    • 資源壓縮加密 Resources Compression and Encryption
    • SmartAssembly 會將資源以加密并壓縮的方式嵌入到程序集中,運行時進行解壓縮與解密。
    • 字符串壓縮加密 Strings Encoding
    • SmartAssembly 會將字符串都進行加密,運行時自動對其進行解密。
    • 防止 MSIL Disassembler 對其進行反編譯 MSIL Disassembler Protection
    • 在程序集中加一個 Attribute,這樣 MSIL Disassembler 就不會反編譯這個程序集。
    • 密封類
    • 如果 SmartAssembly 發現一個類可以被密封,就會把它密封,這樣能獲得一點點性能提升。
    • 生成調試信息 Generate Debugging Information
    • 可以生成混淆后的 pdb 文件

    以上所有 SmartAssembly 對程序集的修改中,我標為 粗體 的是真的在做混淆,而標為 斜體 的是一些輔助功能。

    后面我只會說明其混淆功能。

    裁剪 Pruning

    我故意在 Whitman.Core 中寫了一個沒有被用到的 internal 類 UnusedClass,如果我們開啟了裁剪,那么這個類將消失。

    ▲ 沒用到的類將消失

    特別注意,如果標記了 InternalsVisibleTo,尤其注意不要不小心被誤刪了。

    名稱混淆 Obfuscation

    類/方法名與字段名的混淆

    名稱混淆中,類名和方法名的混淆有三個不同級別:

    • 等級 1 是使用 ASCII 字符集
    • 等級 2 是使用不可見的 Unicode 字符集
    • 等級 3 是使用高級重命名算法的不可見的 Unicode 字符集

    需要注意:對于部分程序集,類與方法名(NameMangling)的等級只能選為 3,否則混淆程序會無法完成編譯

    字段名的混淆有三個不同級別:

    • 等級 1 是源碼中字段名稱和混淆后字段名稱一一對應
    • 等級 2 是在一個類中的不同字段使用不同名稱即可(這不廢話嗎,不過 SmartAssembly 應該是為了強調與等級 1 和等級 3 的不同,必須寫一個描述)
    • 等級 3 是允許不同類中的字段使用相同的名字(這樣能夠更加讓人難以理解)

    需要注意:對于部分程序集,字段名(FieldsNameMangling)的等級只能選為 2 或 3,否則混淆程序會無法完成編譯

    實際試驗中,以上各種組合經常會出現無法編譯的情況。

    下面是 Whitman 中 RandomIdentifier 類中的部分字段在混淆后的效果:

    // Token: 0x04000001 RID: 1
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private int \u0001;
    // Token: 0x04000002 RID: 2
    private readonly Random \u0001=new Random();
    // Token: 0x04000003 RID: 3
    private static readonly Dictionary<int, int> \u0001=new Dictionary<int, int>();
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    

    這部分的原始代碼可以在 冷算法:自動生成代碼標識符(類名、方法名、變量名) 找到。

    如果你需要在混淆時使用名稱混淆,你只需要在以上兩者的組合中找到一個能夠編譯通過的組合即可,不需要特別在意等級 1~3 的區別,因為實際上都做了混淆,1~3 的差異對逆向來說難度差異非常小的。

    需要 特別小心如果有 InternalsVisibleTo 或者依據名稱的反射調用,這種混淆下極有可能掛掉!!!請充分測試你的軟件,切記!!!

    轉移方法 ChangeMethodParent

    如果開啟了 ChangeMethodParent,那么混淆可能會將一個類中的方法轉移到另一個類中,這使得逆向時對類型含義的解讀更加匪夷所思。

    排除特定的命名空間

    如果你的程序集中確實存在需要被按照名稱反射調用的類型,或者有 internal 的類/方法需要被友元程序集調用,請排除這些命名空間。

    流程混淆 Control Flow Obfuscation

    列舉我在 Whitman.Core 中的方法:

    public string Generate(bool pascal)
    {
     var builder=new StringBuilder();
     var wordCount=WordCount <=0 ? 4 - (int) Math.Sqrt(_random.Next(0, 9)) : WordCount;
     for (var i=0; i < wordCount; i++)
     {
     var syllableCount=4 - (int) Math.Sqrt(_random.Next(0, 16));
     syllableCount=SyllableMapping[syllableCount];
     for (var j=0; j < syllableCount; j++)
     {
     var consonant=Consonants[_random.Next(Consonants.Count)];
     var vowel=Vowels[_random.Next(Vowels.Count)];
     if ((pascal || i !=0) && j==0)
     {
     consonant=CultureInfo.CurrentCulture.TextInfo.ToTitleCase(consonant);
     }
     builder.Append(consonant);
     builder.Append(vowel);
     }
     }
     return builder.ToString();
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    

    ▲ 這個方法可以在 冷算法:自動生成代碼標識符(類名、方法名、變量名) 找到。

    流程混淆修改方法內部的實現。為了了解各種不同的流程混淆級別對代碼的影響,我為每一個混淆級別都進行反編譯查看。

    ▲ 沒有混淆

    0 級流程混淆

    ▲ 0 級流程混淆

    1 級流程混淆

    ▲ 1 級流程混淆

    可以發現 0 和 1 其實完全一樣。又被 SmartAssembly 耍了。

    2 級流程混淆

    2 級流程混淆代碼很長,所以我沒有貼圖:

    // Token: 0x06000004 RID: 4 RVA: 0x00002070 File Offset: 0x00000270
    public string Generate(bool pascal)
    {
     StringBuilder stringBuilder=new StringBuilder();
     StringBuilder stringBuilder2;
     if (-1 !=0)
     {
     stringBuilder2=stringBuilder;
     }
     int num2;
     int num=num2=this.WordCount;
     int num4;
     int num3=num4=0;
     int num6;
     int num8;
     if (num3==0)
     {
     int num5=(num <=num3) ? (4 - (int)Math.Sqrt((double)this._random.Next(0, 9))) : this.WordCount;
     if (true)
     {
     num6=num5;
     }
     int num7=0;
     if (!false)
     {
     num8=num7;
     }
     if (false)
     {
     goto IL_10E;
     }
     if (7 !=0)
     {
     goto IL_134;
     }
     goto IL_8E;
     }
     IL_6C:
     int num9=num2 - num4;
     int num10;
     if (!false)
     {
     num10=num9;
     }
     int num11=RandomIdentifier.SyllableMapping[num10];
     if (6 !=0)
     {
     num10=num11;
     }
     IL_86:
     int num12=0;
     int num13;
     if (!false)
     {
     num13=num12;
     }
     IL_8E:
     goto IL_11E;
     IL_10E:
     string value;
     stringBuilder2.Append(value);
     num13++;
     IL_11E:
     string text;
     bool flag;
     if (!false)
     {
     if (num13 >=num10)
     {
     num8++;
     goto IL_134;
     }
     text=RandomIdentifier.Consonants[this._random.Next(RandomIdentifier.Consonants.Count)];
     value=RandomIdentifier.Vowels[this._random.Next(RandomIdentifier.Vowels.Count)];
     flag=((pascal || num8 !=0) && num13==0);
     }
     if (flag)
     {
     text=CultureInfo.CurrentCulture.TextInfo.ToTitleCase(text);
     }
     if (!false)
     {
     stringBuilder2.Append(text);
     goto IL_10E;
     }
     goto IL_86;
     IL_134:
     if (num8 >=num6)
     {
     return stringBuilder2.ToString();
     }
     num2=4;
     num4=(int)Math.Sqrt((double)this._random.Next(0, 16));
     goto IL_6C;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    

    ▲ 2 級流程混淆

    這時就發現代碼的可讀性降低了,需要耐心才能解讀其含義。

    3 級流程混淆

    以下是 3 級流程混淆:

    // Token: 0x06000004 RID: 4 RVA: 0x0000207C File Offset: 0x0000027C
    public string Generate(bool pascal)
    {
     StringBuilder stringBuilder=new StringBuilder();
     int num2;
     int num=num2=this.WordCount;
     int num4;
     int num3=num4=0;
     int num7;
     int num8;
     string result;
     if (num3==0)
     {
     int num5;
     if (num > num3)
     {
     num5=this.WordCount;
     }
     else
     {
     int num6=num5=4;
     if (num6 !=0)
     {
     num5=num6 - (int)Math.Sqrt((double)this._random.Next(0, 9));
     }
     }
     num7=num5;
     num8=0;
     if (false)
     {
     goto IL_104;
     }
     if (7==0)
     {
     goto IL_84;
     }
     if (!false)
     {
     goto IL_12A;
     }
     return result;
     }
     IL_73:
     int num9=num2 - num4;
     num9=RandomIdentifier.SyllableMapping[num9];
     IL_81:
     int num10=0;
     IL_84:
     goto IL_114;
     IL_104:
     string value;
     stringBuilder.Append(value);
     num10++;
     IL_114:
     string text;
     bool flag;
     if (!false)
     {
     if (num10 >=num9)
     {
     num8++;
     goto IL_12A;
     }
     text=RandomIdentifier.Consonants[this._random.Next(RandomIdentifier.Consonants.Count)];
     value=RandomIdentifier.Vowels[this._random.Next(RandomIdentifier.Vowels.Count)];
     flag=((pascal || num8 !=0) && num10==0);
     }
     if (flag)
     {
     text=CultureInfo.CurrentCulture.TextInfo.ToTitleCase(text);
     }
     if (!false)
     {
     stringBuilder.Append(text);
     goto IL_104;
     }
     goto IL_81;
     IL_12A:
     if (num8 < num7)
     {
     num2=4;
     num4=(int)Math.Sqrt((double)this._random.Next(0, 16));
     goto IL_73;
     }
     result=stringBuilder.ToString();
     return result;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    

    ▲ 3 級流程混淆

    3 級流程混淆并沒有比 2 級高多少,可讀性差不多。不過需要注意的是,這些差異并不是隨機差異,因為重復生成得到的流程結果是相同的。

    4 級流程混淆

    以下是 4 級流程混淆:

    // Token: 0x06000004 RID: 4 RVA: 0x0000207C File Offset: 0x0000027C
    public unsafe string Generate(bool pascal)
    {
     void* ptr=stackalloc byte[14];
     StringBuilder stringBuilder=new StringBuilder();
     StringBuilder stringBuilder2;
     if (!false)
     {
     stringBuilder2=stringBuilder;
     }
     int num=(this.WordCount <=0) ? (4 - (int)Math.Sqrt((double)this._random.Next(0, 9))) : this.WordCount;
     *(int*)ptr=0;
     for (;;)
     {
     ((byte*)ptr)[13]=((*(int*)ptr < num) ? 1 : 0);
     if (*(sbyte*)((byte*)ptr + 13)==0)
     {
     break;
     }
     *(int*)((byte*)ptr + 4)=4 - (int)Math.Sqrt((double)this._random.Next(0, 16));
     *(int*)((byte*)ptr + 4)=RandomIdentifier.SyllableMapping[*(int*)((byte*)ptr + 4)];
     *(int*)((byte*)ptr + 8)=0;
     for (;;)
     {
     ((byte*)ptr)[12]=((*(int*)((byte*)ptr + 8) < *(int*)((byte*)ptr + 4)) ? 1 : 0);
     if (*(sbyte*)((byte*)ptr + 12)==0)
     {
     break;
     }
     string text=RandomIdentifier.Consonants[this._random.Next(RandomIdentifier.Consonants.Count)];
     string value=RandomIdentifier.Vowels[this._random.Next(RandomIdentifier.Vowels.Count)];
     bool flag=(pascal || *(int*)ptr !=0) && *(int*)((byte*)ptr + 8)==0;
     if (flag)
     {
     text=CultureInfo.CurrentCulture.TextInfo.ToTitleCase(text);
     }
     stringBuilder2.Append(text);
     stringBuilder2.Append(value);
     *(int*)((byte*)ptr + 8)=*(int*)((byte*)ptr + 8) + 1;
     }
     *(int*)ptr=*(int*)ptr + 1;
     }
     return stringBuilder2.ToString();
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    

    ▲ 4 級流程混淆

    我們發現,4 級已經開始使用沒有含義的指針來轉換我們的內部實現了。這時除了外部調用以外,代碼基本已無法解讀其含義了。

    動態代理 References Dynamic Proxy

    還是以上一節中我們 Generate 方法作為示例,在開啟了動態代理之后(僅開啟動態代理,其他都關掉),方法變成了下面這樣:

    // Token: 0x06000004 RID: 4 RVA: 0x0000206C File Offset: 0x0000026C
    public string Generate(bool pascal)
    {
     StringBuilder stringBuilder=new StringBuilder();
     int num=(this.WordCount <=0) ? (4 - (int)\u0002.\u0002((double)\u0001.~\u0001(this._random, 0, 9))) : this.WordCount;
     for (int i=0; i < num; i++)
     {
     int num2=4 - (int)\u0002.\u0002((double)\u0001.~\u0001(this._random, 0, 16));
     num2=RandomIdentifier.SyllableMapping[num2];
     for (int j=0; j < num2; j++)
     {
     string text=RandomIdentifier.Consonants[\u0003.~\u0003(this._random, RandomIdentifier.Consonants.Count)];
     string text2=RandomIdentifier.Vowels[\u0003.~\u0003(this._random, RandomIdentifier.Vowels.Count)];
     bool flag=(pascal || i !=0) && j==0;
     if (flag)
     {
     text=\u0006.~\u0006(\u0005.~\u0005(\u0004.\u0004()), text);
     }
     \u0007.~\u0007(stringBuilder, text);
     \u0007.~\u0007(stringBuilder, text2);
     }
     }
     return \u0008.~\u0008(stringBuilder);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    

    ▲ 動態代理

    注意到 _random.Next(0, 9) 變成了 \u0001.~\u0001(this._random, 0, 9),Math.Sqrt(num) 變成了 \u0002.\u0002(num)。

    也就是說,一些常規方法的調用被替換成了一個代理類的調用。那么代理類在哪里呢?

    ▲ 生成的代理類

    生成的代理類都在根命名空間下。比如剛剛的 \u0001.~\u0001 調用,就是下面這個代理類:

    // Token: 0x0200001A RID: 26
    internal sealed class \u0001 : MulticastDelegate
    {
     // Token: 0x06000030 RID: 48
     public extern \u0001(object, IntPtr);
     // Token: 0x06000031 RID: 49
     public extern int Invoke(object, int, int);
     // Token: 0x06000032 RID: 50 RVA: 0x000030A8 File Offset: 0x000012A8
     static \u0001()
     {
     MemberRefsProxy.CreateMemberRefsDelegates(25);
     }
     // Token: 0x04000016 RID: 22
     internal static \u0001 \u0001;
     // Token: 0x04000017 RID: 23
     internal static \u0001 ~\u0001;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    

    字符串編碼與加密 Strings Encoding

    字符串統一收集編碼 Encode

    字符串編碼將程序集中的字符串都統一收集起來,存為一個資源;然后提供一個輔助類統一獲取這些字符串。

    比如 Whitman.Core 中的字符串現在被統一收集了:

    ▲ 統一收集的字符串和解密輔助類

    在我的項目中,統一收集的字符串可以形成下面這份字符串(也即是上圖中 Resources 文件夾中的那個文件內容):

    cQ==dw==cg==dA==eQ==cA==cw==ZA==Zg==Zw==aA==ag==aw==bA==eg==eA==Yw==dg==Yg==bg==bQ==dHI=ZHI=Y2g=d2g=c3Q=YQ==ZQ==aQ==bw==dQ==YXI=YXM=YWk=YWlyYXk=YWw=YWxsYXc=ZWU=ZWE=ZWFyZW0=ZXI=ZWw=ZXJlaXM=aXI=b3U=b3I=b28=b3c=dXI=MjAxOC0wOC0yNlQxODoxMDo0Mw==`VGhpcyBhc3NlbWJseSBoYXMgY
    mVlbiBidWlsdCB3aXRoIFNtYXJ0QXNzZW1ibHkgezB9LCB3aGljaCBoYXMgZXhwaXJlZC4=RXZhbHVh
    dGlvbiBWZXJzaW9uxVGhpcyBhc3NlbWJseSBoYXMgYmVlbiBidWlsdCB3aXRoIFNtYXJ0QXNzZW1ibHk
    gezB9LCBhbmQgdGhlcmVmb3JlIGNhbm5vdCBiZSBkaXN0cmlidXRlZC4=IA==Ni4xMi41Ljc5OQ==U21hcnRBc3NlbWJseQ==UGF0aA==U29mdHdhcmVcUmVkIEdhdGVc(U29mdHdhcmVcV293NjQzMk5vZ
    GVcUmVkIEdhdGVc
    1
    2
    3
    4
    5
    6
    7
    8
    9
    

    雖然字符串難以讀懂,但其實我原本就是這么寫的;給你看看我的原始代碼就知道了(來自 冷算法:自動生成代碼標識符(類名、方法名、變量名)):

    private static readonly List<string> Consonants=new List<string>
    {
     "q","w","r","t","y","p","s","d","f","g","h","j","k","l","z","x","c","v","b","n","m",
     "w","r","t","p","s","d","f","g","h","j","k","l","c","b","n","m",
     "r","t","p","s","d","h","j","k","l","c","b","n","m",
     "r","t","s","j","c","n","m",
     "tr","dr","ch","wh","st",
     "s","s"
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    

    生成的字符串獲取輔助類就像下面這樣不太容易讀懂:

    // Token: 0x0200000A RID: 10
    public class Strings
    {
     // Token: 0x0600001C RID: 28 RVA: 0x00002B94 File Offset: 0x00000D94
     public static string Get(int stringID)
     {
     stringID -=Strings.offset;
     if (Strings.cacheStrings)
     {
     object obj=Strings.hashtableLock;
     lock (obj)
     {
     string text;
     Strings.hashtable.TryGetValue(stringID, out text);
     if (text !=null)
     {
     return text;
     }
     }
     }
     int index=stringID;
     int num=(int)Strings.bytes[index++];
     int num2;
     if ((num & 128)==0)
     {
     num2=num;
     if (num2==0)
     {
     return string.Empty;
     }
     }
     else if ((num & 64)==0)
     {
     num2=((num & 63) << 8) + (int)Strings.bytes[index++];
     }
     else
     {
     num2=((num & 31) << 24) + ((int)Strings.bytes[index++] << 16) + ((int)Strings.bytes[index++] << 8) + (int)Strings.bytes[index++];
     }
     string result;
     try
     {
     byte[] array=Convert.FromBase64String(Encoding.UTF8.GetString(Strings.bytes, index, num2));
     string text2=string.Intern(Encoding.UTF8.GetString(array, 0, array.Length));
     if (Strings.cacheStrings)
     {
     try
     {
     object obj=Strings.hashtableLock;
     lock (obj)
     {
     Strings.hashtable.Add(stringID, text2);
     }
     }
     catch
     {
     }
     }
     result=text2;
     }
     catch
     {
     result=null;
     }
     return result;
     }
     // Token: 0x0600001D RID: 29 RVA: 0x00002CF4 File Offset: 0x00000EF4
     static Strings()
     {
     if (Strings.MustUseCache=="1")
     {
     Strings.cacheStrings=true;
     Strings.hashtable=new Dictionary<int, string>();
     }
     Strings.offset=Convert.ToInt32(Strings.OffsetValue);
     using (Stream manifestResourceStream=Assembly.GetExecutingAssembly().GetManifestResourceStream("{f6b5a51a-b2fb-4143-af01-e2295062799f}"))
     {
     int num=Convert.ToInt32(manifestResourceStream.Length);
     Strings.bytes=new byte[num];
     manifestResourceStream.Read(Strings.bytes, 0, num);
     manifestResourceStream.Close();
     }
     }
     // Token: 0x0400000C RID: 12
     private static readonly string MustUseCache="0";
     // Token: 0x0400000D RID: 13
     private static readonly string OffsetValue="203";
     // Token: 0x0400000E RID: 14
     private static readonly byte[] bytes=null;
     // Token: 0x0400000F RID: 15
     private static readonly Dictionary<int, string> hashtable;
     // Token: 0x04000010 RID: 16
     private static readonly object hashtableLock=new object();
     // Token: 0x04000011 RID: 17
     private static readonly bool cacheStrings=false;
     // Token: 0x04000012 RID: 18
     private static readonly int offset=0;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    

    生成字符串獲取輔助類后,原本寫著字符串的地方就會被替換為 Strings.Get(int) 方法的調用。

    字符串壓縮加密 Compress

    前面那份統一收集的字符串依然還是明文存儲為資源,但還可以進行壓縮。這時,Resources 中的那份字符串資源現在是二進制文件(截取前 256 字節):

    00000000: 7b7a 7d02 efbf bdef bfbd 4def bfbd efbf
    00000010: bd7e 6416 efbf bd6a efbf bd22 efbf bd08
    00000020: efbf bdef bfbd 4c42 7138 72ef bfbd efbf
    00000030: bd54 1337 efbf bd0e 22ef bfbd 69ef bfbd
    00000040: 613d efbf bd6e efbf bd35 efbf bd0a efbf
    00000050: bd33 6043 efbf bd26 59ef bfbd 5471 efbf
    00000060: bdef bfbd 2cef bfbd 18ef bfbd 6def bfbd
    00000070: efbf bdef bfbd 64ef bfbd c9af efbf bdef
    00000080: bfbd efbf bd4b efbf bdef bfbd 66ef bfbd
    00000090: 1e70 efbf bdef bfbd ce91 71ef bfbd 1d5e
    000000a0: 1863 efbf bd16 0473 25ef bfbd 2204 efbf
    000000b0: bdef bfbd 11ef bfbd 4fef bfbd 265a 375f
    000000c0: 7bef bfbd 19ef bfbd d5bd efbf bdef bfbd
    000000d0: efbf bd70 71ef bfbd efbf bd05 c789 efbf
    000000e0: bd51 eaae beef bfbd ee97 adef bfbd 0a33
    000000f0: d986 141c 2bef bfbd efbf bdef bfbd 1fef
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    

    這份壓縮的字符串在程序啟動的時候會進行一次解壓,隨后就直接讀取解壓后的字符串了。所以會占用啟動時間(雖然不長),但不會占用太多運行時時間。

    為了能夠解壓出這些壓縮的字符串,Strings 類相比于之前會在讀取后進行一次解壓縮(解密)。可以看下面我額外標注出的 Strings 類新增的一行。

     using (Stream manifestResourceStream=Assembly.GetExecutingAssembly().GetManifestResourceStream("{4f639d09-ce0f-4092-b0c7-b56c205d48fd}"))
     {
     int num=Convert.ToInt32(manifestResourceStream.Length);
     byte[] buffer=new byte[num];
     manifestResourceStream.Read(buffer, 0, num);
    ++ Strings.bytes=SimpleZip.Unzip(buffer);
     manifestResourceStream.Close();
     }
    1
    2
    3
    4
    5
    6
    7
    8
    

    至于嵌入其中的解壓與解密類 SimpleZip,我就不能貼出來了,因為反編譯出來有 3000+ 行:


    字符串緩存 UseCache

    與其他的緩存策略一樣,每次獲取字符串都太消耗計算資源的話,就可以拿內存空間進行緩存。

    在實際混淆中,我發現無論我是否開啟了字符串緩存,實際 Strings.Get 方法都會緩存字符串。你可以回到上面去重新閱讀 Strings.Get 方法的代碼,發現其本來就已帶緩存。這可能是 SmartAssembly 的 Bug。

    使用類的內部委托獲取字符串 UseImprovedEncoding

    之前的混淆都會在原來有字符串地方使用 Strings.Get 來獲取字符串。而如果開啟了這一選項,那么 Strings.Get 就不是全局調用的了,而是在類的內部調用一個委托字段。

    比如從 Strings.Get 調用修改為 \u0010(),,而 \u0010 是我們自己的類 RandomIdentifier 內部的被額外加進去的一個字段 internal static GetString \u0010;。

    防止 MSIL Disassembler 對其進行反編譯 MSIL Disassembler Protection

    這其實是個沒啥用的選項,因為我們程序集只會多出一個全局的特性:

    [assembly: SuppressIldasm]
    1
    

    只有 MSIL Disassembler 和基于 MSIL Disassembler 的工具認這個特性。真正想逆向程序集的,根本不會在乎 MSIL Disassembler 被禁掉。

    dnSpy 和 dotPeek 實際上都忽略了這個特性,依然能毫無障礙地反編譯。

    dnSpy 可以做挺多事兒的,比如:

    • 斷點調試 Windows 源代碼 - lindexi
    • 神器如 dnSpy,無需源碼也能修改 .NET 程序 - walterlv

    密封

    在 OtherOptimizations 選項中,有一項 SealClasses 可以將所有可以密封的類進行密封(當然,此操作不會修改 API)。

    在上面的例子中,由于 RandomIdentifier 是公有類,可能被繼承,所以只有預先寫的內部的 UnusedClass 被其標記為密封了。

    // Token: 0x02000003 RID: 3
    internal sealed class UnusedClass
    {
     // Token: 0x06000007 RID: 7 RVA: 0x000026D0 File Offset: 0x000008D0
     internal void Run()
     {
     }
     // Token: 0x06000008 RID: 8 RVA: 0x000026D4 File Offset: 0x000008D4
     internal async Task RunAsync()
     {
     }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    

    實際項目中,我該如何選擇

    既然你希望選擇“混淆”,那么你肯定是希望能進行最大程度的保護。在保證你沒有額外產生 Bug,性能沒有明顯損失的情況下,能混淆得多厲害就混淆得多厲害。

    基于這一原則,我推薦的混淆方案有(按推薦順序排序):

    1. 流程混淆
    • 建議必選
    • 直接選用 4 級流程(不安全代碼)混淆,如果出問題才換為 3 級(goto)混淆,理論上不需要使用更低級別
    • 流程混淆對性能的影響是非常小的,因為多執行的代碼都是有編譯期級別優化的,沒有太多性能開銷的代碼
    • 流程混淆僅影響實現,不修改 API,所以基本不會影響其他程序各種對此程序集的調用
    1. 名稱混淆
    • 盡量選擇
    • 任意選擇類/方法名和字段名的級別,只要能編譯通過就行(因為無論選哪個,對程序的影響都一樣,逆向的難度差異也較小)
    • 名稱混淆不影響程序執行性能,所以只要能打開,就盡量打開
    • 如果有 InternalsVisibleTo 或者可能被其他程序集按名稱反射調用,請:
    • 關閉此混淆
    • 使用 Exclude 排除特定命名空間,使此命名空間下的類/方法名不進行名稱混淆
    • 如果你能接受用 Attribute 標記某些類不應該混淆類名,也可以使用這些標記(只是我不推薦這么做,這讓混淆污染了自己的代碼)
    1. 動態代理
    • 推薦選擇
    • 動態代理僅影響實現,不修改 API,所以基本不會影響其他程序各種對此程序集的調用
    • 動態代理會生成新的類/委托來替換之前的方法調用,所以可能造成非常輕微的性能損失(一般可以忽略)
    1. 字符串壓縮加密
    • 可以選擇
    • 由于所有的字符串都被統一成一個資源,如果額外進行壓縮加密,那么逆向時理解程序的含義將變得非常困難(沒有可以參考的錨點)
    • 會對啟動時間有輕微的性能影響,如果額外壓縮加密,那么會有更多性能影響;如果你對啟動性能要求較高,還是不要選了
    • 會輕微增加內存占用和讀取字符串時的 CPU 占用,如果你對程序性能要求非常高,還是不要選了

    以上四種混淆方式從四個不同的維度對你類與方法的實現進行了混淆,使得你寫的類的任何地方都變得無法辨認。流程混淆修改方法內實現的邏輯,名稱混淆修改類/屬性/方法的名稱,動態代理將方法內對其他方法的調用變得不再直接,字符串壓縮加密將使得字符串不再具有可讀的含義。對逆向閱讀影響最大的就是以上 4 種混淆了,如果可能,建議都選擇開啟。

    如果你的程序中有需要保護的“嵌入的資源”,在沒有自己的保護手段的情況下,可以使用“資源壓縮加密”。不過,我更加推薦你自己進行加密。

    至于 SmartAssembly 推薦的其他選項,都是噱頭重于實際效果:

    • 裁剪
    • 一般也不會有多少開發者會故意往程序集中寫一些不會用到的類吧!
    • 依賴合并/依賴嵌入
    • 并不會對逆向造成障礙,開不開啟差別不大,反而降低了性能
    • 防止 MSIL Disassembler 反編譯
    • 并不會對逆向造成障礙,防君子不防小人
    • 密封類
    • 聲稱可以提升性能,但這點性能提升微乎其微

    SmartAssembly 的官方文檔寫得還是太簡單了,很難得到每一個設置項的含義和實際效果。

    以上這些信息的得出,離不開 dnSpy 的反編譯。

網站首頁   |    關于我們   |    公司新聞   |    產品方案   |    用戶案例   |    售后服務   |    合作伙伴   |    人才招聘   |   

友情鏈接: 餐飲加盟

地址:北京市海淀區    電話:010-     郵箱:@126.com

備案號:冀ICP備2024067069號-3 北京科技有限公司版權所有