主頁 > 後端開發 > 【JS 逆向百例】網洛者反爬練習平臺第一題:JS 混淆加密,反 Hook 操作

【JS 逆向百例】網洛者反爬練習平臺第一題:JS 混淆加密,反 Hook 操作

2021-12-10 06:13:54 後端開發

關注微信公眾號:K哥爬蟲,持續分享爬蟲進階、JS/安卓逆向等技術干貨!

宣告

本文章中所有內容僅供學習交流,抓包內容、敏感網址、資料介面均已做脫敏處理,嚴禁用于商業用途和非法用途,否則由此產生的一切后果均與作者無關,若有侵權,請聯系我立即洗掉!

寫在前面

題目本身不是很難,但是其中有很多坑,主要是反 Hook 操作和本地聯調補環境,本文會詳細介紹每一個坑,并不只是一筆帶過,寫得非常詳細!

通過本文你將學到:

  1. Hook Function 和定時器來消除無限 debugger;
  2. 解決反 Hook,通過 Hook 的方式找到加密引數 _signature;
  3. 分析瀏覽器與本地環境差異,如何尋找 navigator、document、location 等物件,如何本地補環境;
  4. 如何利用 PyCharm 進行本地聯調,定位本地和瀏覽器環境的差異,從而過掉檢測,

逆向目標

  • 目標:網洛者反反爬蟲練習平臺第一題:JS 混淆加密,反 Hook 操作
  • 鏈接:http://spider.wangluozhe.com/challenge/1
  • 簡介:本題要提交的答案是100頁的所有資料并加和,要求以 Hook 的方式完成此題,不要以 AST、扣代碼等方式解決,不要使用 JS 反混淆工具進行解密,(Hook 代碼的寫法和用法,K哥以前文章有,本文不再詳細介紹)

01.png

繞過無限 debugger

首先觀察到點擊翻頁,URL 并沒有發生變化,那么一般就是 Ajax 請求,每一次請求有些引數會改變,熟練的按下 F12 準備查找加密引數,會發現立馬斷住,進入無限 debugger 狀態,往上跟一個堆疊,可以發現 debugger 字樣,如下圖所示:

02.png

這種情況在K哥以前的案例中也有,當時我們是直接重寫這個 JS,把 debugger 字樣給替換掉就行了,但是本題很顯然是希望我們以 Hook 的方法來過掉無限 debugger,除了 debugger 以外,我們注意到前面還有個 constructor 字樣,在 JavaScript 中它叫構造方法,一般在物件創建或者實體化時候被呼叫,它的基本語法是:constructor([arguments]) { ... },詳細介紹可參考 MDN 構造方法,在本案例中,很明顯 debugger 就是 constructor 的 arguments 引數,因此我們可以寫出以下 Hook 代碼來過掉無限 debugger:

// 先保留原 constructor
Function.prototype.constructor_ = Function.prototype.constructor;
Function.prototype.constructor = function (a) {
    // 如果引數為 debugger,就回傳空方法
    if(a == "debugger") {
        return function (){};
    }
    // 如果引數不為 debugger,還是回傳原方法
    return Function.prototype.constructor_(a);
};

注入 Hook 代碼的方法也有很多,比如直接在瀏覽器開發者工具控制臺輸入代碼(重繪網頁會失效)、Fiddler 插件注入、油猴插件注入、自寫瀏覽器插件注入等,這些方法在K哥以前的文章都有介紹,今天就不再贅述,

本次我們使用 Fiddler 插件注入,注入以上 Hook 代碼后,會發現會再次進入無限 debugger,setInterval,很明顯的定時器,他有兩個必須的引數,第一個是要執行的方法,第二個是時間引數,即周期性呼叫方法的時間間隔,以毫秒為單位,詳細介紹可參考菜鳥教程 Window setInterval(),同樣我們也可以將其 Hook 掉:

// 先保留原定時器
var setInterval_ = setInterval
setInterval = function (func, time){
    // 如果時間引數為 0x7d0,就回傳空方法
    // 當然也可以不判斷,直接回傳空,有很多種寫法
    if(time == 0x7d0)
    {
        return function () {};
    }
    // 如果時間引數不為 0x7d0,還是回傳原方法
    return setInterval_(func, time)
}

03.png

將兩段 Hook 代碼粘貼到瀏覽器插件里,開啟 Hook,重新重繪頁面就會發現已經過掉了無限 debugger,

04.png

Hook 引數

過掉無限 debugger 后,我們隨便點擊一頁,抓包可以看到是個 POST 請求,Form Data 里,page 是頁數,count 是每一頁資料量,_signature 是我們要逆向的引數,如下圖所示:

