gulp压缩

下载插件

    下载gulp-cli

npm install gulp-cli -g --save

    接着,确认一下版本:

$ gulp -v
CLI version: 2.3.0
Local version: 4.0.2

算了,你什么也没看见

压缩 html

    任选其一:

npm install gulp-htmlmin --save
npm install gulp-htmlclean --save

    gulp-htmlmin的配置:

.pipe(htmlmin({
    removeComments: true, // 清除 HTML 註釋
    collapseWhitespace: true, // 壓縮 HTML
    collapseBooleanAttributes: true, // 省略布爾屬性的值 <input checked="true"/> ==> <input />
    removeEmptyAttributes: true, // 刪除所有空格作屬性值 <input id="" /> ==> <input />
    removeScriptTypeAttributes: true, // 刪除 <script> 的 type="text/javascript"
    removeStyleLinkTypeAttributes: true, // 刪除 <style> 和 <link> 的 type="text/css"
    minifyJS: true, // 壓縮頁面 JS
    minifyCSS: true, // 壓縮頁面 CSS
    minifyURLs: true
}))

&nbsp;&nbsp;&nbsp;&nbsp;gulp-htmlclean的配置:

.pipe(htmlclean({
    protect: /<\!--%fooTemplate\b.*?%-->/g,
    edit: function(html) { return html.replace(/\begg(s?)\b/ig, 'omelet$1'); }
}))

压缩 css

&nbsp;&nbsp;&nbsp;&nbsp;任选其一:

npm install gulp-clean-css --save

&nbsp;&nbsp;&nbsp;&nbsp;gulp-clean-css的配置:

gulp.task('minify-css', () => {
  return gulp.src('./public/**/*.css')
    .pipe(cleanCSS())
    .pipe(gulp.dest('./public'))
})

压缩 js

&nbsp;&nbsp;&nbsp;&nbsp;任选其一:

重要:gulp-babelgulp-uglify搭配食用!

npm install gulp-tester --save
npm install gulp-babel --save && npm install gulp-uglify --save

&nbsp;&nbsp;&nbsp;&nbsp;gulp-tester的配置:

不好意思,terser配置没写,,哈哈哈哈。

.pipe(terser())

&nbsp;&nbsp;&nbsp;&nbsp;gulp-babel+gulp-uglify的配置:

.pipe(babel({presets: ['@babel/preset-env']}))
.pipe(uglify().on('error', function (e) {console.log(e)}))

压缩 image

npm install gulp-imagemin --save

&nbsp;&nbsp;&nbsp;&nbsp;gulp-imagemin的配置:

.pipe(imagemin({
      optimizationLevel: 5, // 類型:Number  預設:3 取值範圍:0-7(優化等級)
      progressive: true,    7// 類型:Boolean 預設:false 無失真壓縮jpg圖片
      interlaced: false,    // 類型:Boolean 預設:false 隔行掃描gif進行渲染
      multipass: false      // 類型:Boolean 預設:false 多次優化svg直到完全優化
    }))

gulpflie

&nbsp;&nbsp;&nbsp;&nbsp;在你的博客根目录创建一个gulpfile.js的文件,格式如下:
&nbsp;&nbsp;&nbsp;&nbsp;并且根据你自己的插件安装情况,来进行配置:
Gulpfile.js:

/* Gulp 压缩 */
// gulp-cli

const gulp = require('gulp')

/* html */
/* 如果是使用 htmlclean,请注释掉下面一行 */
const htmlmin = require('gulp-html-minifier-terser')
/* 同理 */
//const htmlclean = require('gulp-htmlclean')

/* css */
const cleanCSS = require('gulp-clean-css')

/* js */
/* gulp-tester (如果使用 gulp-terser,把下面的//去掉) */
//const terser = require('gulp-terser');
/* babel (如果使用babel,把下面兩行註釋去掉) */
/* 重要:gulp-uglify + gulp-babel */
const uglify = require('gulp-uglify')
const babel = require('gulp-babel')

/* image */
const imagemin = require('gulp-imagemin')

/* 压缩 */
// ---------------

// 壓縮 html
gulp.task('minify-html', () => {
  return gulp.src('./public/**/*.html', './public/**/**/*.html', './public/*.html')
    // 如果使用的是 htmlclean,则保留如下。
    /*.pipe(htmlclean({
        protect: /<\!--%fooTemplate\b.*?%-->/g,
        edit: function(html) { return html.replace(/\begg(s?)\b/ig, 'omelet$1'); }
      }))*/
    // 如果使用的是 htmlmin,则保留如下。
    .pipe(htmlmin({
      removeComments: true, // 清除 HTML 註釋
      collapseWhitespace: true, // 壓縮 HTML
      collapseBooleanAttributes: true,      // 省略布爾屬性的值 <input checked="true"/> ==> <input />
      removeEmptyAttributes: true,          // 刪除所有空格作屬性值 <input id="" /> ==> <input />
      removeScriptTypeAttributes: true,     // 刪除 <script> 的 type="text/javascript"
      removeStyleLinkTypeAttributes: true,  // 刪除 <style> 和 <link> 的 type="text/css"
      minifyJS: true,   // 壓縮頁面 JS
      minifyCSS: true,  // 壓縮頁面 CSS
      minifyURLs: true
    }))
    .pipe(gulp.dest('./public'))
})

/* 压缩 css */
gulp.task('minify-css', () => {
  return gulp.src('./public/**/*.css', './public/mysource/**/*.css')
    .pipe(cleanCSS())
    .pipe(gulp.dest('./public'))
})

/* 压缩 js */
// minify js - gulp-tester (如果使用 gulp-terser,把注释//去掉)
/* 重要:gulp-uglify + gulp-babel */
//gulp.task('compress', () =>
//  gulp.src(['./public/**/*.js', '!./public/**/*.min.js'])
//    .pipe(terser())
//    .pipe(gulp.dest('./public'))
//)
// minify js - babel( 如果不是使用babel,把下面註釋掉)
gulp.task('compress', () =>
  gulp.src(['./public/**/*.js', '!./public/**/*.min.js', './public/mysource/**/*.js', './public/*.js'])
    .pipe(babel({
      presets: ['@babel/preset-env']
    }))
    .pipe(uglify().on('error', function (e) {
      console.log(e)
    }))
    .pipe(gulp.dest('./public'))
)

/* 压缩 image */
// 壓縮 public/uploads 目錄內圖片
gulp.task('minify-images', async () => {
  gulp.src('./public/img/**/*.*', './public/mysource/image/*.*')
    .pipe(imagemin({
      optimizationLevel: 5, // 類型:Number  預設:3 取值範圍:0-7(優化等級)
      progressive: true,    7// 類型:Boolean 預設:false 無失真壓縮jpg圖片
      interlaced: false,    // 類型:Boolean 預設:false 隔行掃描gif進行渲染
      multipass: false      // 類型:Boolean 預設:false 多次優化svg直到完全優化
    }))
    .pipe(gulp.dest('./public/img'))
})

// 執行 gulp 命令時執行的任務
gulp.task('default', gulp.parallel(
  'compress', 'minify-css', 'minify-html', 'minify-images'
))

部署

