主頁 > 後端開發 > 【JS 逆向百例】某音 X-Bogus 逆向分析,JSVMP 純演算法還原

【JS 逆向百例】某音 X-Bogus 逆向分析,JSVMP 純演算法還原

2022-10-20 06:17:24 後端開發

宣告

本文章中所有內容僅供學習交流使用,不用于其他任何目的,不提供完整代碼,抓包內容、敏感網址、資料介面等均已做脫敏處理,嚴禁用于商業用途和非法用途,否則由此產生的一切后果均與作者無關!

本文章未經許可禁止轉載,禁止任何修改后二次傳播,擅自使用本文講解的技術而導致的任何意外,作者均不負責,若有侵權,請在公眾號【K哥爬蟲】聯系作者立即洗掉!

逆向目標

  • 目標:某音網頁端用戶資訊介面 X-Bogus 引數
  • 介面:aHR0cHM6Ly93d3cuZG91eWluLmNvbS9hd2VtZS92MS93ZWIvdXNlci9wcm9maWxlL290aGVyLw==

什么是 JSVMP?

JSVMP 全稱 Virtual Machine based code Protection for JavaScript,即 JS 代碼虛擬化保護方案,

JSVMP 的概念最早應該是由西北大學2015級碩士研究生匡開圓,在其2018年的學位論文中提出的,論文標題為:《基于 WebAssembly 的 JavaScript 代碼虛擬化保護方法研究與實作》,同年還申請了國家專利,專利名稱:《一種基于前端位元組碼技術的 JavaScript 虛擬化保護方法》,網上可以直接搜到,也可在公眾號【K哥爬蟲】后臺回復 JSVMP,免費獲取原版高清無水印的論文和專利,本文就簡單介紹一下 JSVMP,想要詳細了解,當然還是建議去讀一下這篇論文,

01

JSVMP 的核心是在 JavaScript 代碼保護程序中引入代碼虛擬化思想,實作源代碼的虛擬化程序,將目標代碼轉換成自定義的位元組碼,這些位元組碼只有特殊的解釋器才能識別,隱藏目標代碼的關鍵邏輯,在匡開圓的論文中,利用 WebAssembly 技術實作了特殊的虛擬解釋器,通過編譯隱藏解釋器的執行邏輯,JSVMP 的保護流程如下圖所示:

02

一個完整的 JSVMP 保護系統,大致的架構應該是這樣子的:服務器端讀取 JavaScript 代碼 —> 詞法分析 —> 語法分析 —> 生成AST語法樹 —> 生成私有指令 —> 生成對應私有解釋器,將私有指令加密與私有解釋器發送給瀏覽器,然后一邊解釋,一邊執行,

03

JSVMP 有哪些學習資料?

除了匡開圓的論文以外,還有以下文章也值得學習:

  • H5應用加固防破解-js虛擬機保護方案淺談
  • JS加密?用虛擬機opcode保護JS原始碼
  • 給"某音"的js虛擬機寫一個編譯器

JSVMP 逆向方法有哪些?

就目前來講,JSVMP 的逆向方法有三種(自動化不算):RPC 遠程呼叫,補環境,日志斷點還原演算法,其中日志斷點也稱為插樁,找到關鍵位置,輸出關鍵引數的日志資訊,從結果往上倒推生成邏輯,以達到演算法還原的目的,RPC 技術K哥以前寫過文章,補環境的方式以后有時間再寫,本文主要介紹如何使用插樁來還原演算法,

抓包情況

隨便來到某個博主主頁,抓包后搜索可發現一個介面,回傳的是 JSON 資料,里面包含了博主某音號,認證資訊、簽名,關注、粉絲、獲贊等,請求 Query String Parameters 里包含了一個 X-Bogus 引數,每次請求會改變,此外還有 sec_user_id 是博主主頁 URL 后面那一串,webid 直接請求主頁回傳內容里就有,msToken 與 cookie 有關,清除 cookie 訪問,就沒這個引數了,實測該介面不驗證 webidmsToken,直接置空即可,

04

05

