2019-9-14 1248 0
2019-9-14 939 0
编程杂谈

注:有好久没更新了,也不是说没有学习,主要Js逆向,不适合发在网站上本文借鉴自:https://juejin.cn/post/6844903811601924103在此篇文章上进行了一点优化:处理标签多个属性值,增加属性值的兼容性说明JsinnerHTML方法是把文本型的HTML标签转换成DOM树,实现过程与解释器差不多,也算了解一下解释器解释器步骤词法分析->语法分析->解释执行词法分析词法分析的具体任务就是:把字符流变成token流。词法分析通常有两种方案:一种是状态机,一种是正则表达式。我们这里选择状态机。状态机的原理将整个HTML字符串进行遍历,每次读取一个字符,进行一次决策(决定出下一个字符处于哪个状态),当一个状态决策完成的token就会被存入到tokens里。<pclass="yy"id="xx">测试元素</p>对于上述节点信息,我们可以拆分出如下token开始标签:<p属性标签:class="yy"属性标签:id="xx"文本节点:测试元素结束标签:</p>词法分析函数说明封装开头函数functionHTMLLexicalParser(htmlString,tokenHandler){this.token=[];//存储已经分析完成的一个个tokenthis.tokens=[];//标签属性词法分析结束标志为处理标签多个属性添加this.attrFlag=0;//待处理的字符串this.htmlString=htmlString//处理函数tokens转换成树结构函数this.tokenHandler=tokenHandler}start函数start处理的比较简单,如果是<字符,表示开始标签或结束标签,因此我们需要下一个字符信息才能确定到底是哪一类token,所以返回tagState函数去进行再判断,否则认为是文本节点,返回文本状态函数HTMLLexicalParser.prototype.start=function(c){if(c==='<'){//表示开始标签或结束标签,所以需要进一步确认this.token.push(c)//记录tokenreturnthis.tagState}else{returnthis.textState(c)}}tagState、textState函数tagState根据下一个字符,判断进入开始标签状态还是结束标签状态,如果是/表示是结束标签,否则是开始标签textState用来处理每一个文本节点字符,遇到<表示得到一个完整的文本节点token。HTMLLexicalParser.prototype.tagState=function(c){this.token.push(c)if(c==='/'){//表示结束状态,返回结束处理函数returnthis.endTagState}else{//表示开始处理标签状态,接下来会有字母(开始)、空格(属性)、>(标签结束)returnthis.startTagState}}HTMLLexicalParser.prototype.textState=function(c){if(c==='<'){//表示文本状态处理完成,把此窗台存入tokens中this.emitToken('text',this.token.join(''));this.token=[]//置空token准备处理下一状态returnthis.start(c)}else{//还处理文本状态this.token.push(c)returnthis.textState}}emitToken、startTagState、endTagState函数emitToken用来将产生的完整token存储在tokens中,参数是token类型和值。startTagState用来处理开始标签,这里有三种情况:接下来的字符是字母,则认定依旧处于开始标签状态遇到空格,则认定开始标签态结束,接下来是处理属性遇到>同样认定为开始标签态结束,但接下来是处理新的节点信息endTagState用来处理结束标签,结束标签没有属性,因此只有两种情况:如果接下来的字符是字母,则认定依旧处于结束标签态遇到>同样认定为结束标签态结束,但接下来是处理新的节点信息HTMLLexicalParser.prototype.emitToken=function(type,value){varres={type,value}this.tokens.push(res)//流式处理this.tokenHandler&&this.tokenHandler(res)//存在则执行该函数}HTMLLexicalParser.prototype.startTagState=function(c){if(c.match(/[a-zA-Z]/)){//处理标签名状态this.token.push(c.toLowerCase())returnthis.startTagState}if(c===''){//标签名状态结束进入标签属性状态this.emitToken('startTag',this.token.join(''))this.token=[]returnthis.attrState}if(c==='>'){//标签结束状态进入开始分析状态this.emitToken('startTag',this.token.join(''))this.token=[]returnthis.start}}HTMLLexicalParser.prototype.endTagState=function(c){if(c.match(/[a-zA-Z]/)){//双标签结束时状态this.token.push(c.toLowerCase())returnthis.endTagState}if(c==='>'){//双标签结束时状态进入开始分析状态this.token.push(c)this.emitToken('endTag',this.token.join(''))this.token=[]returnthis.start}}attrStateattrState处理属性标签,也处理三种情形如果是字母、数字、等号、下划线、空格、中划线、冒号、分号,则认定为依旧处于属性标签态如果遇到引号,则表示遇到标签属性值,第二次遇到才表示一个标签属性结束(不代表标签状态结束),继续处理标签状态如果遇到>则认定为属性标签状态结束,接下来开始新的节点信息HTMLLexicalParser.prototype.attrState=function(c){if(c.match(/[a-zA-Z0-9=_\-\:;]/)){this.token.push(c)returnthis.attrState}if(c.match(/['"]/)){this.attrFlag=this.attrFlag+1;if(this.attrFlag==2){this.token.push(c)this.emitToken('attr',this.token.join(''))this.token=[]this.attrFlag=0;returnthis.attrState}this.token.push(c)returnthis.attrState}if(c==='>'){returnthis.start}}parse、getOutPutparse解析函数HTMLLexicalParser.prototype.parse=function(){varstate=this.start;for(varcofthis.htmlString.split('')){state=state.bind(this)(c)}}HTMLLexicalParser.prototype.getOutPut=function(){returnthis.tokens}测试词法分析varp=newHTMLLexicalParser('<divclass="xxyy"data="hh">测试并列元素的</div><pclass="pp"data="kk"style="display:none;">测试并列元素的</p>')p.parse()p.getOutPut()语法分析语法分析:就是把上一步分析的结果,处理成有层次的树结构定义树结构//语法分析functionElement(tagName){this.tagName=tagNamethis.attr={}this.childNodes=[]}functionText(value){this.value=value||''}处理词法分析结果思路通过上图分析结果很容易看出层次结构startTagtoken,push一个新节点elementendTagtoken,则表示当前节点处理完成,此时出栈一个节点,同时将该节点归入栈顶元素节点的childNodes属性,这里需要做个判断,如果出栈之后栈空了,表示整个节点处理完成,考虑到可能有平行元素,将元素push到stacks。attrtoken,直接写入栈顶元素的attr属性texttoken,由于文本节点的特殊性,不存在有子节点、属性等,就认定为处理完成。这里需要做个判断,因为文本节点可能是根级别的,判断是否存在栈顶元素,如果存在直接压入栈顶元素的childNodes属性,不存在push到stacks。functionHTMLSyntacticalParser(){this.stack=[]this.stacks=[]}HTMLSyntacticalParser.prototype.getOutPut=function(){returnthis.stacks}//一开始搞复杂了,合理利用基本数据结构真是一件很酷炫的事HTMLSyntacticalParser.prototype.receiveInput=function(token){varstack=this.stackconsole.log('token',token)if(token.type==='startTag'){stack.push(newElement(token.value.substring(1)))}elseif(token.type==='attr'){vart=token.value.split('=');//console.log('t',t);varkey=t[0].replace(/^\s*|\s*$/g,""),value=t[1].replace(/'|"/g,'')stack[stack.length-1].attr[key]=value}elseif(token.type==='text'){if(stack.length){stack[stack.length-1].childNodes.push(newText(token.value))}else{this.stacks.push(newText(token.value))}}elseif(token.type==='endTag'){varparsedTag=stack.pop()if(stack.length){stack[stack.length-1].childNodes.push(parsedTag)}else{this.stacks.push(parsedTag)}}console.log(stack);}测试语法分析结果varhtml='<divclass="xxyy"data="hh"><pclass="ss"data="ff"style="display:none;">嵌套</p></div><pclass="pp"data="kk"style="display:none;">并列</p>'varsyntacticalParser=newHTMLSyntacticalParser()varlexicalParser=newHTMLLexicalParser(html,syntacticalParser.receiveInput.bind(syntacticalParser))lexicalParser.parse()syntacticalParser.getOutPut()解释执行就是把上面的树结构,使用递归映射成真实的dom结构functionvdomToDom(array){varres=[]for(letitemofarray){res.push(handleDom(item))}returnres}functionhandleDom(item){if(iteminstanceofElement){varelement=document.createElement(item.tagName)for(letkeyinitem.attr){element.setAttribute(key,item.attr[key])}if(item.childNodes.length){for(leti=0;i<item.childNodes.length;i++){element.appendChild(handleDom(item.childNodes[i]))}}returnelement}elseif(iteminstanceofText){returndocument.createTextNode(item.value)}}封装函数functionhtml(element,htmlString){varsyntacticalParser=newHTMLSyntacticalParser()varlexicalParser=newHTMLLexicalParser(htmlString,syntacticalParser.receiveInput.bind(syntacticalParser))lexicalParser.parse()vardom=vdomToDom(syntacticalParser.getOutPut())varfragment=document.createDocumentFragment()dom.forEach(item=>{fragment.appendChild(item)})element.appendChild(fragment)}我修改过的代码地址:https://pan.bigdataboy.cn/s/Xaato