&nbsp;&nbsp;&nbsp;&nbsp;既然配置都设置好了!接下来就是真材实料的压缩了!
&nbsp;&nbsp;&nbsp;&nbsp;在hexo g之后,hexo d之前,加一句命令行gulp即可。

hexo cl && hexo g && gulp && hexo d

关于javaScript变量 作用域链 this指针的详解

Undefined

代码:

var a = 1;

function hehe()

{

         window.alert(a);

         var a = 2;

         window.alert(a);

}

hehe();

第一个alert:

图片失效!!!

第二个alert:

图片失效!!!

原理我描述如下:

按照javascript作用域链的原理,当一个变量在当前作用域下找不到该变量的定义,会沿着作用域链往上找直到在全局作用域里查找。

按上面的代码所示,虽然函数内部重新定义了变量的值,但在定义之前使用了该变量;按照作用域链的原理会在全局作用域里找到变量定义,而实际情况却是变量未定义,这到底是怎么回事呢?

下面开始本文的主要内容,我会从基础知识一步步讲起。

Javascript的变量

变量的两种类型:基本类型和引用类型。基本类型是指:Undefined、Null、Boolean、Number和String,引用类型是指多个基本类型构成的对象。

下面我们来看看下面的代码:

var str = "sharpxiajun";
str.attr01 = "hello world";
console.log(str);//  运行结果:sharpxiajun
console.log(str.attr01);// 运行结果:undefined

运行之,发现基本类型无法添加属性,当然方法也同样不可以,例如下面的代码:

str.ftn = function(){
    console.log("str ftn");
}
str.ftn();

运行之,结果如下图所示:

img

当我们使用引用类型时候,结果就和上面完全不同了,大家请看下面的代码:

var obj1 = new Object();
obj1.name = "obj1 name";
console.log(obj1.name);// 运行结果:obj1 name

Javascript里的基本类型是存放在栈区(内存里的栈内存),存储结构如下图所示:

img

javascript里引用类型的存储需要内存的栈区和堆区(内存里的堆内存)共同完成,如下图所示:

img

场景一:如下代码所示:

var qqq;
console.log(qqq);// 运行结果:undefined

上面的代码就是命名了变量但未初始化,此时在内存里只有栈区的变量标示符而没有变量值,更没有堆区存储的对象。

场景二:如下代码所示:

var qqq;
console.log(qqq);// 运行结果:undefined
console.log(xxx);

img

提示变量未定义!在任何语言里变量未定义就使用都是违法的,但是我们javascript变量未定义也可以使用,怎么我的例子里却不能使用了?

那么我们看看下面的代码:

xxx = "outer xxx";
console.log(xxx);// 运行结果:outer xxx
function testFtn(){
     sss = "inner sss";
     console.log(sss);// 运行结果:outer sss
}
testFtn();
console.log(sss);//运行结果:outer sss
console.log(window.sss);//运行结果:outer sss

我们看看window对象的结构,如下图所示:

img

由这两个场景我们可以知道:

***javascript**变量如果没有被var定义也没有赋值操作才会**报出“xxx is not defined”,后续的代码将不能正常运行;而只有赋值操作的变量,不管在哪个作用域里赋值,都是全局变量即window对象*

下面我修改一下代码,如下所示:

//var a = 1;
function hehe()
{
     console.log(a);
     var a = 2;
     console.log(a);
}
hehe();

结果如下图所示:

img

我再改下代码:

//var a = 1;
function hehe()
{
    console.log(a);
    // var a = 2;
    console.log(a);
}
hehe();

运行之,结果如下所示:

img

对比二者代码,我们发现问题的关键是var a=2所引起的!

javascript变量是松散类型即定义时不需要指定变量类型,当变量值确定后才有类型,但没有使用var的变量必须有赋值。

**javascript代码运行前还有一个*预加载*,构造运行环境例如全局环境、函数运行环境和作用域链。环境和作用域的构造就是指定好变量属于哪个作用域,变量的定义是在预加载时完成而非在运行时。*预加载时会扫描所有代码但不运行,当扫描到被赋值操作的变量没有var定义,那么该变量就会被赋予全局变量即window对象。*

重新分析引子里的代码:在函数的局部作用域内变量a被重新定义了,在预加载时a的作用域也就不再属于全局变量而是函数作用域;赋值操作是在运行期执行的,第一次使用a变量时,a变量在局部作用域内还没被赋值,只有栈区的标示符,结果就undefined了。

*javascript两个特别的类型:undefined和null。变量的值为undefined,说明只有栈区的标示符;如果对变量进行赋值基本类型,那么栈区就有值了;如果***赋值引用类型******那么堆区会有一个对象,而栈区的值则是堆区对象的地址。变量的值是null,说明这个变量是个空对象,栈区的标示符和值都有值,堆区也有个空对象,这么说来null其实比undefined更耗内存了!

那么我们看看下面的代码:

var ooo = null;
console.log(ooo);// 运行结果:null
console.log(ooo == undefined);// 运行结果:tru
console.log(ooo == null);// 运行结果:true
 console.log(ooo === undefined);// 运行结果:false
console.log(ooo === null);// 运行结果:true

运行之,null可以和undefined相等;但使用更加精确的三等号“===”,发现还是有点不同,其实javascript里null是undefined的父类,要让一个变量是null必须使用等号“=”进行赋值!

javascript开发规范要求变量定义时马上赋值,好处就是程序很难因为变量未定义报错从而终止程序的运行,而且变量定义最好放在变量作用域的最前端。

下面我们再看一段代码:

var str;
    if (undefined != str && null != str && "" != str){
        console.log("true");
    }else{
        console.log("false");
    }
    if (undefined != str && "" != str){
        console.log("true");
    }else{
        console.log("false");
    }
    if (null != str && "" != str){
        console.log("true");
    }else{
        console.log("false");
    }
    if (!!str){
        console.log("true");
    }else{
        console.log("false");
    }
    str = "";
    if (!!str){
        console.log("true");
    }else{
        console.log("false"){
    }

运行之,结果都是打印出false。

使用双等号“==”时,undefined和null是一回事,所以第一个if语句的写法完全多余,而第二种和第三种写法是等价,究其本质前三种写法本质都是一致的;第四种写法更加完美,javascript里如果if语句的条件是undefined和null,if判断是false,所以判断代码是否为未定义和null时最好使用!运算符。

代码四里字符串被赋值空字符串时,if的判断也是false。javascript里有五种基本类型,undefined、null、boolean、Number和string,现在我们发现除了Number都可以使用!来判断if的ture和false,那么基本类型Number呢?

var num = 0;
if (!!num){
        console.log("true");
}else{
        console.log("false");
}

运行之,结果是false;如果我们把num改为负数或正数,那么运行之的结果就是true了。

所以变量初始化值如果基本类型是string就赋值空字符串,如果是number就赋值0,***if语句就可以判断变量是否初始化过***!

但是当变量是对象时候,结果却不一样了,如下代码:

var obj = {};
if (!!obj){
        console.log("true");
}else{
        console.log("false");
}

运行之,代码是true。

所以引用类型变量赋值null,if语句就可以判断变量是否初始化过!

场景三:复制变量的值和函数传递参数

首先看看这个场景的代码:

var s1 = "sharpxiajun";
var s2 = s1;
console.log(s1); 运行结果:sharpxiajun
console.log(s2); 运行结果:sharpxiajun
s2 = "xtq";
console.log(s1); 运行结果:sharpxiajun
console.log(s2); 运行结果:xtq

上面是基本类型变量的复制,我们再看看下面的代码:

var obj1 = new Object();
obj1.name = "obj1 name";
console.log(obj1.name);// 运行结果:obj1 name
var obj2 = obj1;
console.log(obj2.name);// 运行结果:obj1 name
obj1.name = "sharpxiajun";
console.log(obj2.name);// 运行结果:sharpxiajun

当复制的是对象,obj1和obj2两个对象被关联起来了,obj1的属性改变时,obj2的属性也改变。

我们看看下面的代码:

function testFtn(sNm,pObj){
        console.log(sNm);// 运行结果:new Name
        console.log(pObj.oName);// 运行结果:new obj
        sNm = "change name";
        pObj.oName = "change obj";
}
var sNm = "new Name";
var pObj = {oName:"new obj"};
testFtn(sNm,pObj);
console.log(sNm);// 运行结果:new Name
console.log(pObj.oName);// 运行结果:change obj

这个结果和变量复制的结果是一致的。

函数传参的本质就是外部的变量复制到函数参数的变量里,是按值传递的

如下两张图所示:

img

img

在javascript里变量的存储有三个位置:

位置一:栈区变量标示符;

位置二:栈区变量值(栈区对象地址);

位置三:堆区对象。

**javascript的变量复制(函数传参也是)本质是传栈区变量值。基本类型的值在**栈区变量值里,复制时**两个变量是独立的;复制引用类型时,*栈区变量值*是堆区对象的地址,因此其中一个改变,另一个也改变。

原理讲完了,下面我列举一个拔高的例子,代码如下:

var ftn1 = function(){
        console.log("test:ftn1");
    };
var ftn2 = function(){
        console.log("test:ftn2");
};
function ftn(f){
       f();
       f = ftn2;
}
ftn(ftn1);// 运行结果:test:ftn1
console.log("====================华丽的分割线======================");
ftn1();// 运行结果:test:ftn1

这个代码以前是这么分析的:f是函数的参数,属于函数的局部作用域,更改f的值,是没法改变ftn1的值,因为到了外部作用域f就失效了。但是这种解释很难说明我上文里给出的函数传参的实例。其实这个问题应该这么分析:在javascript里函数也是对象,局部作用域里f = ftn2操作是将f在栈区变量地址改为ftn2的地址,对ftn1和ftn2没有影响。

***记住:javascript变量复制和函数传参是在传递栈区变量值,*当栈区变量值为undefined、null、“”(空字符串)、0、false时,if判断是为false,我们可以通过!运算符计算。

当我们的代码如下:

var obj = {};
    if (!!obj){
        console.log("true");
    }else{
        console.log("false");
}

结果则是true,其中var obj = {}相当于var obj = new Object()。虽然对象里没什么内容,但是在堆区里对象的内存已经分配,栈区变量值已经是内存地址了,所以if判断是true。

3) 作用域链相关的问题

了解作用域链前先了解作用域:

作用域在许多程序设计语言中非常重要。 通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。 作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名字冲突。

在java里通过{}来设置作用域,在{}里面的变量会得到保护,这种保护就是不让{}里的变量被外部变量混淆。那么{}的方式适合于javascript吗?

我们看看下面的例子:

var s1 = "sharpxiajun";
    function ftn(){
        var s2 = "xtq";
        console.log(this);// 运行结果: windows
        console.log("s1:" + this.s1 + ";s2:" + this.s2);//运行结果:s1:sharpxiajun;s2:undefined
        console.log("s1:" + this.s1 + ";s2:" + s2);// 运行结果:s1:sharpxiajun;s2:xtq
    }
    ftn();

javascript最大的作用域是window,加载时自动构造。上面代码里的大括号是函数定义,函数作用域内定义的s2变量不能被window对象访问的。可以说s2变量是被{}保护起来了,它的生命周期和这个函数的生命周期有关。

但是在javascript语言里还有非函数的{},我们再看看下面的例子:

if (true){
        var a = "aaaa";
    }
    console.log(a);// 运行结果:aaaa

我们发现javascript里{}有时是起不到定义作用域的功能。这也说明javascript里的作用域定义是和其他语言例如java不同的。

javascript作用域execution context,翻译成执行上下文或执行环境。我们来想想javascript里哪些情况是执行的:

情况一:页面加载时script标签下的代码会顺序执行,而这些能被执行的代码都是属于window的变量或函数;

情况二:函数的名字后面加上小括号(),例如ftn(),执行的是函数。

如此说来,javascript里的执行环境有两类一类是全局执行环境,即window代表的全局环境;一类是函数代表的函数执行环境,这也就是局部作用域。

执行环境有个对象叫做variable object,翻译为变量对象或上下文变量,存储其执行环境里所有的变量和函数,全局执行环境的上下文变量就是window。

javascript执行环境栈execution context stack:每个要被执行的函数会先把函数的执行环境压入到执行环境栈里,执行完后被弹出,控制权交给全局环境;如果函数后面还有代码,代码接着执行;如果函数里嵌套了函数,那么嵌套函数执行完毕后,执行环境栈的控制权就交由了外部函数,然后依次类推,最后就是全局执行环境了。

函数的执行环境被压入到执行环境栈里后,函数执行的第一步不是函数里的第一行代码而是在上下文变量里构造一个作用域链scope chain,保证执行环境里有权访问的变量和函数是有序的。

我们看看下面的代码:

var b1 = "b1";
    function ftn1(){
        var b2 = "b2";
        var b1 = "bbb";
        function ftn2(){
            var b3 = "b3";
            b2 = b1;
            b1 = b3;
            console.log("b1:" + b1 + ";b2:" + b2 + ";b3:" + b3);// 运行结果:b1:b3;b2:bbb;b3:b3
        }
        ftn2();
    }
    ftn1();
console.log(b1);// 运行结果:b1

ftn2函数可以访问变量b1,b2,这个体现了有权访问;当ftn1作用域里改变了b1的值并且把b1变量重新定义为ftn1的局部变量,那么ftn2访问到的b1就是ftn1的,ftn2访问到b1后就不会在全局作用域里查找b1了,这个体现了有序性。

**总结:**作用域指的是执行环境execution context,通过上下文变量variable object体现。**当函数的执行环境压入到了执行环境栈**execution context stack**时上下文变量会构造一个对象作用域链scope chain保证执行环境里有权访问的变量和函数是有序的。作用域链只能向上访问**变量**,直到window,*不允许*向下访问。

4) this、new、apply和call详解

*this***对象****:上下文变量构建作用域链时还会构造一个this对象,*是当前执行环境外部上下文变量的一份拷贝,*不包含作用域链变量。

例如代码:

var b1 = "b1";
    function ftn1(){
        console.log(this);// 运行结果: window
        var b2 = "b2";
        var b1 = "bbb";
        function ftn2(){
            console.log(this);// 运行结果: window
            var b3 = "b3";
            b2 = b1;
            b1 = b3;
            console.log("b1:" + b1 + ";b2:" + b2 + ";b3:" + b3);// 运行结果:b1:b3;b2:bbb;b3:b3
        }
        ftn2();
    }
    ftn1();