05.png

我們直接搜索 _signature,只有一個結果,其中有個 window.get_sign() 方法就是設定 _signature 的函式,如下圖所示:

06.png

這里問題來了!!!我們再看看本題的題目,JS 混淆加密,反 Hook 操作,作者也再三強調本題是考驗 Hook 能力!并且到目前為止,我們好像還沒有遇到什么反 Hook 手段,所以,這樣直接搜索 _signature 很顯然太簡單了,肯定是要通過 Hook 的方式來獲取 _signature,并且后續的 Hook 操作肯定不會一帆風順!

話不多說,我們直接寫一個 Hook window._signature 的代碼,如下所示:

(function() {
    //嚴謹模式 檢查所有錯誤
    'use strict';
    //window 為要 hook 的物件,這里是 hook 的 _signature
	var _signatureTemp = "";
    Object.defineProperty(window, '_signature', {
		//hook set 方法也就是賦值的方法 
		set: function(val) {
				console.log('Hook 捕獲到 _signature 設定->', val);
                debugger;
				_signatureTemp = val;
				return val;
		},
		//hook get 方法也就是取值的方法 
		get: function()
		{
			return _signatureTemp;
		}
    });
})();

將兩個繞過無限 debugger 的 Hook 代碼,和這個 Hook _signature 的代碼一起,使用 Fiddler 插件一同注入(這里注意要把繞過 debugger 的代碼放在 Hook _signature 代碼的后面,否則有可能不起作用,這可能是插件的 BUG),重新重繪網頁,可以發現前端的一排頁面的按鈕不見了,打開開發者工具,可以看到右上角提示有兩個錯誤,點擊可跳轉到出錯的代碼,在控制臺也可以看到報錯資訊,如下圖所示:

07.png

整個 1.js 代碼是經過了 sojson jsjiami v6 版本混淆了的,我們將里面的一些混淆代碼在控制臺輸出一下,然后手動還原一下這段代碼,有兩個變數 i1I1i1liillllli1,看起來費勁,直接用 ab 代替,如下所示:

(function() {
    'use strict';
    var a = '';
    Object["defineProperty"](window, "_signature", {
        set: function(b) {
            a = b;
            return b;
        },
        get: function() {
            return a;
        }
    });
}());

是不是很熟悉?有 get 和 set 方法,這不就是在進行 Hook window._signature 操作嗎?整個邏輯就是當 set 方法設定 _signature 時,將其賦值給 a,get 方法獲取 _signature 時,回傳 a,這么操作一番,實際上對于 _signature 沒有任何影響,那這段代碼存在的意義是啥?為什么我們添加了自己的 Hook 代碼就會報錯?

來看看報錯資訊:Uncaught TypeError: Cannot redefine property: _signature,不能重新定義 _signature?我們的 Hook 代碼在頁面一加載就運行了 Object.defineProperty(window, '_signature', {}),等到網站的 JS 再次 defineProperty 時就會報錯,那很簡單嘛,既然不讓重新定義,而且網站自己的 JS Hook 代碼不會影響 _signature,直接將其刪掉不就行了嘛!這個地方大概就是反 Hook 操作了,

保存原 1.js 到本地,洗掉其 Hook 代碼,使用 Fiddler 的 AutoResponder 功能替換回應(替換方法有很多,K哥以前的文章同樣有介紹),再次重繪發現例外解除,并且成功 Hook 到了 _signature

08.png

09.png

逆向引數

成功 Hook 之后,直接跟堆疊,直接把方法暴露出來了:window._signature = window.byted_acrawler(window.sign())

10.png

先來看看 window.sign(),選中它其實就可以看到是 13 位毫秒級時間戳,我們跟進 1.js 去看看他的實作代碼:

11.png

我們將部分混淆代碼手動還原一下:

window["sign"] = function sign() {
    try {
        div = document["createElement"];
        return Date["parse"](new Date())["toString"]();
    } catch (IIl1lI1i) {
        return "123456789abcdefghigklmnopqrstuvwxyz";
    }
}

這里就要注意了,有個坑給我們埋下了,如果直接略過,覺得就一個時間戳沒啥好看的,那你就大錯特錯了!注意這是一個 try-catch 陳述句,其中有一句 div = document["createElement"];,有一個 HTML DOM Document 物件,創建了 div 標簽,這段代碼如果放到瀏覽器執行,沒有任何問題,直接走 try 陳述句,回傳時間戳,如果在我們本地 node 執行,就會捕獲到 document is not defined,然后走 catch 陳述句,回傳的是那一串數字加字母,最后的結果肯定是不正確的!