Python极客

说明使用Anaconda安装,是方便控制虚拟环境Python的版本,不于本机的全局相冲突。比如:全局==>Python3.8/虚拟环境==>Python3.6PyTorch0.4/虚拟环境==>Python3.7PyTorch1.7安装参考:https://bigdataboy.cn/post-342.html安装CUDA正常安装过程就行CUDA(ComputeUnifiedDeviceArchitecture),是显卡厂商NVIDIA推出的运算平台。CUDA是一种由NVIDIA推出的通用并行计算架构,该架构使GPU能够解决复杂的计算问题。下载网站:https://developer.nvidia.com/cuda-downloads历史版本下载:https://developer.nvidia.com/cuda-toolkit-archive检测CUDAcmd输入:nvcc-V安装cuDNNNVIDIAcuDNN是用于深度神经网络的GPU加速库。它强调性能、易用性和低内存开销。NVIDIAcuDNN可以集成到更高级别的机器学习框架中,如谷歌的Tensorflow、加州大学伯克利分校的流行caffe软件。简单的插入式设计可以让开发人员专注于设计和实现神经网络模型,而不是简单调整性能,同时还可以在GPU上实现高性能现代并行计算。cuDNN的下载需要登录,但是nvidia登录时的验证码经常被强(各种办法尝试都不行),登陆难度极大不用登陆小技巧:进入历史版本选择好,右键复制链接(就是资源实际地址),然后使用迅雷等工具下载下载网站:https://developer.nvidia.com/rdp/cudnn-download历史版本:https://developer.nvidia.com/rdp/cudnn-archive解压&移动解压下载的cuDNN把解压的cuDNN里的文件,移动到[路径]\NVIDIAGPUComputingToolkit\CUDA\v11.4\下相应的文件下cnDNN[bin]->CUDN[bin]cnDNN[include]->CUDN[include]cnDNN[lib\x64]->CUDN[lib\x64]添加环境变量把下面两个路径添加进环境变量C:\ProgramFiles\NVIDIAGPUComputingToolkit\CUDA\v11.4\lib\x64C:\ProgramFiles\NVIDIAGPUComputingToolkit\CUDA\v11.4\检测结果cmd运行这个exe程序创建虚拟环境condacreate-npytorchpython=3.7虚拟环境名字:pytorch虚拟环境PY版本:3.7安装PyTorch切换环境condaactivatepytorch查看本机CUDA版本生成安装命令&安装PyTorch有点大,安装时耐心等待PyTorch官网:https://pytorch.org/命令:condainstallpytorchtorchvisiontorchaudiocudatoolkit=11.1-cpytorch-cconda-forge如果下载是在太慢,可以先用其他工具下载好,再本地安装PyTorch国内镜像:https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorchcondainstall--use-local~/Downloads/a.tar.bz2验证安装进入pytthonshell进行验证importtorchprint(torch.__version__)print(torch.cuda.is_available())Pycharm使用该环境创建新项目,使用conda管理虚拟环境选择这个环境验证