函数ftn1和ftn2里的this指针都是指向window,这是为什么了?因为通过function xxx(){}形式定义函数,这个函数不管在哪定义都属于window。

但是我们知道很多this指针都不是指向window,例如下面的代码:

var obj = {
    name:"sharpxiajun",
    ftn:function(){
        console.log(this);// 运行结果: Object { name="sharpxiajun", ftn=function()}
        console.log(this.name);//运行结果: sharpxiajun
    }
}
obj.ftn();// :

这里this指针指向了Object,我前文不是说javascript里作用域只有两种:一个是全局的一个是函数,为什么这里Object也是可以制造出作用域?

那我们看看下面的代码:

var obj1 = new Object();
obj1.name = "xtq";
obj1.ftn = function(){
    console.log(this);// 运行结果: Object { name="xtq", ftn=function()}
    console.log(this.name);//运行结果: xtq
}
obj1.ftn();

这两种写法是等价的,第一种叫做字面量定义,而第二种是标准写法。Object对象的本质也是个function,调用对象里的函数时,函数的外部执行环境就是obj1本身,this指针也是指向了obj1。

下面我们看看在java语言里是如何使用this指针的,代码如下:

public class Person {
    private String name;
    private String sex;
    private int age;
    private String job;
    public Person(String name, String sex, int age, String job) {
        super();
        this.name = name;
        this.sex = sex;
        this.age = age;
        this.job = job;
    }
    private void showPerson(){
        System.out.println("姓名:" + this.name);
        System.out.println("性别:" + this.sex);
        System.out.println("年龄:" + this.age);
        System.out.println("工作:" + this.job);
    }
    public void printInfo(){
        this.showPerson();
    }
    public static void main(String[] args) {
        Person person = new Person("马云", "男", 46, "董事长");
        person.printInfo();
    }
}
//姓名:马云
//性别:男
//年龄:46
//工作:董事长

上面的代码执行后没有任何问题,下面我修改下这个代码,加一个静态的方法,静态方法里使用this指针调用类里的属性,如下图所示:

img

我们发现IDE会报出语法错误“Cannot use this in a static context”,this指针在java语言里是不能在静态方法里使用的。

在面向对象编程里有两个重要的概念:一个是类,一个是实例化的对象。类是一个抽象的概念,类就像一个模具,而实例化对象就是通过这个模具制造出来的产品。

有上面代码我们可以看到,this指针在java语言里只能在实例化对象里使用,this指针等于这个被实例化好的对象,而this后面加上点操作符后面的东西就是this所拥有的东西,例如:姓名,工作,手,脚等等。

javascript的this指针也只能在实例化对象里使用,但是javascript的this指针却比java难以理解的多,原因得有三:

原因一:javascript是一个函数编程语言,也是面向对象的语言;javascript的函数是高阶函数,可以作为对象传递,还可以作为构造函数,创建实例化对象,结果导致方法执行时候this指针的指向会不断发生变化。

原因二:由上面java的例子我们看到,this指针只有在使用new操作符后才会生效;但是javascript里的this在没有进行new操作也会生效,这时候this往往会指向全局对象window。

原因三:javascript里call和apply操作符可以改变this指针。

我们先看看下面的代码:

<script type="text/javascript">
    this.a = "aaa";
    console.log(a);//aaa
    console.log(this.a);//aaa
    console.log(window.a);//aaa
    console.log(this);// window
    console.log(window);// window
    console.log(this == window);// true
    console.log(this === window);// true
</script>

script标签里直接使用this指针就指向window,window在页面加载时由javascript引擎完成。程序员无法通过编程语言来控制这个实例化过程,所以开发时就没有构建这个this指针的感觉,常常会忽视它。

还和function的使用有关,我们看看下面的代码:

<script type="text/javascript">
    function ftn01(){
       console.log("I am ftn01!");
    }
    var ftn02 = function(){
        console.log("I am ftn02!");
    }
</script>

上面是两种定义函数的方式,第一种称作声明函数,第二种称作函数表达式,这两种方式的区别常常会让我们混淆this指针的使用。

我们再看看下面的代码:

<script type="text/javascript">
    console.log(ftn01);//ftn01()  注意:在firebug下这个打印结果是可以点击,点击后会显示函数的定义
    console.log(ftn02);// undefined
    function ftn01(){
       console.log("I am ftn01!");
    }
    var ftn02 = function(){
        console.log("I am ftn02!");
    }
</script>

这又是一段没有按顺序执行的代码,ftn02打印结果是undefined,有栈区变量标识符,没有栈区变量值,堆区没有对象,这是javascript引擎在预处理时扫描变量定义所致。ftn01打印出完整的函数定义,这只能说明一个问题:

***声明函数会在预处理时把函数定义和赋值操作都完成,且声明函数都是window对象的属性*

关于函数表达式的写法还有秘密可以探寻,我们看下面的代码:

<script type="text/javascript">
    function ftn03(){
        var ftn04 = function(){
            console.log(this);// window
        };
        ftn04();
    }
    ftn03();
</script>

ftn04虽然在ftn03作用域下,但是它里面的this指针指向window。说明函数表达式在函数内部写时,this指针指向window。

原因是javascript里任何匿名函数都是属于window,在全局作用域构造时完成定义和赋值。匿名函数是没有名字的变量,定义时会返回内存地址;如果有个变量接收了这个地址,那么匿名函数就能使用了;匿名函数是在全局执行环境构造时定义和赋值,所以匿名函数内的this指向window对象。

这下子坏了,this都指向window,那我们到底怎么才能改变它了?

在本文开头我说出了this的秘密,this都是指向实例化对象,前面讲到那么多情况this都指向window,就是因为这些时候只做了一次实例化操作,实例化window对象,所以this都是指向window。我们要把this从window变成别的对象,就得要让function被实例化,那如何让javascript的function实例化呢?

答案就是使用new操作符。我们看看下面的代码:

<script type="text/javascript">
    var obj = {
        name:"sharpxiajun",
        job:"Software",
        show:function(){
            console.log("Name:" + this.name + ";Job:" + this.job);
            console.log(this);// Object { name="sharpxiajun", job="Software", show=function()}
        }
    };
    var otherObj = new Object();
    otherObj.name = "xtq";
    otherObj.job = "good";
    otherObj.show = function(){
        console.log("Name:" + this.name + ";Job:" + this.job);
        console.log(this);// Object { name="xtq", job="good", show=function()}
    };
    obj.show();//Name:sharpxiajun;Job:Software
    otherObj.show();//Name:xtq;Job:good
</script>

这是我上篇讲到的关于this指向Object的实例。Javascript里通过字面量方式定义对象的方式是new Object的简写,二者是等价的,本质也是new操作符。

下面我使用javascript来重写本篇开头用java定义的类,代码如下:

<script type="text/javascript">
    function Person(name,sex,age,job){
        this.name = name;
        this.sex = sex;
        this.age = age;
        this.job = job;
        this.showPerson = function(){
            console.log("姓名:" + this.name);
            console.log("性别:" + this.sex);
            console.log("年龄:" + this.age);
            console.log("工作:" + this.job);
            console.log(this);// Person { name="马云", sex="男", age=46, 更多...}
        }
    }
    var person = new Person("马云", "男", 46, "董事长");
    person.showPerson();