解決方法也很簡單,在本地代碼里,要么去掉 try-catch 陳述句,直接 return 時間戳,要么在開頭定義一下 document,再或者直接注釋掉創建 div 標簽的這行代碼,但是K哥在這里推薦直接定義一下 document,因為誰能保證在其他地方也有類似的坑呢?萬一隱藏得很深,沒發現,豈不是白費力氣了?

然后再來看看 window.byted_acrawler(),return 陳述句里主要用到了 sign() 也就是 window.sign() 方法和 IIl1llI1() 方法,我們跟進 IIl1llI1() 方法可以看到同樣使用了 try-catch 陳述句,nav = navigator[liIIIi11('2b')]; 和前面 div 的情況如出一轍,同樣的這里也建議直接定義一下 navigator,如下圖所示:

14.png

15.png

到這里用到的方法基本上分析完畢,我們將 window、document、navigator 都定義一下后,本地運行一下,會提示 window[liIIIi11(...)] is not a function

16.png

我們去網頁里看看,會發現這個方法其實就是一個定時器,沒有太大作用,直接注釋掉即可:

17.png

PyCharm 本地聯調

經過以上操作以后,再次本地運行,會提示 window.signs is not a function,出錯的地方是一個 eval 陳述句,我們去瀏覽器看一下這個 eval 陳述句,發現明明是 window.sign(),為什么本地就變成了 window.signs(),平白無故多了個 s 呢?

18.png

19.png

造成這種情況的原因只有一個,那就是本地與瀏覽器的環境差異,混淆的代碼里肯定有環境檢測,如果不是瀏覽器環境的話,就會修改 eval 里的代碼,多加了一個 s,這里如果你直接刪掉包含 eval 陳述句的整個函式和上面的 setInterval 定時器,代碼也能正常運行,但是,K哥一向是追求細節的!多加個 s 的原因咱必須得搞清楚呀!

我們在本地使用 PyCharm 進行除錯,看看到底是哪里給加了個 s,出錯的地方是這個 eval 陳述句,我們點擊這一行,下個斷點,右鍵 debug 運行,進入除錯界面(PS:原代碼有無限 debugger,如果不做處理,PyCharm 里除錯同樣也會進入無限 debugger,可以直接把前面的 Hook 代碼加到本地代碼前面,也可以直接洗掉對應的函式或變數):

20.png

左側是呼叫堆疊,右側是變數值,整體上和 Chrome 里面的開發者工具差不多,詳細用法可參考 JetBrains 官方檔案,主要介紹一下圖中的 8 個按鈕:

  1. Show Execution Point (Alt + F10):如果你的游標在其它行或其它頁面,點擊這個按鈕可跳轉到當前斷點所在的行;
  2. Step Over (F8):步過,一行一行地往下走,如果這一行上有方法也不會進入方法;
  3. Step Into (F7):步入,如果當前行有方法,可以進入方法內部,一般用于進入用戶撰寫的自定義方法內,不會進入官方類別庫的方法;
  4. Force Step Into (Alt + Shift + F7):強制步入,能進入任何方法,查看底層原始碼的時候可以用這個進入官方類別庫的方法;
  5. Step Out (Shift + F8):步出,從步入的方法內退出到方法呼叫處,此時方法已執行完畢,只是還沒有完成賦值;
  6. Restart Frame:放棄當前斷點,重新執行斷點;
  7. Run to Cursor (Alt + F9):運行到游標處,代碼會運行至游標行,不需要打斷點;
  8. Evaluate Expression (Alt + F8):計算運算式,可以直接運行運算式,不需要在命令列輸入,

我們點擊步入按鈕(Step Into),會進入到 function IIlIliii(),這里同樣使用了 try-catch 陳述句,繼續下一步,會發現捕獲到了例外,提示 Cannot read property 'location' of undefined,如下圖所示:

21.png

我們輸出一下各個變數的值,手動還原一下代碼,如下:

function IIlIliii(II1, iIIiIIi1) {
    try {
        href = https://www.cnblogs.com/ikdl/p/window["document"]["location"]["href"];
        check_screen = screen["availHeight"];
        window["code"] = "gnature = window.byted_acrawler(window.sign())";
        return '';
    } catch (I1IiI1il) {
        window["code"] = "gnature = window.byted_acrawlers(window.signs())";
        return '';
    }
}