Python极客

安装AnacondaAnaconda历史版本:https://repo.anaconda.com/archive/(本文使用:Anaconda3-5.2.0-Windows-x86_64.exe)正常安装过程同意->下一步->下一步->…->跳过(Skip)不安装VS->完成校验安装是否成功打开AnacondaPrompt配置环境变量添加{路径}Anaconda3\Scripts到Path里cmd输入:conda--version常用操作修改下载源可参考清华源官方文:https://mirror.tuna.tsinghua.edu.cn/help/anaconda/打开C:\Users\[电脑用户名]\.condarcchannels:-defaultsshow_channel_urls:truedefault_channels:-https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main-https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r-https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2custom_channels:conda-forge:https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloudmsys2:https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloudbioconda:https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloudmenpo:https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloudpytorch:https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloudsimpleitk:https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud查看修改后的源命令:condainfo常用命令建议在AnacondaPrompt里使用,而不是在cmd更新全部工具包condaupgrade--all创建指定Py版本虚拟环境condacreate-n[名字]python=[版本号]condacreate-nvenvpython=3.6进入虚拟环境condaactivate[名字]模块安装(进入环境执行)condainstall[模块名]或者pipinstall[模块名]卸载模块(进入环境执行)condaremove[模块名]或者pipuninstall[模块名]卸载环境condaremove-n[环境名]--all查看环境列表condaenvlist