</script>

function Person相当于在定义一个类,javascript的function既是函数又是对象;javascript的构造函数可以理解为类和构造函数合二为一,当然javascript语言规范里是没有类的概念。

new操作符会让构造函数产生如下变化:

1. 创建一个新对象;

2. 将构造函数的作用域赋给新对象(因此this就指向了这个新对象);

3. 执行构造函数中的代码(为这个新对象添加属性);

4. 返回新对象

第四点要着重讲下,记住构造函数被new操作,要让new正常作用最好不要在构造函数里写return,没有return的构造函数都是按上面四点执行,有了return情况就复杂了,这个知识我会在讲prototype时候讲到。

Javascript改变this指针还有call和apply,它们的作用相同,只是参数不同。call和apply的第一个参数一样,apply第二个参数是数组,call从第二个参数开始后面有许多参数。

我们看看下面的码:

<script type="text/javascript">
    var name = "sharpxiajun";
    function ftn(name){
        console.log(name);
        console.log(this.name);
        console.log(this);
    }
    ftn("101");
    var obj = {
      name:"xtq"
    };
    ftn.call(obj,"102");
    /*
    * 结果如下所示:
    *101
     T002.html (第 73 行)
     sharpxiajun
     T002.html (第 74 行)
     Window T002.html
     T002.html (第 75 行)
     T002.html (第 73 行)
     xtq
     T002.html (第 74 行)
     Object { name="xtq"}
    * */
</script>

其实理清上面情况也是有迹可循的,就以定义对象里的方法有传入函数参数为例:

情形一:传入的参数是函数的别名,那么函数的this就是指向window;

情形二:传入的参数是被new过的构造函数,那么this就是指向实例化的对象本身;

情形三:如果想把被传入的函数对象里this的指针指向外部字面量定义的对象,那么我们就是用apply和call

我们可以通过代码看出我的结论,代码如下:

<script type="text/javascript">
var name = "I am window";
var obj = {
    name:"sharpxiajun",
    job:"Software",
    ftn01:function(obj){
        obj.show();
    },
    ftn02:function(ftn){
        ftn();
    },
    ftn03:function(ftn){
        ftn.call(this)
    }
};
function Person(name){
    this.name = name;
    this.show = function(){
        console.log("姓名:" + this.name);
        console.log(this);
    }
}
var p = new Person("Person");
obj.ftn01(p);
obj.ftn02(function(){
   console.log(this.name);
   console.log(this);
});
obj.ftn03(function(){
    console.log(this.name);
    console.log(this);
});
</script>

JS逆向的简单分析

基于requests\hashlib模块实现md5加密方式的有道翻译接口破解
第一步打开控制台查询任意一个单词进行抓包,获取请求方式和查看所需的查询参数(salt,ts,sign)
第二部,根据所需查询参数在控制台中搜索参数所在的 js文件的位置,打断点调试,查看各个参数在js文件中对应函数的实现过程;经调试发现ts是一个时间戳,salt是0-9和时间戳拼接成的一个字符串,sign是所查询单词和salt以及两个固定字符串的一个拼接.
第三步,用python实现Form Data中查询参数的动态生成.
第四步,发请求,传入第三步实现的参数,获取相应结果

阅读更多

教会你写百分之九十的shell脚本!

本文章主要内容来自菜鸟教程 , 也添加了一些知识点

shell脚本?

在说什么是shell脚本之前,先说说什么是shell。
shell是外壳的意思,就是操作系统的外壳。我们可以通过shell命令来操作和控制操作系统,比如Linux中的Shell命令就包括ls、cd、pwd等等。总结来说,Shell是一个命令解释器,它通过接受用户输入的Shell命令来启动、暂停、停止程序的运行或对计算机进行控制。
shell 是一个应用程序,它连接了用户和 Linux 内核,让用户能够更加高效、安全、低成本地使用 Linux 内核,这就是 Shell 的本质。
shell 本身并不是内核的一部分,它只是站在内核的基础上编写的一个应用程序。
那么什么是shell脚本呢?
shell脚本就是由Shell命令组成的执行文件,将一些命令整合到一个文件中,进行处理业务逻辑,脚本不用编译即可运行。它通过解释器解释运行,所以速度相对来说比较慢。
shell脚本中最重要的就是对shell命令的使用与组合,再使用shell脚本支持的一些语言特性,完成想要的功能。

阅读更多

使用 ssh Key

什么是ssh

&nbsp;&nbsp;&nbsp;&nbsp;SSH为Secure Shell的缩写,由IETF的网络小组(Network Working Group)所制定;SSH 为建立在应用层基础上的安全协议。
&nbsp;&nbsp;&nbsp;&nbsp;SSH 是较可靠,专为远程登录会话和其他网络服务提供安全性的协议。利用 SSH 协议可以有效防止远程管理过程中的信息泄露问题。SSH最初是UNIX系统上的一个程序,后来又迅速扩展到其他操作平台。
&nbsp;&nbsp;&nbsp;&nbsp;SSH在正确使用时可弥补网络中的漏洞。SSH客户端适用于多种平台。几乎所有UNIX平台—包括HP-UX、Linux、AIX、Solaris、Digital UNIX、Irix,以及其他平台,都可运行SSH。

阅读更多

基于butterfly标签页头部透明

前言

这玩意只是适用于butterfly,具体效果看本文头图,没错没了。为什么我要写这玩意呢?因为群里某个叫天的人一直在那bb说要改Butterfly的头图,他一个劲的问,我一个劲的回答,然后我发现,朽木不可雕也!废话不多说,直接进入教程。

阅读更多

turtle

写在前面

其实我也不知道为什么我会写这个,本文涉及信号与传递,Python

阅读更多

爬虫被403怎么办

403

先来给不知道的同学们科普一下错误码,错误码有很多啊,比如404(最常见的),那么为啥这会返还一个我们不知道是什么的码呢?
我们随便编辑一个Python爬虫程序

import requests

url = 'https://cn.chen-shang.top' # 举个栗子
a = requests.get(url)
print(a)

拿我的博客网址举个例子,我们发现终端的输出为

Response <200>

看不懂?没关系,有一个规律,2打头的都是请求成功。200的看了,来看看404

import requests

url = 'https://ccknbc.cc/405' # 菜狗的网址
a = requests.get(url)
print(a)

输出结果是什么

Response <404>

也是一个道理,4打头都是请求失败,不搞5打头了,一个4打头找了我老半天。

现在,重点分析403是个什么东西

403这玩意4打头看得出来是报错了,为啥报错呢?权限不够,403错误是一种在网站访问过程中,常见的错误提示,表示资源不可用。服务器理解客户的请求,但拒绝处理它,通常由于服务器上文件或目录的权限设置导致的WEB访问错误。
现在很多网站的api接口返回httpcode返回码是403提示禁止访问。如果您也遇到这样的情况,请先不要急着去修改网站相关的参数
第一、先进api的网站。用浏览器访问,如果浏览器访问该api接口能成功。说明是设置了权限的问题。如果不能成功的话。很可以是该接口已修改或已失效,这时候就没办法访问了。
第二、如果浏览器能访问成功的话。那就好办了。调用该接口时,捕获异常中的responseBody,很有可能数据就在这里面,笔者就遇到的是这种问题。