這么一來,就發現了端倪,在本地我們并沒有 document、location、href、availHeight 物件,所以就會走 catch 陳述句,變成了 window.signs(),就會報錯,這里解決方法也很簡單,可以直接刪掉多余代碼,直接定義為不帶 s 的那串陳述句,或者也可以選擇補一下環境,在瀏覽器里看一下 href 和 screen 的值,定義一下即可:

var window = {
    "document": {
        "location": {
            "href": "http://spider.wangluozhe.com/challenge/1"
        }
    },
}

var screen = {
    "availHeight": 1040
}

然后再次運行,又會提示 sign is not defined,這里的 sign() 其實就是 window.sign(),也就是下面的 window[liIIIi11('a')] 方法,任意改一種寫法即可:

22.png

再次運行,沒有錯誤了,我們可以自己寫一個方法來獲取 _signature:以下寫法二選一,都可以:

function getSign(){
    return window[liIIIi11('9')](window[liIIIi11('a')]())
}

function getSign(){
    return window.byted_acrawler(window.sign())
}

// 測驗輸出
console.log(getSign())

我們運行一下,發現在 Pycharm 里并沒有任何輸出,同樣的我們在題目頁面的控制臺輸出一下 console.log,發現被置空了,如下圖所示:

23.png

看來他還對 console.log 做了處理,其實這種情況問題不大,我們直接使用 Python 腳本來呼叫前面我們寫的 getSign() 方法就能得到 _signature 的值了,但是,再次重申,K哥一向是追求細節的!我就得找到處理 console.log 的地方,把它變為正常!

這里我們仍然使用 Pycharm 來除錯,進一步熟悉本地聯調,在 console.log(getSign()) 陳述句處下個斷點,一步一步跟進,會發現進到了陳述句 var IlII1li1 = function() {};,查看此時變數值,發現 console.logconsole.warn 等方法都被置空了,如下圖所示:

24.png

再往下一步跟進,發現直接回傳了,這里有可能第一次運行 JS 時就會對 console 相關命令進行方法置空處理,所以先在疑似對 console 處理的方法里面下幾個斷點,再重新除錯,會發現會走到 else 陳述句,然后直接將 IlII1li1 也就是空方法,賦值給 console 相關命令,如下圖所示:

25.png

定位到了問題所在,我們直接把 if-else 陳述句注釋掉,不讓它置空即可,然后再次除錯,發現就可以直接輸出結果了:

26.png

呼叫 Python 攜帶 _signature 挨個計算每一頁的資料,最終提交成功:

2.png

完整代碼

GitHub 關注 K 哥爬蟲,持續分享爬蟲相關代碼!歡迎 star !https://github.com/kgepachong/

以下只演示部分關鍵代碼,不能直接運行!完整代碼倉庫地址:https://github.com/kgepachong/crawler/

JavaScript 加密關鍵代碼架構

var window = {
    "document": {
        "location": {
            "href": "http://spider.wangluozhe.com/challenge/1"
        }
    },
}

var screen = {
    "availHeight": 1040
}
var document = {}
var navigator = {}
var location = {}

// 先保留原 constructor
Function.prototype.constructor_ = Function.prototype.constructor;
Function.prototype.constructor = function (a) {
    // 如果引數為 debugger,就回傳空方法
    if(a == "debugger") {
        return function (){};
    }
    // 如果引數不為 debugger,還是回傳原方法
    return Function.prototype.constructor_(a);
};

// 先保留原定時器
var setInterval_ = setInterval
setInterval = function (func, time){
    // 如果時間引數為 0x7d0,就回傳空方法
    // 當然也可以不判斷,直接回傳空,有很多種寫法
    if(time == 0x7d0)
    {
        return function () {};
    }
    // 如果時間引數不為 0x7d0,還是回傳原方法
    return setInterval_(func, time)
}

var iil = 'jsjiami.com.v6'
  , iiIIilii = [iil, '\x73\x65\x74\x49\x6e\x74\x65\x72\x76\x61\x6c', '\x6a\x73\x6a', ...];