2021-8-25 194 0
奇巧淫技

难点头部顺序反爬、某些框架内部会对头部处理造成获取不到CookieTip:文末可以下载例子猿人学该题官网:http://match.yuanrenxue.com/match/3确定逆向目标请求加密一般在参数头部(Cookie,token,还有其他的自定义的),这里就只有Cookie了开始逆向既然第二个请求,带有Cookie,那么就需要确定该Cookie是生成,还是服务器返回,这里还有第一个请求,所以大概率是服务器返回分析请求第一个请求状态码是202(表示服务器端已经收到请求消息,但是尚未进行处理),还有Set-Cookie,那就可以确定是服务器返回的了获取不到Cookie的原因这里获取不到Cookie,有两个原因一、反爬(对请求头部的顺序有要求,最好是使用原始顺序)二、requests框架的原因(框架会对设置的头部进行优化修改)获取Cookie规避了上面两个问题后,就可以获取到Cookie请求数据importrequestsdefapp(page:int)->dict:session=requests.Session()session.headers={'Host':'match.yuanrenxue.com','Connection':'keep-alive','Pragma':'no-cache','Cache-Control':'no-cache','User-Agent':'Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/91.0.4472.101Safari/537.36','DNT':'1','Accept':'*/*','Origin':'http://match.yuanrenxue.com','Referer':'http://match.yuanrenxue.com/match/3','Accept-Encoding':'gzip,deflate','Accept-Language':'zh-CN,zh;q=0.9',}session.post(url="http://match.yuanrenxue.com/jssm",)print(session.cookies)s=session.get(url="http://match.yuanrenxue.com/api/match/3",headers={"User-Agent":"yuanrenxue.project",},params={"page":page})returns.json()if__name__=='__main__':for_inrange(1,6):print(app(_))结果正确Tip:直接访问数据接口,会返回一段Js其实这段代码一点用都没有。完整代码:https://pan.bigdataboy.cn/s/JZh7

奇巧淫技

难点加密在Cookie,Cookie有时间限制,暗桩反调,混淆,调试阻塞Tip:文末可以下载例子猿人学该题官网:http://match.yuanrenxue.com/match/2确定逆向目标开始逆向加密值在Cookie有两种情况,该加密Cookie是服务器返回或者是本地生成分析请求反ob混淆这段代码经过了ob混淆,格式化后是这样的一键ob反混淆(猿人学提供有)网址:http://tool.yuanrenxue.com/decode_obfuscator分析加密处反混淆后,直接看见了document["cookie"],还有location["reload"]();这个不就就是:设置Cookie然后再次请求嘛调试加密值发现M()函数为空,V(Y)函数疑似加密结果导出加密值这个加密值在函数内部,想要使用就用全局变量接收一下暗桩一这段代码,其中!a6["test"](a2)执行起来很慢,并且一直返回true,所以直接写死,就会发现执行起来很快了,一些调试工具也不会阻塞了vara2=B(this,function(){vara5=function(){vara6=a5["constructor"]("return/\"+this+\"/")()["compile"]("^([^]+(+[^]+)+)+[^]}");return!a6["test"](a2);//true};returna5();});暗桩二如果说暗桩一是技巧,那暗桩二才是真正的暗桩,可以看这段代码改造了console.log只要我们一使用就会陷入,虽然这个暗桩对Js执行没有影响,但是调试就很容易陷入console=newObject()console.log=function(s){while(1){for(i=0;i<1100000;i++){history.pushState(0,0,i)}}}console.toString='[objectObject]'console.log.toString='?toString(){[nativecode]}'请求计算结果稍微修改Js,计算结果importexecjsimportrequestsdefget_c():returnexecjs.compile(open('./main.js','r',encoding="utf-8").read()).call('f')defapp(page:int)->dict:rep=requests.get(url="http://match.yuanrenxue.com/api/match/2",headers={"Cookie":f"sessionid=你的sessionid;m={get_c()};","User-Agent":"yuanrenxue.project",},params={"page":page})returnrep.json()if__name__=='__main__':sum=0for_inrange(1,6):data=app(_)forvalueindata.get('data'):sum=sum+value.get("value")print(sum)结果正确完整代码:https://pan.bigdataboy.cn/s/EYSW