也有可能是服务器看你不顺眼,把你挡在门外了,虽然我很喜欢用request,但是为了快,这次用 urllib3

import urllib.request as request
src=!["https://www.ptt.cc/bbs/movie/index.html"]
with request.urlopen(src) as response:
    data=response.read().decode('utf-8')
print(data)

在这种情况下直接对网路进行连接,一般情况下会被服务器拒绝出现这样的报错
“”“urllib.error.HTTPError: HTTP Error 403: Forbidden”“”

这种情况是网站认为你不是普通用户操作,所以我们需要模拟普通用户。
一般用户使用会给网站发送一个这总系统和浏览器的信息,所以我们需要模拟。也要向网站发送一个这样的信息。
遇到这样子的,主要是爬虫被发现了,建议用Python的正则表达式,放出我之前写的一个残次品,虽然没什么用,但是我就哪一个写了正则表达式

# coding = utf -8
import requests
import re
# url
url = 'https://www.tukuppt.com/yinxiao/'
# The detection of IP
headers = {
     'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36 FS'
}
# data
a = requests.get(url)
a = """<a class="title" target="_blank" href=“.*?”>(.*?)</a>"""
b = '<source src="(.*?)" type="audio/mpeg">'
urls = re.findall(a, b.text)
print(urls)

for url, name in zip(urls, name):
     music = requests.get('http'+url,headers)
     with open('data.txt', 'w') as f:
          f.write(music.content)
     print('<%s OK>' % name)

上面那个是我爬熊猫办公的。其实还可以更简单,就是找到你浏览器的User-Agent,像我的就是

'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36 FS'

加入,如下是urllib3爬ptt的

#抓取PTT电影版的网页原始码(HTML)
import urllib.request as req
url="https://www.ptt.cc/bbs/movie/index.html"
#建立一个Request物件,附加Request Headers 的资讯  
request=req.Request(url,headers={
    "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36"
})
with req.urlopen(request) as response:
    data=response.read().decode("utf-8")
#资料解析
import bs4
root=bs4.BeautifulSoup(data,"html.parser")
titles=root.find_all("div",class_="title")
print(titles)

JS逆向反调试和反反调试

写在最前面

本文转载于CSDN大佬: @花爷
已获得其本人允许
根据《中华人民共和国著作权法》规定

1、已在报刊上刊登或者网络上传播的作品,除著作权人声明或者上载该作品的网络服务提供者受著作权人的委托声明不得转载、摘编的以外,网站予以转载、摘编,并按有关规定支付报酬、注明出处的,不构成侵权。

2、为个人学习、研究或者欣赏,使用他人已经发表的作品而转载的不属于侵权,可以不经著作权人许可,不向其支付报酬,但应当指明作者姓名、作品名称,并且不得侵犯著作权人依照本法享有的其他权利。

切入JS逆向反调试

现在调试JS各种反调,既然有反调,那我们就肯定有过这个反调试的方法。这里给大家推荐一个JS逆向练习平台。

JS逆向练习平台

1.我们这里讲的是第5题,它这里的反调试是不让你打开开发者人员工具。打开开发者人员工具,就自动返回主页面。

2.思路:a:它不让我打开开发者人员工具,肯定是在打开开发者人员工具之后它检测了什么玩意

b:.那他就是调用了浏览器内置的东西,我这个就解决方法是下页面事件监听断点,如图:

这里的意思:
事件侦听器断点 event listener breakpoints
事件突变 Dom mutation
下面还有一些我就不翻译了,有兴趣可以去看看,翻译一下,小弟英语不好。
下这个断点之后,跳转完页面,加载ok之后他就会断下来,如图,我们就可以得到这道题的答案了。

答案大佬打了一下马赛克,这边大佬不公布答案,大家可以尝试一下。

切入JS反反调试

你千万别跟任何人谈任何事情。你只要一谈起,就会想念起每一个人来,我只知道我很想念我所谈到的每一个人。——J·D·塞林格《麦田里的守望者》

[JS逆向]过无限debugger调试

在JS逆向过程当中,获取用发F12抓取XHR的时候,常常会发现网页不让我们打开F12开发人员工具。如果这个时候我们开启工具中禁止断点之后,虽然我们可以抓取xhr,但是这样我们无法调试代码部分了,开启了禁止断点之后,我们自己也无法在代码当中下断点了

需求

在采集某些网站时,目标网站为了防止别人分析调试前端代码,采取了反调试措施。其中一种做法是当你按F12进入浏览器控制台后,浏览器会自动命中debugger断点,并且无限循环,导致无法调试。以食品药品监督管理总局数据查询网站为例。如下图:

按F12进入控制台

解决方法

禁用浏览器断点


点击图中按钮,之后将不会再命中任何断点。这种方法虽然可以防止无限循环命中debugger断点,但是也存在很大的缺陷,因为对于其他代码,我们还是需要断点调试功能的。所以这个方法仅限于静态分析。

利用中间人修改响应代码

用Fiddler删除响应代码中的debugger即可达到目的
实现的核心代码很简单:如下

FiddlerApplication.BeforeRequest += delegate(Fiddler,Session oS)
{
     oS.bBufferResponse = true;
}

FiddlerApplication.BeforeResponse += (oS) =>
{
     oS.utilDecodeResponse();
     oS.utilReplaceLnResponse("debugger", String.Empty);
     /*
     *
     Code 实在是找不到FiddlerCode的代码格式,只能用Python代替一下
     *
     */
}

实际使用中发现,位于响应html页的debugger被删除了,但是仍然会弹出断点。分析页面得到,debugger断点位置一共有2处 第一处位于”http://qy1.sfda.gov.cn/datasearch/face3/dir.html"debugger以明文形式存在,Fiddler删除的就是这部分。通过分析另一处debugger位置,发现debugger是通过eval去实现的,响应中并没有直接出现debugger字段,所以没有被替换掉。

在Console输入

> _$uj()
<· "eval"
> _$dQ()
<· "(function() {var a = new Date(); debugger; return new Date() - a > 100;}())"
> _

代码经过强混淆,读者看到的函数名称是和我不一样的。

利用浏览器插件修改响应代码

具体原理和使用Fiddler是相同的,通过浏览器插件将请求重定向以达到修改代码的目的。也存在相同的问题

手动替换代码

既然修改响应结果无法满足需求,那只能从代码中寻找突破了。
以本文的网站为例,查看debugger断点处的调用栈堆,找到调用位置。其实在上文中间人方式结尾处已经发现了。是通过eval去实现断点的。我们先构造一个空方法


将目标网站的方法偷梁换柱

由于网站代码强混淆,所以函数名称会不一样。下面放个GIF图

完美解决 但是注意不要刷新,页面刷新后需要重新替换。

傻瓜式技巧

上文的几种方法,要么是存在缺陷,要么是步骤较为繁琐,我这边还有个压箱底的神技,不需要写任何代码,鼠标点点点就能够满足需求,为了避免伸手党,所以不放了。

总结