var liIIIi11 = function(_0x11145e, _0x3cbe90) {
    _0x11145e = ~~'0x'['concat'](_0x11145e);
    var _0x636e4d = iiIIilii[_0x11145e];
    return _0x636e4d;
};
(function(_0x52284d, _0xfd26eb) {
    var _0x1bba22 = 0x0;
    for (_0xfd26eb = _0x52284d['shift'](_0x1bba22 >> 0x2); _0xfd26eb && _0xfd26eb !== (_0x52284d['pop'](_0x1bba22 >> 0x3) + '')['replace'](/[fnwRwdGKbwKrRFCtSC=]/g, ''); _0x1bba22++) {
        _0x1bba22 = _0x1bba22 ^ 0x661c2;
    }
}(iiIIilii, liIIIi11));
// window[liIIIi11('0')](function() {
//     var l111IlII = liIIIi11('1') + liIIIi11('2');
//     if (typeof iil == liIIIi11('3') + liIIIi11('4') || iil != l111IlII + liIIIi11('5') + l111IlII[liIIIi11('6')]) {
//         var Ilil11iI = [];
//         while (Ilil11iI[liIIIi11('6')] > -0x1) {
//             Ilil11iI[liIIIi11('7')](Ilil11iI[liIIIi11('6')] ^ 0x2);
//         }
//     }
//     iliI1lli();
// }, 0x7d0);
(function() {
    var iiIIiil = function() {}();
    var l1liii11 = function() {}();
    window[liIIIi11('9')] = function byted_acrawler() {};
    window[liIIIi11('a')] = function sign() {};
    (function() {}());
    // (function() {
    //     'use strict';
    //     var i1I1i1li = '';
    //     Object[liIIIi11('1f')](window, liIIIi11('21'), {
    //         '\x73\x65\x74': function(illllli1) {
    //             i1I1i1li = illllli1;
    //             return illllli1;
    //         },
    //         '\x67\x65\x74': function() {
    //             return i1I1i1li;
    //         }
    //     });
    // }());
    var iiil1 = 0x0;
    var l11il1l1 = '';
    var ii1Ii = 0x8;
    function i1Il11i(iiIll1i) {}
    function I1lIIlil(l11l1iIi) {}
    function lllIIiI(IIi1lIil) {}

    // 此處省略 N 個函式
    
    window[liIIIi11('37')]();
}());

function iliI1lli(lil1I1) {
    function lili11I(l11I11l1) {
        if (typeof l11I11l1 === liIIIi11('38')) {
            return function(lllI11i) {}
            [liIIIi11('39')](liIIIi11('3a'))[liIIIi11('8')](liIIIi11('3b'));
        } else {
            if (('' + l11I11l1 / l11I11l1)[liIIIi11('6')] !== 0x1 || l11I11l1 % 0x14 === 0x0) {
                (function() {
                    return !![];
                }
                [liIIIi11('39')](liIIIi11('3c') + liIIIi11('3d'))[liIIIi11('3e')](liIIIi11('3f')));
            } else {
                (function() {
                    return ![];
                }
                [liIIIi11('39')](liIIIi11('3c') + liIIIi11('3d'))[liIIIi11('8')](liIIIi11('40')));
            }
        }
        lili11I(++l11I11l1);
    }
    try {
        if (lil1I1) {
            return lili11I;
        } else {
            lili11I(0x0);
        }
    } catch (liIlI1il) {}
}
;iil = 'jsjiami.com.v6';

// function getSign(){
//     return window[liIIIi11('9')](window[liIIIi11('a')]())
// }

function getSign(){
    return window.byted_acrawler(window.sign())
}

console.log(getSign())

Python 計算關鍵代碼

# ==================================
# --*-- coding: utf-8 --*--
# @Time    : 2021-12-01
# @Author  : 微信公眾號:K哥爬蟲
# @FileName: challenge_1.py
# @Software: PyCharm
# ==================================


import execjs
import requests

challenge_api = "http://spider.wangluozhe.com/challenge/api/1"
headers = {
    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
    "Cookie": "將 cookie 值改為你自己的!",
    "Host": "spider.wangluozhe.com",
    "Origin": "http://spider.wangluozhe.com",
    "Referer": "http://spider.wangluozhe.com/challenge/1",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36",
    "X-Requested-With": "XMLHttpRequest"
}


def get_signature():
    with open('challenge_1.js', 'r', encoding='utf-8') as f:
        ppdai_js = execjs.compile(f.read())
    signature = ppdai_js.call("getSign")
    print("signature: ", signature)
    return signature


def main():
    result = 0
    for page in range(1, 101):
        data = https://www.cnblogs.com/ikdl/p/{"page": page,
            "count": 10,
            "_signature": get_signature()
        }
        response = requests.post(url=challenge_api, headers=headers, data=https://www.cnblogs.com/ikdl/p/data).json()
        for d in response["data"]:
            result += d["value"]
    print("結果為: ", result)


if __name__ == '__main__':
    main()

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

標籤:Python

上一篇:利用python爬取城市公交站點

下一篇:進群就給我發郵件,敢問這是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