奇巧淫技

难点源码压缩,混淆,反调,时间检测Tip:文末可以下载例子猿人学官网:http://match.yuanrenxue.com/确定逆向目标这个m值开始逆向过反调跟值此处是加密的url在发送出去最后出现的地方,我们可以通过函数调用的栈,进行跟值,找到大体加密的地方本地调试最终我们跟到了此处,发现它在<script>标签里,我们无法格式化,就无法断点调试我采用把整个网页扒下来,修改其中的<href>为绝对路径,采用fiddler本地替换网页响应,实现把在本地把<script>压缩的代码格式化,在手动加断点寻找window.f值逆向需要有怀疑精神,oo0O0()这个函数返回空,不会这么简单吧,进入它定位加密位置经过分析最终确定在这一行改造加密函数到这里,差不多就分析完成,就改造一下加密函数就完成了functionf(){varmv=Date.parse(newDate())+100000000;varwindow={};varm=eval("把eval里的全部复制过来window.f=hex_md5('bigdataboy')".replace(/bigdataboy/,mv))//这里有个小坑必须要用替换,因为拼接的md5值是不对的returnm+'丨'+mv/1000}请求计算结果importexecjsimportrequestsdefget_m():returnexecjs.compile(open('./main.js','r',encoding="utf-8").read()).call('f')defapp(page:int)->dict:rep=requests.get(url="http://match.yuanrenxue.com/api/match/1",headers={"Cookie":"自己的sessionid",'User-Agent':'yuanrenxue.project',},params={"page":page,"m":get_m(),})returnrep.json()if__name__=='__main__':sum=0count=0for_inrange(1,6):data=app(_)count=count+len(data.get("data"))forvalueindata.get("data"):sum=sum+value.get("value")print(sum/count)结果成功完整代码:https://pan.bigdataboy.cn/s/Nnsy

编程杂谈

二叉树的遍历因为使用二叉树最多是链式存储结构,所遍历的方式也是按照链式结构来的。遍历的宗旨在遍历是按照某种次序依次访问二叉树的所有节点,并且每个节点仅仅被访问一次。前序遍历(根左右)遍历顺序ABDHIEJCFKG中序遍历(左根右)遍历顺序HDIBEJAFKCG后序遍历(左右根)遍历顺序HIDJEBKFGC层序遍历遍历顺序ABCDEFGHIJK二叉树代码实现前序遍历(根左右)二叉树的遍历方式,在创建二叉树时就确定好了#include<stdio.h>#include<stdlib.h>typedefcharElemType;//二叉树结构typedefstructBiTNode{ElemTypedata;structBiTNode*lchild,*rchild;}BiTNode,*BiTree;//创建二叉树按照用户遵循的前序遍历的(根左右)方式输入数据voidcreateBiTree(BiTree*T){charc;printf("输入元素:");scanf("%c",&c);getchar();if(c=='#'){*T=NULL;}else{*T=(BiTree)malloc(sizeof(BiTNode));(*T)->data=c;createBiTree(&(*T)->lchild);//先创建左子节点createBiTree(&(*T)->rchild);//再创建右子节点}}//遍历二叉树voidpreOrderTraverse(BiTreeT,intlevel){if(T!=NULL){printf("数据%c在第%d层\n",T->data,level);preOrderTraverse(T->lchild,level+1);preOrderTraverse(T->rchild,level+1);}}intmain(){intlevel=1;BiTNode*T;createBiTree(&T);preOrderTraverse(T,level);return0;}输入顺序中序遍历(左根右)输入方式还是前序的输入方式,只是遍历的打印位置修改了一下//中序遍历二叉树voidpreOrderTraverse(BiTreeT,intlevel){if(T!=NULL){preOrderTraverse(T->lchild,level+1);printf("数据%c在第%d层\n",T->data,level);preOrderTraverse(T->rchild,level+1);}}后序遍历(左右后)输入方式还是前序的输入方式,只是遍历的打印位置修改了一下//后序遍历二叉树voidpreOrderTraverse(BiTreeT,intlevel){if(T!=NULL){preOrderTraverse(T->lchild,level+1);preOrderTraverse(T->rchild,level+1);printf("数据%c在第%d层\n",T->data,level);}}