1-DebugPort
2-KdDisableDebugger
3-IsDebuggerPresent和CheckRemoteDebuggerPresent
4-hook

http://www.moguizuofang.com/bbs/thread-3235-1-1.html

http://bbs.pediy.com/showthread.php?t=126802

http://bbs.pediy.com/showthread.php?t=129810

DebugPort是进程EPROCESS结构里的一个成员,指向了一个用于进程调试的对象,如果一个进程不在被调试的时候那么就是NULL,否则他是一个指针。该对象负责在调试器与被调进程之间进行调试事件传递,因此被称为调试端口。被调试程序的事件由这个端口发送到调试器进程的。

HOOK系统中一些与调试相关的函数,也可以防止被各种调试器调试。比如某款程序在内核中就HOOK了下面这些函数:
NtOpenThread():防止调试器在程序内部创建线程
NtOpenProcess():防止OD(OllyDbg)等调试工具在进程列表中看到
KiAttachProcess():防止被附加上
NtReadVirtualMemory():防止被读内存
NtWriteVirtualMemory():防止内存被写
KdReceivePacket():KDCOME.dll 中Com串口接收数据函数
KdSendPacket():KDCOME.dll 中Com串口发送数据函数,可以HOOK这2个函数用来防止双机调试。

反反调试的思路也就出来了。针对清零DebugPort来防止调试的方法,可以通过对DebugPort内存地址下内存断点:
ba w4 debugport_addr
这样一旦有程序代码在修改DebugPort,就会被断下,从而找到对应的清零DebugPort的反调试代码,然后对这部分代码进行patch(用机器码0×90(nop)或者0xC3(ret)取代),从而让它失去作用,当然有的程序会对代码进行校验,一旦发现代码被篡改,就会采取保护措施,比如抛出异常或者退出程序。
针对调用系统函数如KdDisableDebugger()来检测调试器存在从而禁止被调试的方法,可以在对应的这些函数的地址下断点,然后对相关的代码进行patch,然后使该函数判断失效。比如:
bp KdDisableDebugger、eb xxx
针对通过HOOK系统函数来防止进程被调试的方法,可以直接将这些系统函数的钩子直接恢复,可以通过内核驱动程序或者借助一些ARK工具(比如Pchunter)就可以直接检测和恢复这些函数钩子。

总结 -2

在调试一些病毒程序的时候,可能会碰到一些反调试技术,也就是说,被调试的程序可以检测到自己是否被调试器附加了,如果探知自己正在被调试,肯定是有人试图反汇编啦之类的方法破解自己。为了了解如何破解反调试技术,首先我们来看看反调试技术。

一、Windows API方法

Win32提供了两个API, IsDebuggerPresent和CheckRemoteDebuggerPresent可以用来检测当前进程是否正在被调试,以IsDebuggerPresent函数为例,例子如下:

BOOL ret = IsDebuggerPresent();
printf("ret = %d\n", ret);

破解方法很简单,就是在系统里将这两个函数hook掉,让这两个函数一直返回false就可以了,网上有很多做hook API工作的工具,也有很多工具源代码是开放的,所以这里就不细谈了。

二、查询进程PEB的BeingDebugged标志位

当进程被调试器所附加的时候,操作系统会自动设置这个标志位,因此在程序里定期查询这个标志位就可以了,例子如下:

bool PebIsDebuggedApproach()
{
       char result = 0;
       __asm
       {
// 进程的PEB地址放在fs这个寄存器位置上
              mov eax, fs:[30h]
// 查询BeingDebugged标志位
              mov al, BYTE PTR [eax + 2]
              mov result, al
       }

       return result != 0;
}

三、查询进程PEB的NtGlobal标志位

跟第二个方法一样,当进程被调试的时候,操作系统除了修改BeingDebugged这个标志位以外,还会修改其他几个地方,其中NtDll中一些控制堆(Heap)操作的函数的标志位就会被修改,因此也可以查询这个标志位,例子如下:

bool PebNtGlobalFlagsApproach()
{
       int result = 0;

       __asm
       {
  // 进程的PEB
              mov eax, fs:[30h]
  // 控制堆操作函数的工作方式的标志位
              mov eax, [eax + 68h]
  // 操作系统会加上这些标志位FLG_HEAP_ENABLE_TAIL_CHECK,
  // FLG_HEAP_ENABLE_FREE_CHECK and FLG_HEAP_VALIDATE_PARAMETERS,
  // 它们的并集就是x70
  //
  // 下面的代码相当于C/C++的
  //     eax = eax & 0x70
              and eax, 0x70
              mov result, eax
       }

       return result != 0;
}

四、查询进程堆的一些标志位

这个方法是第三个方法的变种,只要进程被调试,进程在堆上分配的内存,在分配的堆的头信息里,ForceFlags这个标志位会被修改,因此可以通过判断这个标志位的方式来反调试。因为进程可以有很多的堆,因此只要检查任意一个堆的头信息就可以了,所以这个方法貌似很强大,例子如下:

bool HeapFlagsApproach()
{
       int result = 0;

       __asm
       {
      // 进程的PEB
              mov eax, fs:[30h]
      // 进程的堆,我们随便访问了一个堆,下面是默认的堆
              mov eax, [eax + 18h]
  // 检查ForceFlag标志位,在没有被调试的情况下应该是
              mov eax, [eax + 10h]
              mov result, eax
       }

       return result != 0;
}

五、使用NtQueryInformationProcess函数

NtQueryInformationProcess函数是一个未公开的API,它的第二个参数可以用来查询进程的调试端口。如果进程被调试,那么返回的端口值会是-1,否则就是其他的值。由于这个函数是一个未公开的函数,因此需要使用LoadLibrary和GetProceAddress的方法获取调用地址,示例代码如下:

// 声明一个函数指针。
typedef NTSTATUS (WINAPI *NtQueryInformationProcessPtr)(
       HANDLE processHandle,
       PROCESSINFOCLASS processInformationClass,
       PVOID processInformation,
       ULONG processInformationLength,
       PULONG returnLength);

bool NtQueryInformationProcessApproach()
{
       int debugPort = 0;
       HMODULE hModule = LoadLibrary(TEXT("Ntdll.dll "));
       NtQueryInformationProcessPtr NtQueryInformationProcess = (NtQueryInformationProcessPtr)GetProcAddress(hModule, "NtQueryInformationProcess");
       if ( NtQueryInformationProcess(GetCurrentProcess(), (PROCESSINFOCLASS)7, &debugPort, sizeof(debugPort), NULL) )
              printf("[ERROR NtQueryInformationProcessApproach] NtQueryInformationProcess failed\n");
       else
              return debugPort == -1;

       return false;
}

六、NtSetInformationThread方法

这个也是使用Windows的一个未公开函数的方法,你可以在当前线程里调用NtSetInformationThread,调用这个函数时,如果在第二个参数里指定0x11这个值(意思是ThreadHideFromDebugger),等于告诉操作系统,将所有附加的调试器统统取消掉。示例代码:

// 声明一个函数指针。
typedef NTSTATUS (*NtSetInformationThreadPtr)(HANDLE threadHandle,
       THREADINFOCLASS threadInformationClass,
       PVOID threadInformation,
       ULONG threadInformationLength);