逆向分析

這條請求是 XHR 請求,所以直接下個 XHR 斷點,當 URL 中包含 X-Bogus 引數時就斷下:

06

07

往前跟堆疊,來到一個叫 webmssdk.js 的 JS 檔案,這里就是生成引數的主要 JS 邏輯了,也就是 JSVMP,整體上做了一個混淆,這里可以使用 AST 來解混淆,K哥以前同樣也寫過 AST 的文章,這里還原混淆不是重點,咱們直接使用 V 佬的插件 v_jstools 來還原:

08

還原后使用瀏覽器的 Overrides 替換功能將 webmssdk.js 替換掉,往上跟堆疊,如下圖所示,到 W 這里就已經生成了 X-Bogus 了,this.openArgs[1] 就是攜帶了 X-Bogus 的完整 URL,仔細觀察這段代碼,有很多三元運算式,當 M 的值為 15 時,就會走到這段邏輯,U 的值生成之后,有一個 S[C] = U 的操作,

09

再往上看代碼,S 是一個陣列,單步除錯的話會發現代碼會一直走這個 if-else 的邏輯,幾乎每一步都有 S 陣列的參與,不斷往里面增刪改查值,for 回圈里面的 I 值,決定著后續 if 陳述句的走向,這里也就是插樁的關鍵所在,如下圖所示:

10

插樁分析

大的 for 回圈和 if-else 邏輯有兩個地方,為了保證最后的日志更加詳細完整,在這兩個地方都下個日志斷點(右鍵 Add logpoint),斷點內容為:

"位置 1", "索引I", I, "索引A", A, "值S: ", JSON.stringify(S, function(key, value) {if (value =https://www.cnblogs.com/ikdl/p/= window) {return undefined} return value})"位置 2", "索引I", I, "索引A", A, "值S: ", JSON.stringify(S, function(key, value) {if (value =https://www.cnblogs.com/ikdl/p/= window) {return undefined} return value})

11

插樁輸出 S 的時候為什么要寫這么長一串呢?首先 JSON.stringify() 方法的作用是將 JavaScript 值轉換為 JSON 字串,基礎語法是 JSON.stringify(value[, replacer [, space]]),如果不將其轉換成 JSON,那么 S 的值,輸出可能是這樣的:[empty, Array(26), 1, Array(0)],你看不到 Array 陣列里面具體的值,該方法有個可選引數 replacer,如果 replacer 為函式,則 JSON.stringify 將呼叫該函式,并傳入每個成員的鍵和值,在函式中可以對成員進行處理,最后回傳處理后的值,如果此函式回傳 undefined,則排除該成員,舉個例子:

var obj1 = {key1: 'value1', key2: 'value2'}
function changeValue(key, value) {
    if (value =https://www.cnblogs.com/ikdl/p/='value2') {
        return '公眾號:K哥爬蟲'
    } return value
}
var obj2 = JSON.stringify(obj1, changeValue)
console.log(obj2)

// 輸出:{"key1":"value1","key2":"公眾號:K哥爬蟲"}

上面的代碼中 JSON.stringify 傳入了一個函式,當 valuevalue2 的時候就將其替換成字串 公眾號:K哥爬蟲,接下來我們演示一下當 valuewindow 時,會發生什么:

12

根據報錯我們可以看到這里由于回圈參考導致例外,要知道在插樁的時候,如果插樁內容有報錯,就會導致不能正常輸出日志,這樣就會缺失一部分日志,這種情況我們就可以加個函式處理一下,讓 value 為 window 的時候,JSON 處理的時候函式回傳 undefined,排除該成員,其他成員正常輸出,如下圖所示:

13

以上就是日志斷點為什么要這樣寫的原因,下好日志斷點后,注意前面我們下的 XHR 斷點不要取消,然后重繪網頁,控制臺就開始列印日志了,因為有很多 XHR 請求都包含了 X-Bogus,如果你 XHR 斷點取消了,日志就會一直列印直到卡死,日志輸出完畢后,大約有8千多條,搜索就能看到最后一條日志 X-Bogus 已經生成了:

14

28個字符生成邏輯

直接在列印的日志頁面右鍵 save as..,將日志匯出到本地進行分析,X-Bogus 由28個字符組成,現在要做的就是看 DFSzswVOAATANH89SMHZqF9WX7n6 這28個字符是怎么來的,在日志里搜索這個字串,找到第一次出現的地方,觀察一下可以發現,他是逐個字符依次生成的,如下圖紅框所示:

15

在上圖中,第8511行,X-Bogus 字串的下一個元素是 null,到了第8512行,就生成數字6了,那么在這兩步之間就是數字6的生成邏輯,這個時候我們看第8511行的日志斷點是 位置 2 索引I 16 索引A 738,那么我們回到原網頁,在位置2,下一個條件斷點(右鍵 Add conditional breakpoint),當 I == 16 && A == 738 && S[7] && S[7] == 21 時就斷下,之所以要加 S[7] 是因為 索引I 16 索引A 738 的位置有很多,在日志里搜一下大概有40多個,多加個限制條件就可以縮小范圍,當然有可能加了多個條件仍然有多個位置都滿足,這就需要你細心觀察了,通過斷點斷下的時候看看控制臺前面輸出的日志來判斷是不是我們想要的位置,這也是一個小細節,一定要找準位置,千萬別搞混了,(提示一下,像我這樣下斷點的話,一般情況下會斷下兩次,第二次是滿足要求的)

(注意:本文描述的日志的多少行、斷點的具體位置、變數的具體值,可能會有所變化,以你的實際情況為準,但思路是一樣的)

16

重繪網頁,斷下之后開始單步跟,來到下圖所示的地方:

17

到這里之后,就不要下一步了,再下一步有可能整個陳述句就執行完畢了,其中的細節你看不到,所以這里我們在控制臺挨個輸入看看:

18

可以看到實際上的邏輯就是回傳指定位置的字符,y 的值就是 S[5],m 的值就是 S[4],經過多次除錯發現 m 的值是固定的,M 就是 charAt() 方法,我們再看看我們本地的日志,S[5] 的值為 [20]charAt() 取值出來就是6,邏輯完全正確,

19

現在我們還需要知道這個20是怎么來的,繼續往上看,找到20第一次出現的地方,在第8510行,那么我們就要使其在上一步斷下,也就是第8509行,如下圖所示:

20

第8509行的索引資訊為 位置 2 索引I 47 索引A 730,同樣的下條件斷點觀察怎么生成的:

21

22

可以看到邏輯是 S[5] & S[6],再看我們本地 S[5] = 5647508S[6] = 63 5647508 & 63 = 20,邏輯正確,20就是這么來的,接下來又開始找 564750863 是怎么生成的,同樣在生成的上一步,也就是8508行下個條件斷點,這行的索引為 位置 2 索引I 72 索引A 726

23

24

25

可以看到 63 是直接 q[A] 生成的,q 是一個大陣列,A 就是索引為 726,q 這個大陣列怎么來的先不用管,而 5647508 這個大數字,搜索一下,發現有很多,咱們也先放著,到這里咱們可以總結一下最后一個字符的生成步驟如下:

short_str = "Dkdpgh4ZKsQB80/Mfvw36XI1R25-WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe="

q[726] = 63
5647508 & 63 = 20
short_str.charAt(20) = '6'

然后接日志著往上看,看倒數第二個字母是怎么來的,方法也和前面演示的一樣,不斷往前下條件斷點,這里就不再逐步演示了,當你找完四個數字后,就可以開始看 5647508 這個大數字怎么來的了,搜索這個數字,同樣的找到第一次出現的地方,在其前一步下條件斷點,步驟捋出來會發現有一個亂碼字串經過 charCodeAt() 操作,再加上一些位運算得到的,亂碼字串類似下圖所示:

26

至于這個亂碼字串怎么來的,我們后面再講,到這里先總結一下,首先我們的 X-Bogus = DFSz swVO AATA NH89 SMHZ qF9W X7n6,將其看成每四個為一組,之所以這么分組,是因為你經過分析后會發現,每一組的每一個字符生成流程都是一樣的,這里以最后兩組為例,流程大致如下:

short_str = "Dkdpgh4ZKsQB80/Mfvw36XI1R25-WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe="
X-Bogus = DFSz swVO AATA NH89 SMHZ qF9W X7n6

============== 第6組【qF9W】==============

"\u0002?-%.*yê^?s6ey??y?V,?".charCodeAt(15) = 158
q[342] = 16
158 << 16 = 10354688
"\u0002?-%.*yê^?s6ey??y?V,?".charCodeAt(16) = 253
q[408] = 8
253 << 8 = 64768
10354688 | 64768 = 10419456
"\u0002?-%.*yê^?s6ey??y?V,?".charCodeAt(17) = 156
156 | 10419456 = 10419612

q[520] = 16515072
10419612 & 16515072 = 10223616
q[532] = 18
10223616 >> 18 = 39
short_str.charAt(39) = 'q'

q[590]= 258048
10419612 & 258048 = 192512
q[602] = 12
192512 >> 12 = 47
short_str.charAt(47) = 'F'

q[660] = 4032
10419612 & 4032 = 3456
q[668] = 6
3456 >> 6 = 54
short_str.charAt(54) = '9'

q[726] = 63
10419612 & 63 = 28
short_str.charAt(28) = 'W'

============== 第7組【X7n6】==============

"\u0002?-%.*yê^?s6ey??y?V,?".charCodeAt(18) = 86
q[342] = 16
86 << 16 = 5636096
"\u0002?-%.*yê^?s6ey??y?V,?".charCodeAt(19) = 44
q[408] = 8
44 << 8 = 11264
5636096 | 11264 = 5647360
"\u0002?-%.*yê^?s6ey??y?V,?".charCodeAt(20) = 148
148 | 5647360 = 5647508

q[520] = 16515072
5647508 & 16515072 = 5505024
q[532] = 18
5505024 >> 18 = 21
short_str.charAt(21) = 'X'

q[590] = 258048
5647508 & 258048 = 139264
q[602] = 12
139264 >> 12 = 34
short_str.charAt(34) = '7'

q[660] = 4032
5647508 & 4032 = 3200
q[668] = 6
3200 >> 6 = 50
short_str.charAt(50) = 'n'

q[726] = 63
5647508 & 63 = 20
short_str.charAt(20) = '6'

將流程對比一下就可以發現,每個步驟 q 里面的取值都是一樣的,這個可以直接寫死,不同之處就在于最開始的 charCodeAt() 操作,也就是回傳亂碼字串指定位置字符的 Unicode 編碼,第7組依次是 18、19、20,第6組依次是15、16、17,以此類推,第1組剛好是0、1、2,如下圖所示:

27

每一組的邏輯都是一樣的,我們就可以寫個通用方法,依次生成七組字串,最后拼接成完整的 X-Bogus,代碼如下:(亂碼字串的生成后文會講)

function getXBogus(originalString){
    // 生成亂碼字串
    var garbledString = getGarbledString(originalString);
    var XBogus = "";
    // 依次生成七組字串
    for (var i = 0; i <= 20; i += 3) {
        var charCodeAtNum0 = garbledString.charCodeAt(i);
        var charCodeAtNum1 = garbledString.charCodeAt(i + 1);
        var charCodeAtNum2 = garbledString.charCodeAt(i + 2);
        var baseNum = charCodeAtNum2 | charCodeAtNum1 << 8 | charCodeAtNum0 << 16;
        // 依次生成四個字符
        var str1 = short_str[(baseNum & 16515072) >> 18];
        var str2 = short_str[(baseNum & 258048) >> 12];
        var str3 = short_str[(baseNum & 4032) >> 6];
        var str4 = short_str[baseNum & 63];
        XBogus += str1 + str2 + str3 + str4;
    }
    return XBogus;
}

亂碼字串生成邏輯

在進行下一步之前,我們要注意兩點:

  • 文章演示有些變數前后不對應,因為每次插樁的值都是會變的,看流程就行了,流程是正確的;

  • 我們日志輸出是經過 JSON.stringify 處理了的,有些步驟是向某個函式傳入亂碼字串進行處理,你會發現處理后的結果和日志不一致,這是正常的,

亂碼字串的生成相對來說稍微復雜一點,但思路仍然一樣,這里就不一一截圖展示了,直接用日志描述一下關鍵步驟,注意以下日志是正向的步驟,就不逆著推了,建議自己先逆著把流程走一走,再來看這個步驟就看得懂了,

Step1:首先對 URL 后面的引數,也就是 Query String Parameters 進行兩次 MD5、兩次轉 Uint8Array 處理,最后得到的 Uint8Array 物件在后面的步驟中用得到,步驟如下:

位置 1 索引I 4  索引A 134:將 URL 后面的引數進行 MD5 加密得到字串
位置 1 索引I 16 索引A 460:將上一步的字串轉換為 Uint8Array 物件
位置 1 索引I 4  索引A 134:將上一步的 Uint8Array 物件進行 MD5 加密,得到字串
位置 1 索引I 29 索引A 472:將上一步的字串轉換為 Uint8Array 物件

上述步驟中,我們將最終得到的結果命名為 uint8Array,關鍵代碼實作如下:

var md5 = require("md5");

// 字串轉換為 Uint8Array 物件,缺失的變數自行補齊
_0x5960a2 = function(a) {
    for (var c = a.length >> 1, e = c << 1, b = new Uint8Array(c), d = 0, f = 0; f < e; ) {
        b[d++] = _0x511f86[a.charCodeAt(f++)] << 4 | _0x511f86[a.charCodeAt(f++)];
    }
    return b;
}

// originalString: URL 后面的原始引數
var uint8Array = _0x5960a2(md5(_0x5960a2(md5(originalString))));

Step2:生成兩個大數,一個是時間戳,我們稱之為 fixedString1,另一個呼叫某個方法生成,我們稱之為 fixedString2

fixedString1
位置 1 索引I 43 索引A 806:1663385262240 / 1000 = 1663385262.24

fixedString2
位置 1 索引I 16 索引A 834:M.apply(null, []) = 536919696

上述步驟中,M 對應以下方法,缺失的方法自行補齊(其中 _0x229792 是創建 canvas):

function _0x2996f8() {
    try {
        return _0x4b3b53 || (_0xb55f3e.perf ? -1 : (_0x4b3b53 = _0x229792(3735928559), _0x4b3b53));
    } catch (a) {
        return -1;
    }
}

Step3:先后生成兩個陣列,我們稱之為 array1array2array2 就是由 array1 的元素位置變換后得來的,嚴格來講,array1 不是一個完整的陣列,而是一個個數字,這一點可以在日志中體現出來,為了方便我們就直接將其視為一個陣列,兩個陣列都有19個元素,步驟如下:

array1[0] 至 array1[3] 為定值

array1[4]
位置 1 索引I 25 索引A 946:uint8Array[14]

array1[5]
位置 1 索引I 25 索引A 970:uint8Array[15]

array1[6] 至 array1[7] 為定值,8、9 與 ua 有關

array1[10]
位置 1 索引I 52 索引A 1090:fixedString1 >> 24 = 99
位置 1 索引I 47 索引A 1098:99 & 255 = 99

array1[11]
位置 1 索引I 52 索引A 1122:fixedString1 >> 16 = 25417
位置 1 索引I 47 索引A 1130:25417 & 255 = 73

array1[12]
位置 1 索引I 52 索引A 1154:fixedString1 >> 8 = 6506755
位置 1 索引I 47 索引A 1162:6506755 & 255 = 3

array1[13]
位置 1 索引I 52 索引A 1186:fixedString1 >> 0 = 241
位置 1 索引I 47 索引A 1194:241 & 255 = 241

array1[14]
位置 1 索引I 52 索引A 1218:fixedString2 >> 24 = 32
位置 1 索引I 47 索引A 1226:32 & 255 = 32

array1[15]
位置 1 索引I 52 索引A 1250:fixedString2 >> 16 = 8192
位置 1 索引I 47 索引A 1258:8192 & 255 = 0

array1[16]
位置 1 索引I 52 索引A 1282:fixedString2 >> 8 = 2097342
位置 1 索引I 47 索引A 1290:2097342 & 255 = 190

array1[17]
位置 1 索引I 52 索引A 1314:fixedString2 >> 0 = 536919696
位置 1 索引I 47 索引A 1322:536919696 & 255 = 144

array1[18]
位置 1 索引I 27 索引A 1352:array1.reduce(function(a, b) { return a ^ b; }); = 100

array1 完整值如下
位置 1 索引I 27 索引A 1538:64,1.00390625,1,8,9,185,69,63,74,125,99,73,3,241,32,0,190,144,100

array2 由 array1 元素交換位置而來:
array2 = [array1[0], array1[2], array1[4], array1[6], array1[8], array1[10], array1[12], array1[14], array1[16], array1[18], array1[1], array1[3], array1[5], array1[7], array1[9], array1[11], array1[13], array1[15], array1[17]]

array2 完整值如下
array2 = [64,1,9,69,74,99,3,32,190,100,1.00390625,8,185,63,125,73,241,0,144]

Step4:將 Step3 得到的 array2 經過轉換得到亂碼字串,步驟如下:

位置 1 索引I 16 索引A 1706:
_0x2f2740.apply(null, array2) = "@\u0000\u0001\u000eíxE??\u0016c%>? \u0000??ó"

位置 1 索引I 16 索引A 1760:
_0x46fa4c.apply(null, ["?", "@\u0000\u0001\u000e\t1E?J}cI\u0003? \u0000??d"]) = "\u0002?-%.*yê^?s6ey??y?V,?"

位置 1 索引I 16 索引A 1812:
_0x2b6720.apply(null, [2, 255, "\u0002?-%.*yê^?s6ey??y?V,?"]) = "\u0002?-%.*yê^?s6ey??y?V,?"

其中用到的函式:

function _0x2f2740(a, c, e, b, d, f, t, n, o, i, r, _, x, u, s, l, v, h, g) {
    let w = new Uint8Array(19);
    return w[0] = a,
    w[1] = r,
    w[2] = c,
    w[3] = _,
    w[4] = e,
    w[5] = x,
    w[6] = b,
    w[7] = u,
    w[8] = d,
    w[9] = s,
    w[10] = f,
    w[11] = l,
    w[12] = t,
    w[13] = v,
    w[14] = n,
    w[15] = h,
    w[16] = o,
    w[17] = g,
    w[18] = i,
    String.fromCharCode.apply(null, w);
}

function _0x46fa4c(a, c) {
    let e, b = [], d = 0, f = "";
    for (let a = 0; a < 256; a++) {
        b[a] = a;
    }
    for (let c = 0; c < 256; c++) {
        d = (d + b[c] + a.charCodeAt(c % a.length)) % 256,
        e = b[c],
        b[c] = b[d],
        b[d] = e;
    }
    let t = 0;
    d = 0;
    for (let a = 0; a < c.length; a++) {
        t = (t + 1) % 256,
        d = (d + b[t]) % 256,
        e = b[t],
        b[t] = b[d],
        b[d] = e,
        f += String.fromCharCode(c.charCodeAt(a) ^ b[(b[t] + b[d]) % 256]);
    }
    return f;
}

function _0x583250(a) {
    return String.fromCharCode(a);
}

function _0x2b6720(a, c, e) {
    return _0x583250(a) + _0x583250(c) + e;
}

自此,整個流程就走完了,可以用 JavaScript 來實作整個演算法,用 Python 也可以,完善代碼后隨便請求一個博主主頁,簡單決議幾個資料,輸出正常:

28

29

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/518479.html

標籤:Python

上一篇:python帶你制作一個gequ下載器,海量gequ免費聽

下一篇:python基礎-字典常用操作

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more