void NtSetInformationThreadApproach()
{
      HMODULE hModule = LoadLibrary(TEXT("ntdll.dll"));
      NtSetInformationThreadPtr NtSetInformationThread = (NtSetInformationThreadPtr)GetProcAddress(hModule, "NtSetInformationThread");

      NtSetInformationThread(GetCurrentThread(), (THREADINFOCLASS)0x11, 0, 0);
}

七、触发异常的方法

这个技术的原理是,首先,进程使用SetUnhandledExceptionFilter函数注册一个未处理异常处理函数A,如果进程没有被调试的话,那么触发一个未处理异常,会导致操作系统将控制权交给先前注册的函数A;而如果进程被调试的话,那么这个未处理异常会被调试器捕捉,这样我们的函数A就没有机会运行了。

这里有一个技巧,就是触发未处理异常的时候,如果跳转回原来代码继续执行,而不是让操作系统关闭进程。方案是在函数A里修改eip的值,因为在函数A的参数_EXCEPTION_POINTERS里,会保存当时触发异常的指令地址,所以在函数A里根据这个指令地址修改寄存器eip的值就可以了,示例代码如下:

// 进程要注册的未处理异常处理程序A
LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *pei)
{
       SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)
              pei->ContextRecord->Eax);
       // 修改寄存器eip的值
       pei->ContextRecord->Eip += 2;
       // 告诉操作系统,继续执行进程剩余的指令(指令保存在eip里),而不是关闭进程
       return EXCEPTION_CONTINUE_EXECUTION;
}

bool UnhandledExceptionFilterApproach()
{
       SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
       __asm
       {
              // 将eax清零
              xor eax, eax
              // 触发一个除零异常
              div eax
       }

       return false;
}

八、调用DeleteFiber函数

如果给DeleteFiber函数传递一个无效的参数的话,DeleteFiber函数除了会抛出一个异常以外,还是将进程的LastError值设置为具体出错原因的代号。然而,如果进程正在被调试的话,这个LastError值会被修改,因此如果调试器绕过了第七步里讲的反调试技术的话,我们还可以通过验证LastError值是不是被修改过来检测调试器的存在,示例代码:

bool DeleteFiberApproach()
{
       char fib[1024] = {0};
       // 会抛出一个异常并被调试器捕获
       DeleteFiber(fib);

       // 0x57的意思是ERROR_INVALID_PARAMETER
       return (GetLastError() != 0x57);
}

从零维到十维,你真的懂吗?[待更.....]

前言

我这个人不多说废话,说多了你们也不听,直接切入正题

零维

简介

说简单点,零维就是个奇点,也就是没有维度的,没有发散和方向的维度。它没有空间,没有物质,一切皆无。也可以说它是全部,是所有,是存在。用于解释为什么有宇宙,宇宙的物质从哪里来的,时间的不间断和前进态是零维的表达。

零维的定义

零维并不能被称为零维空间,因为零维既不是空间也不是时空,零维只是一个概念,就是没有任何维度。可以说,没有任何物质,在这里,没有任何感觉,一切东西都好像静止了。无论是几维空间,在零维下都将不复存在。
最开始,宇宙中的所有物质能量、时间空间的概念都集中于一个体积无限小,密度无限大的点,也就是零维。后来这个点发生了大爆炸,由零维诞生出了11个维度,构成了现今的十一维空间,使时间开始流逝(时间不是一个维度,时间只是用来描述空间运动的一个物理量,因此认为四维空间是三维空间加一维时间的想法都是错的),进而形成了我们所认识的这个宇宙。
零维犹如我们的思想,甚至可能我们的思想就存在于零维。
它没有大小、没有维度。它只是被想象出来的、作为标志一个位置的点。它什么也没有,空间、时间通通不存在,这就是零维度。

在零维里,不像我们生存在的三维世界,这里你感受不到一切,碰不到一切,不像我们三维世界,你看得到那棵树,你摸得到你家的猫,你可以呼吸……甚至可以说,你是不存在的。

但千万别以为错了,我们的思想并不属于三维,我们只是能触碰到三维而已,具体是几维人,有待考证。这是一个富有哲理的科学问题。

关于零的东西就这么少,再多也找不到了,搞张图凑个数

一维

简介

一维空间是指只由一条线内的点所组成的空间,它只有长度,没有宽度和高度,只能向两边无限延展。一维实际是指的是一条线,在理解上即为左-右一个方向(如:时间)。也可理解为点动成线,指没有面积与体积的物体。
在空间维系内,一维空间是最简单的空间,它只由一条线组成。实际上一条线就是一个一维空间,一条曲线如果没有建立坐标系,那么它也是一维的。简而言之,没有建立坐标系的线就是一个一维空间。
如果你在一维空间中,你只能看见前面和后面。
一维空间是指仅由一个要素构成的空间。就如一张纸上有两个点把这两个点连成一条直线,这一条直线没有高度和深度,只有长度。数线是其中一个一维空间的例子,借由数线上的单位长度来表示每个点的位置。

几何

多胞形

在一维的多胞形是一条线段,它的施莱夫利符号是:{}

超球体

在一维中的超球体是一对点,因为它的表面为零维度,所以有时叫作0球。它的长度是:L=2r,r是它的半径。

定义

一维实际是指的是一条线,在理解上即为左-右一个方向(如:时间)。也可理解为点动成线,指没有面积与体积的物体。他没有长,没有宽。

二维

简介

二维空间或译二度空间(Second Dimension)是指仅由宽度→水平线和高度→垂直线(在几何学中为X轴和Y轴)两个要素所组成的平面空间,只在平面延伸扩展,同时也是美术上的一个术语,例如绘画便是要将三维空间的事物,用二维空间来展现。
二维空间是指仅由长度和宽度(在几何学中为X轴和Y轴)两个要素所组成的平面空间,只向所在平面延伸扩展。
二维空间同时也是美术上的一个术语,例如绘画便是要将三维空间(三度空间)的事物,用二度空间来展现。

几何

在几何中,二维空间仅指的是一个平面,上面的每一个点都可以用由两个数构成的坐标(x,y)来表示。如图《二维空间是平面》所示,坐标将平面分成了4个象限。
形象例证有一位专家曾打过一个比方:让我们先假设一些生活在二维空间的扁片人,他们只有平面概念。假如要将一个二维扁片人关起来,只需要用线在他四周画一个圈即可,这样一来,在二维空间的范围内,他无论如何也走不出这个圈。

定义

三维的物体在二维里可以由一处消失,在另一处出现。

线性代数

线性代数中也有另一种探讨二维空间的的方式,其中彼此独立性的想法至关重要。平面有二个维度,因为长方形的长和宽的长度是彼此独立的。以线性代数的方式来说,平面是二维空间,因为平面上的任何一点都可以用二个独立向量的线性组合来表示。
数量积、角度及长度
二个向量A= [A1,A2]和B= [B1,B2]的数量积定义为:

向量可以画成一个箭头,量值为箭头的长度即其,向量的方向就是箭头指向的方向。向量A的长度为。以此观点来看,两个欧几里得向量A和B的数量积定义为 [2]

其中θ为A和B的角度
向量A和自己的数量积为

因此

这也是向量欧几里得距离的公式。

待更…..