easyui datagrid列拖拽 - wuwenhai - 博客园

mikel阅读(1188)

来源: easyui datagrid列拖拽 – wuwenhai – 博客园

还是那句话:“客户是上帝,他们怎么说我们这些程序猿只能尽自己所能来达到效果。”我觉着吧,他们太厉害了 看似没什么必要的功能,硬是能想出来逼着我这个前端还比较薄弱的人来实现。这次提出的要求 如题:datagrid列要能够拖拽。由于之前项目是使用easyui做的,可是datagrid它并没有提供列拖拽的功能。然后又去网上搜索了下,丫丫的竟然也没多少这方面实现的例子。我拍了拍头郁闷呐,又得自己做。好吧 自己做就自己做吧。说下思路:首先easyui 它有提供了拖拽的功能Draggable,那我们就可以想 拖拽标题头到另外的标题头上面我们就对datagrid的columns重新绑定一次 并刷新datagrid这个功能不就行了?没错思路可行,let’s do it!by 梨洛 。实现上代码来解释

复制代码
<script type="text/javascript">
    var cols = [{ field: 'testName', title: '<span class="dropitem">测试名</span>', align: 'center',width:120 },
               { field: 'testValue', title: '<span class="dropitem">测试值</span>', align: 'center', width: 120}];
    var url="/Test/Test1Data";
    $(document).ready(function () {
        init();
        drag();//绑定datagrid,绑定拖拽
    });
    function init() {
        $("#test").datagrid({
            url: url,
            type: "post",
            datatype: "json",
            width: 600,
            height: 280,
            loadMsg: "数据加载中,请稍后...",
            nowrap: true,
            rownumbers: false,
            pagination: true,
            singleSelect: true,
            columns: [cols],
            //bind数据成功重新设置拖动对象
            onLoadSuccess: function (data) {          
                drag();
            }
        });
    }
    //拖动drag和drop都是datagrid的头的datagrid-cell
    function drag() {
        $('.datagrid-header-inner .datagrid-cell').draggable({
            revert: true,
            proxy: 'clone'
        }).droppable({
            accept: '.datagrid-header-inner .datagrid-cell',
            onDrop: function (e, source) {
                //取得拖动源的html值
                var src = $(e.currentTarget.innerHTML).html();
                //取得拖动目标的html值
                var sou = $(source.innerHTML).html();
                var tempcolsrc;//拖动后源和目标列交换
                var tempcolsou;
                var tempcols=[];
                for (var i = 0; i < cols.length; i++) {
                    if (cols[i].title == sou) {
                        tempcolsrc = cols[i];//循环读一遍列把源和目标列都记下来
                    }
                    else if (cols[i].title == src) {
                        tempcolsou = cols[i];
                    }
                }
                for (var i = 0; i < cols.length; i++) {
                    //再循环一遍,把源和目标的列对换
                    var col = {
                        field: cols[i].field,
                        title: cols[i].title,
                        align: cols[i].align,
                        width: cols[i].width
                    };
                    if (cols[i].title == sou) {
                        col = tempcolsou;
                    }
                    else if (cols[i].title == src) {
                        col = tempcolsrc;
                    }
                     tempcols.push(col);  
                }
                 cols = tempcols;
                 //1秒后执行重绑定datagrid操作。可能是revert需要时间,这边如果没有做延时就直接重绑 就会出错。
                 //我目前的水平就想到这个笨办法,各位如果有好的想法建议可以提出来讨论下。
                timeid = setTimeout("init()", 1000);
            }
        });
    }
</script>

<div id="test"></div>
复制代码

上效果图:

原datagrid

拖动列

拖动后两列交换

再附上这几篇文章的源码

相关JQuery取元素什么的 请参看前两篇 jquery模拟enter按下
转载请注明出处http://www.cnblogs.com/wujie6166

thinkphp在php5.6以上版本显示"No input file specified" - 韦书文 - 博客园

mikel阅读(1047)

来源: thinkphp在php5.6以上版本显示”No input file specified” – 韦书文 – 博客园

今天在练习php的时候,有些新功能只能兼容php7+以上的版本,我只好切换php的版本。后来发现再次打开thinkphp时,网址进入登入页面,但是却显示”No input file specified”,如下图所示:

上网查了一下才知道,PHP5.6以上的是fast_cgi模式,而在某些情况下,不能正确识别path_info所造成的错误,这个时候,你只需要更改.htaccess文件,如下图所示

只需要在index.php后面添加”?”即可,如下图所示

再次打开页面将正常显示

[C#/ASP.NET]List中Sort()、Find()、FindAll()、Exist()的使用方法 - zock - 博客园

mikel阅读(1135)

来源: [C#/ASP.NET]List中Sort()、Find()、FindAll()、Exist()的使用方法 – zock – 博客园

[C#/ASP.NET]中List<T>真的非常好用。一个例子简单说明一下List<>中Sort()、Find()、FindAll()、Exist()的使用方法

简单介绍:

List<T>.Sort() → 排序T

List<T>.Find() → 找出一個T

List<T>.FindAll() →找出多個T

List<T>.Exist() →判斷T是否存在

示例代码:

页面文件GenericList.aspx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="GenericList.aspx.cs" Inherits="GenericList" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>GenericList</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        原始資料:
        <asp:GridView ID="GridView1" runat="server">
        </asp:GridView>
    </div>
    </form>
</body>
</html>

后台代码文件GenericList.aspx.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class GenericList : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        List<Person> lstPerson = new List<Person>();
        lstPerson.Add(new Person(1, "puma", 10));
        lstPerson.Add(new Person(2, "F6 Team", 20));
        lstPerson.Add(new Person(3, "ASP.NET", 30));
        lstPerson.Add(new Person(4, "Dotblogs", 40));
        //原始資料顯示在GridView上
        this.GridView1.DataSource = lstPerson;
        this.GridView1.DataBind();
        //List<T>.Find()
        //找出Name='puma'的Person
        Response.Write("找出Name='puma'的Person→ ");
        Response.Write(lstPerson.Find(delegate(Person p) { return p.Name == "puma"; }).ToString() + "<p>");
        //List<T>.FindAll()
        //找出Age>10的數目
        Response.Write("找出Age>10的數目→ ");
        Response.Write(lstPerson.FindAll(delegate(Person p) { return p.Age > 10; }).Count.ToString() + "<p>");
        //List<T>.Exists()
        //檢查Name='F6'是否存在
        Response.Write("檢查Name='F6'是否存在→ ");
        Response.Write(lstPerson.Exists(delegate(Person p) { return p.Name == "F6"; }).ToString() + "<p>");
        //List<T>.Sort()
        //依Name升冪排序
        Response.Write("<p>依Name升冪排序↑<br/>");
        lstPerson.Sort(delegate(Person p1, Person p2) { return Comparer<string>.Default.Compare(p1.Name, p2.Name); });
        foreach (Person p in lstPerson)
        {
            Response.Write(p.ToString() + "<br/>");
        }
        //List<T>.Sort()
        //依Name降冪排序
        Response.Write("<p>依Name降冪排序↓<br/>");
        lstPerson.Sort(delegate(Person p1, Person p2) { return Comparer<string>.Default.Compare(p2.Name, p1.Name); });
        foreach (Person p in lstPerson)
        {
            Response.Write(p.ToString() + "<br/>");
        }
    }
}
public class Person
{
    private int _ID;
    private string _Name;
    private int _Age;
    public Person(int ID, string Name, int Age)
    {
        _ID = ID;
        _Name = Name;
        _Age = Age;
    }
    public int ID
    {
        set { _ID = value; }
        get return _ID; }
    }
    public string Name
    {
        set { _Name = value; }
        get return _Name; }
    }
    public int Age
    {
        set { _Age = value; }
        get return _Age; }
    }
    public override string ToString()
    {
        return string.Format("ID:{0},Name:{1},Age:{2}", _ID, _Name, _Age);
    }
}

本文地址:http://www.cnblogs.com/atree/archive/2011/02/25/Asp-Net_List_Sort_Find_FindAll_Exist.html

(1条消息)解决:Uncaught TypeError: $ is not a function_JavaScript_weixin_41551266的博客-CSDN博客

mikel阅读(624)

来源: (1条消息)解决:Uncaught TypeError: $ is not a function_JavaScript_weixin_41551266的博客-CSDN博客

本来好好的,突然就出现的错误,不过这并不是什么难解决的错误;

我的问题是:在js文件里我定义了一个 var $;变量,只要把这个去掉就没问题了。

顺便总结一下:出现这种错误的解决方法:

1,先看看你的jq文件是否已经先导入了

一般来说,你的js文件需要位于最后

2.就是在js文件里找一下有没有 var $; 【呜呜我傻】

看完一些博客,大概明白,WordPress会先运行自己的脚本,然后释放$,这样就不会与其他库冲突。

所以一旦你定义了$;他就会默认使用你对$的定义,而不会管JQ的定义;

3. 下面是一些防止冲突的方法

JQuery(function ($) {
直接使用JQuery就不会又冲突问题,再在jQuery使用$,利用闭包函数的作用域规避冲突

 

参考链接:https://crunchify.com/how-to-fix-wordpress-uncaught-typeerror-is-not-a-function-jQuery-error/

https://stackoverflow.com/questions/12343714/typeerror-is-not-a-function-when-calling-jquery-function
————————————————
版权声明:本文为CSDN博主「weixin_41551266」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_41551266/article/details/90577776

Redis缓存设计与性能优化 - 白露非霜 - 博客园

mikel阅读(935)

来源: Redis缓存设计与性能优化 – 白露非霜 – 博客园

Redis我们一般是用作缓存,扛并发;或者用于某些特定的业务场景,比如前面说到redis各种数据类型的使用场景以及redis的哨兵和集群模式。

这里主要整理了下redis用作缓存,存在的一些问题,以及改善方案。

 

简单的流程就像这个样子,一般请先到缓存区获取,如果缓存没有再到后端的数据库去查询。

1.缓存穿透

缓存穿透是指,是指查询一个根本不存在数据,这样缓存层里面没有,就会去访问后面的存储层了。如果有大量的这种恶意请求过来,都打向后面的存储层。显然我们的存储层是扛不住这样的压力。这样缓存就失去了保护后面存储的意义了。

解决方案:

1.缓存空对象

对于缓存穿透,可以采用缓存空对象,第一次进来缓存和DB都没有,就存个空对象到缓存里面。但是如果大批量的恶意请求过来,这样做就会导致缓存的key暴增,显然不是一个很好的方案。

2.布隆过滤器

对于不存在的数据布隆过滤器一般都能够过滤掉,不让请求再往后端发送。当布隆过滤器说某个值存在时,这个值可能不存在;但是它说不存在时,那就肯定不存在。布隆过滤器是一个大型的位数组和几个不一样的无偏 hash 函数。所谓无偏就是能够把元素的hash值算得比较均匀。向布隆过滤器中添加 key 时,会使用多个hash 函数对key进行hash分别算得一个整数索引值然后对位数组长度进行取模运算得到一个位置,每个hash函数都会算得一个不同的位置。再把位数组的这几个位置都置为 1 就 完成了 add 操作。

向布隆过滤器询问 key 是否存在时,跟 add 一样,也会把 hash 的几个位置都算出来,看看位数组中这几个位置是否都为1,只要有一个位为0,那么说明布隆过滤器中这个key肯定不存在。但是都是 1,这并不能说明这个key就一定存在,只是极有可能存在,因为这些位被置为1可能是因为其它的key存在所致。

 

guvua包布隆过滤器的使用,导包

 

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
        </dependency>

 

伪代码:

复制代码
    public void bloomFilterTest() {
        BloomFilter<CharSequence> bloomFilter = BloomFilter.create(
                Funnels.stringFunnel(Charset.forName("UTF-8")),
                1000, //期望存入的数据个数
                0.001);//误差率
        //添加到布隆过滤器
        String[] keys = new String[1000];
        for (String key: keys) {
            bloomFilter.put(key);
        }

        String key = "key";
        boolean exist = bloomFilter.mightContain(key);
        if (!exist) {
            return;
        }
        //todo 存在才去缓存获取
    }
复制代码

可以看到这个类里面有很多的hash算法:com.google.common.hash.Hashing

redisson也有布隆过滤器的实现。

 

 

2.缓存失效

由于大批量的key同时失效,导致,大量的请求同时打向数据库,造成数据库压力过大,甚至直接挂掉。我们在批量写入缓存的时候,设置超时时间,可以是一个固定时间+随机时间方式来生成,这样就可以错开失效时间。

3.缓存雪崩

缓存雪崩是指缓存层挂掉之后,所有请求都打向数据库,数据库扛不住,也可能挂掉,就导致对应的服务也挂掉,也会影响上游的调用服务。这样的级联问题。就像雪崩最开始一小片,然后越来越大,导致整个服务崩溃。

解决方案:

1.保证缓存层的高可用性,比如redis哨兵或者redis集群。

2.各依赖服务之间做限流,熔断,降级等,比如Hystri,阿里的sentinel

4.缓存一致性

引入缓存之后,随之而来的问题就是当DB数据更新时,缓存中的数据就会与db数据不一致。所以数据修改时是先更新缓存还是先更新DB?

如果先更新缓存,然后更新DB失败,那么下一个请求过来读取的缓存数据不是最新的。而我们实际上最终数据肯定都是以DB为准的。

先更新db 在更新缓存,这是在更新DB的时候来的请求读取的数据也是不是最新的

淘汰缓存——更新DB——重新刷进缓存,在更新db是来的请求在缓存没有数据,就会去请求DB,如果并发 可能操作多各请求去写DB,那么就需要加锁了

加锁——淘汰缓存——更新DB——重新刷进缓存,这样相对而言就比较保险了

5.bigkey问题

Bigkey是什么?在redis中,一个字符串最大512MB;hash,list,set,zset可以存储2^31 – 1 个元素。

一般来说字符串超过10kb,其他的几种元素个数不要超过5000个。

可以使用src/redis-cli –bigkeys 来查看bigkey,我这里设置了一个30多K的字符串,看下扫描结果,扫除了一个字符串类型的bigkey,4084字节。

 

 

Bigkey有哪些危害。一是删除时阻塞其他请求,比如一个bigkey,平时都没什么,但是设置了过期时间,到期了删除时,可能就会阻塞其他请求,4.0之后可以开启lazyfree-lazy- expire yes来异步删除;二是造成网络拥堵,比如一个key数据量达到1MB,假设并发量1000,这个时候获取它就会产生1000MB的流量,千兆网卡,峰值的速率也才128MB/S,并不是扛不住并发,而是会占用大量网络带宽。

对于很大list,set这些,我们可以将数据拆分,生成一个系列的的key去存放数据。如果是redis集群这些key自然就可以分到不同的小主从上面去,如果是单机,那么可以自己实现一个路由算法,来如何获取这一系列key中的某一个。

6. 客户端使用

1.避免多个服务使用一个redis实例,如果实在有,可以看下将业务拆分,把这些公共数据服务化。

2.使用连接池,控制有效连接,同时也提高效率。连接池重要参数设置:

1 maxActive 资源池中最大连接数 默认值8

2 maxIdle 资源池允许最大空闲 的连接数 默认值8

3 minIdle 资源池确保最少空闲 的连接数 默认值0

4 blockWhenExhausted 当资源池用尽后,调用者是否要等待。只有当为true时,下面的maxWaitMillis才会生效,默认值true 建议使用默认值

5 maxWaitMillis 当资源池连接用尽后,调用者的最大等待时间(单位为毫秒) -1:表示永不超时 不建议使用默认值

6 testOnBorrow 向资源池借用连接时是否做连接有效性检测(ping),无效连接会被移除 默认值false 业务量很大时候建议 设置为false(多一次 ping的开销)。

7 testOnReturn 向资源池归还连接时是否做连接有效性检测(ping),无效连接会被移除 默认值false 业务量很大时候建议 设置为false(多一次 ping的开销)。

8 jmxEnabled 是否开启jmx监控,可用于监控 默认值true 建议开启,但应用本身也要开启

前面三个参数相对而言更重要,单独拎出来再说下:

最大连接数maxActive:

可以从业务希望的并发量,客户端执行时间,redis资源设置(应用个数(集群部署多少个实例) * maxActive <= maxclients(redis最大连接数,redis配置中设置的)),等因素考虑。

比如一次客户端执行时间2ms,那么一个连接的QPS就是500,业务期望的QPS是3000,那么理论上连接池大小3000/500=60个,实际上考虑其他影响,一般设置比理论值稍微大点。但这个值不是越大越好,一方面连接太多占用客户端和服务端资源,另一方面对    于Redis这种高 QPS的服务器,一个大命令的阻塞即使设置再大资源池仍然会无济于事。

最大空闲连接数maxIdle:

maxIdle实际上才是业务需要的最大连接数,空闲的连接造好放在那儿,进来一个请求就可以直接拿来用了。maxActive是为了给出总量,所以maxIdle不要设置过小,否则会有当空闲连接不够,就会创建新的连接,又会有新的开销,最佳就是maxActive =       maxIdle。这样就避免连接池伸缩带来的性能干扰。但是如果并发量不大或者maxActive设置过高,会导致不必要的连接资源浪费。一般推荐maxIdle可以设置为按上面的业务期望QPS计算出来的理论连接数,maxActive可以再放大一些。

最小空闲连接数minIdle:

至少保持多少空闲连接,在使用连接的过程中,如果连接数超过了minIdle,那么继续建立连接,如果超过了 maxIdle,当超过的连接执行完业务后会慢慢被移出连接池释放掉。

3.缓存预热

比如说上线一个抢购活动,肯定到点开始就会有很多人来请求了,这个时候就可以提前做数据的预热,既可以把连接池初始化好,也可以把数据放好。

SQLSERVER中的timestamp 和 C#中的byte[] 转换 - iDEAAM - 博客园

mikel阅读(1298)

来源: SQLSERVER中的timestamp 和 C#中的byte[] 转换 – iDEAAM – 博客园

项目中由于需求设计,数据库中需要一个timestamp时间戳类型的字段来作为区别数据添加和修改的标识。由于timestamp在SQL SERVER 2005数据库中,不可为空的timestamp类型在语义上等同于binary(8)类型,可为空的 timestamp类型在语义上等同于varbinary(8)类型,这将导致在C#程序中获取到的timestamp类型则变成了byte[]类型。所以如果我们需要从数据库中获取并使用这个时间戳的话就必需经过转换。

我们先建立一张测试表,语句如下:

CREATE TABLE [dbo].[tb_Ts](
 [id] [int] IDENTITY(1,1) NOT NULL,
 [TS] [timestamp] NULL,
  [nvarchar](50) NULL
) ON [PRIMARY]

表名为tb_Ts,只有三个字段id,TS和text。其中TS字段就是我们需要的时间戳字段

SQL Server 中timestamp类型的定义

首先看下timestamp在SQL Server 2005中的定义,该定义摘抄自SQL Server 2005联机丛书(具体详情点击此链接):

timestamp 公开数据库中自动生成的唯一二进制数字的数据类型。timestamp 通常用作给表行加版本戳的机制。 存储大小为 8 个字节。 timestamp 数据类型只是递增的数字,不保留日期或时间。 若要记录日期或时间,请使用 datetime 数据类型。

备注:

每个数据库都有一个计数器,当对数据库中包含 timestamp 列的表执行插入或更新操作时,该计数器值就会增加。 该计数器是数据库时间戳。 这可以跟踪数据库内的相对时间,而不是时钟相关联的实际时间。

一个表只能有一个 timestamp 列, 每次修改或插入包含 timestamp 列的行时,就会在 timestamp 列中插入增量数据库时间戳值。 这一属性使 timestamp 列不适合作为键使用,尤其是不能作为主键使用。

对数据行(row)的任何更新都会更改 timestamp 值,从而更改键值。 如果该列属于主键,那么旧的键值将无效,进而引用该旧值的外键也将不再有效。 如果该表在动态游标中引用,则所有更新均会更改游标中行的位置。 如果该列属于索引键,则对数据行的所有更新还将导致索引更新。

使用某一行中的 timestamp 列可以很容易地确定该行中的任何值自上次读取以后是否发生了更改。 如果对行进行了更改,就会更新该时间戳值。 如果没有对行进行更改,则该时间戳值将与以前读取该行时的时间戳值一致。 若要返回数据库的当前时间戳值,请使用 @@DBTS

Transact-SQL timestamp 数据类型不同于在 SQL-2003 标准中定义的 timestamp 数据类型。 SQL-2003 timestamp 数据类型等同于 Transact-SQL datetime 数据类型。

rowversion 的数据类型为 timestamp 数据类型的同义词,并具有数据类型同义词的行为。 在 DDL 语句,请尽量使用 rowversion 而不是 timestamp。 有关详细信息,请参阅 数据类型同义词 (Transact-SQL)

程序中获取出的timestamp

如何使用SQL语句插入timestamp字段值?

我们从上面的timestamp定义中知道了timestamp这个值一般都是数据库自动添加和修改的,相当于自动增长标识一样(而且执行update修改语句这个字段也会自动更新),所以一般这个字段我们只做查询操作。如果要更新这个字段则会提示这个错误信息:不能更新时间戳列。但是这个字段是可以手动添加的,不过也只能使用DEFALUT字段(default字段为SQL Service数据库的一个默认值),如果传入其他值则会提示错误信息:不能将显式值插入时间戳列。请对列列表使用 INSERT 来排除时间戳列,或将 DEFAULT 插入时间戳列。下面是添加timestamp的SQL语句:

INSERT INTO [tb_Ts]([TS]) VALUES(DEFAULT)

解决数据库中timestamp类型和C#中byte[]类型转换问题

在程序中我们发现,通过ADO.NET获取数据库中timestamp字段值到程序中,结果类型为byte[]。假设在数据库中timestamp的值为0x00000000000007D6,那么获取到.net程序中的值就不是这样了,一把来说会变成byte[]的数组类型。那么我们的解决方式有两种,第一种方式是直接在数据库中将timestamp进行转换,可以转换成十六进制字符串类型或者BIGINT的长整形,这也是我推荐的方法。还有一种是在.NET 程序中使用BitConverter方法进行转换。以下是两种方式的代码:

方法一(在SQL中转换):

SELECT TS
,CAST(TS AS VARBINARY(8)) AS 'timestamp转十六进制字符串' 
,CONVERT(BIGINT,TS) AS 'timestamp转bigint类型'
FROM tb_Ts

这样一来我们就可以获取到timestamp的十六进制字符串或者bigint,最终查询出来的结果如下图:timestamp转换成十六进制字符串或者bigint类型的结果

另外要说明的一点是,VARBINARY(8)对应的C# 类型是byte[],所以建议直接转换成bigint类型,否则在C#中还要调用下面的方法


方法二(在程序中转换,调用下面的方法即可):

/// <summary>
/// 将数据库中timespan转换成十六进制字符串
/// </summary>
/// <param name="objTs">从数据库中获取的timespan值</param>      
/// <returns>timespan十六进制字符串</returns>
public  string ConvertToTimeSpanString(object objTs)
{
byte[] btTsArray=objTs as byte[];
string strTimeSpan = "0x"+ BitConverter.ToString(btTsArray).Replace("-","");
return strTimeSpan;
}

PC微信逆向:使用HOOK拦截二维码_Python_鬼手的博客-CSDN博客

mikel阅读(2624)

源: PC微信逆向:使用HOOK拦截二维码_Python_鬼手的博客-CSDN博客

文章目录
微信版本
寻找微信二维码基址
PNG文件格式
使用CE过滤基址
使用OD确定二维码基址
验证二维码基址
寻找微信二维码内容的基址
微信二维码的存储内容
使用CE寻找二维码内容的基址
验证基址
定制微信登录二维码的可能性
使用hook截取二维码
最终效果
微信版本

寻找微信二维码基址
PNG文件格式
微信二维码在内存中存放形式是png格式的二进制数据,所以我们需要眼熟一下png的文件格式,如图

前32个字节是固定的,分别是btPngSignature和struct PNG_CHUNK chunk结构,其中保存有png图片的标识。

其中NG和IHDR是每个PNG文件都会有的标识,眼熟一下就好。微信的二维码图片就是通过这种格式在内存中存放

使用CE过滤基址
首先在微信未登录状态下附加微信,此时二维码还未加载

然后选择未知的数值,点击首次扫描

出现三百万个结果

此时我们再次点击切换账号,出现二维码,让保存二维码的地址被赋值

然后选择变动的数值 再次扫描

此时还剩下七万个结果

然后用手机扫描二维码 不要点击登录,再次扫描变动的数值,此时还剩三万多个结果

接着随意移动微信框,点击未变动的数值,还剩一万多个结果。返回二维码登录重复以上操作,直到地址栏还剩下两个绿色的基址,这两个绿色的基址就是我们要的。

因为随机基址的存在,这个地址在各位的电脑上是不一样的。但是低四位是一致的,这两个地址应该是xxxx9194和xxxx919C。

使用OD确定二维码基址
然后重启一次微信,再用CE附加,回到这个状态

用OD附加微信,在找到的第一个地址xxxx9194下内存写入断点

点击切换账号,在二维码未加载时程序会断下。注意,这个地方会断下来两次,第二次才是我们要的结果。

因为二维码是存放在微信的核心模块WeChatWin中的,所以我们在堆栈中找到所有的WeChatWin中的函数

像这种API的调用就可以直接排除掉,然后在每一个疑似函数上下断点。找的时候堆栈尽量往下拉,这个函数会比较靠后。

因为我已经找过一遍了,所以直接告诉你们是这一个。特征是有一个ecx传参。

接着在这个函数上下断点,删除内存访问断点,F9运行

然后扫一下二维码,点击返回二维码登录,程序断下

此时观察ecx指针的内容,明显是一个结构体,结构体的第一个是地址,第二个好像是大小。然后在这个地址上数据窗口跟随

里面是PNG文件的二进制数据,这个就是我们要找的微信二维码的基址

验证二维码基址
打开PCHunter,选择微信进程,查看->查看进程内存,输入地址和大小,然后将内存dump下来

打开图片

现在已经确定就是我们需要的二维码。然后我们将这个call的地址减去模块基址,记录下偏移。待会需要HOOK这个call

寻找微信二维码内容的基址
微信二维码的存储内容
二维码其实是一种开放性的信息存储器,它将固定的信息存储在自己的黑白小方块之间。大部分的二维码都有一个特点,就是里面存放的其实是一段文本。我们可以利用这个文本来寻找突破口

将微信的二维码截图保存,然后用在线的二维码解码器解析微信的二维码

可以看到解码之后的结果是一段网址

使用CE寻找二维码内容的基址

如果直接搜索这段网址是找不到任何结果的,原因是因为微信在保存这段位置的时候,实际上是将它分为了两部分存储

第一部分:http://weixin.qq.com/x
第二部分:/I-yOUnFpRaZOwZyVPC0H
1
2
第一部分的固定不变的,第二部分被当作一个参数传入,客户端从服务器获取的只是第二部分的内容。所以我们去搜索第二部分。

另外,微信的二维码会定时刷新,刷新的时候会改变第二部分的内容。如果你搜不到的话可能是因为之前的文本已经失效了。从解析文本到搜索文本最好在一分钟之内完成

此时 我们直接搜索第二部分的文本

搜索完成之后,等待二维码自动刷新,然后找到那个变化之后的地址,用截图上传的方式确保找到的是正确的地址

然后用OD附加微信,在找到的地址上下内存写入断点

等待二维码自动刷新,二维码刷新时会往原来存放二维码的地址写入新的二维码数据,程序就会断下

此时eax指向二维码的文本内容,我们找到堆栈中的第一个地址,在数据窗口显示,此时就能找到存放微信二维码数据的基址了

然后我们在CE中添加WeChatWin.dll模块,找到模块基址,算出偏移(用0x104CF618-0xF250000=127F618)。然后将这个地址换成模块基址+偏移的方式添加到CE地址栏。

验证基址
重新打开微信,用CE载入

保留当前列表,然后将二维码内容指针的值添加到列表

点击确定。此时二维码的内容和解析出来的内容一致,说明基址有效

定制微信登录二维码的可能性
那么我们拿到这个二维码的内容有什么作用呢?我们可以将这个获取到的二维码内容调用二维码生成器的API接口进行再次编码,然后生成一个更加漂亮好看专属二维码,效果如图:

使用hook截取二维码
接着我们编写一个dll,将这个dll注入到微信进程中,利用IAT Hook截取微信的二维码。部分关键代码如下:

开启HOOK

void StartHook(DWORD dwHookOffset,LPVOID pFunAddr, HWND hWnd)
{
hDlg = hWnd;
//拿到模块基址
DWORD dwWeChatWinAddr = GetWeChatWinAddr();
//需要HOOK的地址
DWORD dwHookAddr = dwWeChatWinAddr + dwHookOffset;

//填充数据
jmpCode[0] = 0xE9;
//计算偏移
*(DWORD*)(&jmpCode[1]) = (DWORD)pFunAddr – dwHookAddr-5;

// 保存以前的属性用于还原
DWORD OldProtext = 0;

// 因为要往代码段写入数据,又因为代码段是不可写的,所以需要修改属性
VirtualProtect((LPVOID)dwHookAddr, 5, PAGE_EXECUTE_READWRITE, &OldProtext);

//保存原有的指令
memcpy(backCode, (void*)dwHookAddr, 5);

//写入自己的代码
memcpy((void*)dwHookAddr, jmpCode, 5);

// 执行完了操作之后需要进行还原
VirtualProtect((LPVOID)dwHookAddr, 5, OldProtext, &OldProtext);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
卸载HOOK

void UnHook(DWORD dwHookOffset)
{
DWORD dwWeChatWinAddr = GetWeChatWinAddr();
DWORD dwHookAddr = dwWeChatWinAddr + dwHookOffset;

// 保存以前的属性用于还原
DWORD OldProtext = 0;

// 因为要往代码段写入数据,又因为代码段是不可写的,所以需要修改属性
VirtualProtect((LPVOID*)dwHookAddr, 5, PAGE_EXECUTE_READWRITE, &OldProtext);

// Hook 就是向其中写入自己的代码
memcpy((LPVOID*)dwHookAddr, backCode, 5);

// 执行完了操作之后需要进行还原
VirtualProtect((LPVOID*)dwHookAddr, 5, OldProtext, &OldProtext);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
保存图片

void SaveImg(DWORD qrcode)
{
//获取图片长度
DWORD dwPicLen = qrcode + 0x4;
size_t cpyLen = (size_t)*((LPVOID*)dwPicLen);
//拷贝图片的数据
char PicData[0xFFF] = { 0 };
memcpy(PicData, *((LPVOID*)qrcode), cpyLen);

//将文件写到本地
HANDLE hFile = CreateFileA(“E:\\qrcode.png”,GENERIC_ALL,0,NULL,CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,NULL);
if (hFile==NULL)
{
MessageBox(NULL, “创建图片文件失败”, “错误”, 0);
return;
}

DWORD dwRead = 0;
if (WriteFile(hFile, PicData, cpyLen, &dwRead, NULL) == 0)
{
MessageBox(NULL, “写入图片文件失败”, “错误”, 0);
return;
}

CloseHandle(hFile);
//显示图片
CImage img;
CRect rect;
//拿到控件的句柄
HWND hPic = GetDlgItem(hDlg, IDC_QRPIC);
GetClientRect(hPic, &rect);
//载入图片
img.Load(“E:\\qrcode.png”);
img.Draw(GetDC(hPic), rect);

//显示二维码内容
ShowQrCodeContent(hDlg);

//完成之后卸载HOOK
UnHook(QrCodeOffset);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
最终效果
最终效果如图:

最后附上工程和成品DLL

目前微信机器人的成品已经发布,需要代码请移步Github。还请亲们帮忙点个star

https://github.com/TonyChen56/WeChatRobot
————————————————
版权声明:本文为CSDN博主「鬼手56」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_38474570/article/details/92798577

【PC微信探秘】使用C#读取PC微信内存数据_C#_赵庆明老师-CSDN博客

mikel阅读(1084)

来源: 【PC微信探秘】使用C#读取PC微信内存数据_C#_赵庆明老师-CSDN博客

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace L014ReadWeChatMemory
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void Button1_Click(object sender, EventArgs e)
{
this.textBox1.Clear();

//微信进程
Process WxProcess = null;
//WeChatWin.dll基址
IntPtr WeChatWinBaseAddress = IntPtr.Zero;
//微信版本
String WeChatVersion = “”;
Process[] processes = Process.GetProcesses();
foreach (Process process in processes)
{
if (process.ProcessName == “WeChat”)
{
WxProcess = process;
this.textBox1.AppendText(“微信已找到!” + Environment.NewLine);
this.textBox1.AppendText(“微信句柄:\t” + “0x” + ((int)(process.Handle)).ToString(“X8”) + Environment.NewLine);
foreach (ProcessModule processModule in process.Modules)
{
if (processModule.ModuleName == “WeChatWin.dll”)
{
WeChatWinBaseAddress = processModule.BaseAddress;
this.textBox1.AppendText(“微信基址:\t” + “0x” + ((int)(processModule.BaseAddress)).ToString(“X8”) + Environment.NewLine);

WeChatVersion = processModule.FileVersionInfo.FileVersion;
this.textBox1.AppendText(“微信版本:\t” + processModule.FileVersionInfo.FileVersion + Environment.NewLine);
break;
}
}
break;
}
}

if (WxProcess == null)
{
this.textBox1.AppendText(“微信没有找到!”);
return;
}

//微信号
int WxNameAddress = (int)WeChatWinBaseAddress + 0x1131B90;
this.textBox1.AppendText(“微信号地址:\t” + “0x” + ((int)(WxNameAddress)).ToString(“X8”) + Environment.NewLine);
this.textBox1.AppendText(“微信号:\t” + GetString(WxProcess.Handle, (IntPtr)WxNameAddress) + Environment.NewLine);

//微信昵称
int WxNickNameAddress = (int)WeChatWinBaseAddress + 0x1131C64;
this.textBox1.AppendText(“微信昵称地址:\t” + “0x” + ((int)(WxNickNameAddress)).ToString(“X8”) + Environment.NewLine);
this.textBox1.AppendText(“微信昵称:\t” + GetString(WxProcess.Handle, (IntPtr)WxNickNameAddress) + Environment.NewLine);

}

String GetString(IntPtr hProcess, IntPtr lpBaseAddress, int nSize = 100)
{
byte[] data = new byte[nSize];
if (ReadProcessMemory(hProcess, lpBaseAddress, data, nSize, 0) == 0)
{
//读取内存失败!
return “”;
}
String result = “”;
String TempString = Encoding.ASCII.GetString(data);
// \0
foreach (char item in TempString)
{
if (item == ‘\0’)
{
break;
}
result += item.ToString();
}
return result;
}

[DllImport(“Kernel32.dll”)]
//BOOL ReadProcessMemory(
// HANDLE hProcess,
// LPCVOID lpBaseAddress,
// LPVOID lpBuffer,
// SIZE_T nSize,
// SIZE_T* lpNumberOfBytesRead
//);
public static extern int ReadProcessMemory(
IntPtr hProcess, //正在读取内存的进程句柄。句柄必须具有PROCESS_VM_READ访问权限。
IntPtr lpBaseAddress, //指向要从中读取的指定进程中的基址的指针。在发生任何数据传输之前,系统会验证基本地址和指定大小的内存中的所有数据是否都可以进行读访问,如果无法访问,则该函数将失败。
byte[] lpBuffer, //指向缓冲区的指针,该缓冲区从指定进程的地址空间接收内容。
int nSize, //要从指定进程读取的字节数。
int lpNumberOfBytesRead //指向变量的指针,该变量接收传输到指定缓冲区的字节数。如果lpNumberOfBytesRead为NULL,则忽略该参数。
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
示例来源:
网易云课堂《2019 PC 微信探秘》

交流QQ群:
456197310
————————————————
版权声明:本文为CSDN博主「赵庆明老师」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013667796/article/details/90754802

PC微信hook学习笔记(一)—— 获取个人信息_数据库_qq_32886245的博客-CSDN博客

mikel阅读(2663)

来源: (1条消息)PC微信hook学习笔记(一)—— 获取个人信息_数据库_qq_32886245的博客-CSDN博客

PC微信hook学习笔记(一)—— 获取微信个人信息
1 起步
2. 获取基址
2.1 用CE查看个人信息
2.1.1 获取昵称基址
2.1.2 dll模块基址
2.2 用OD查看个人信息
2.3 内存查看命令
2.4 头像地址
3 地址偏移
3.1 用CE查看地址
3.2 指针
3.3 计算地址偏移量
3.3.1 其他信息地址
3.4 登录信息地址
这次跟着2019PC微信hook逆向分析课程学习下如何发送微信消息。
教程示例代码github地址:https://github.com/hedada-hc/pc_wechat_hook

本篇笔记学习教程:找微信个人数据基址
本篇笔记参考博客:PC微信逆向:使用CE+OD查找个人数据
博客大佬地址:请访问

1 起步
CE
CE修改器(Cheat Engine)是一款内存修改编辑工具,它允许你修改你的游戏,所以你将总是赢.它包括16进制编辑,反汇编程序,内存查找工具。与同类修改工具相比,它具有强大的反汇编功能,且自身附带了辅助工具制作工具,可以用它直接生成辅助工具。

OD
OD,是一个反汇编工具,又叫OllyDebug,一个新的动态追踪工具。OllyDbg是一种具有可视化界面的32位汇编分析调试器,是一个新的动态追踪工具,将IDA与SoftICE结合起来的思想,Ring3级调试器,非常容易上手,己代替SoftICE成为当今最为流行的调试解密工具了。同时还支持插件扩展功能,是目前最强大的调试工具。

下载
在吾爱破解的吾爱中下载OD(吾爱破解专用版Ollydbg.rar)
在西西软件园下载Cheat Engine 7.0.0中文版 绿色汉化版
安装
(1)解压缩吾爱破解专用版Ollydbg.rar,按照使用说明 .txt来操作即可。
(2)解压缩CE的zip文件
2. 获取基址
2.1 用CE查看个人信息
2.1.1 获取昵称基址
打开微信并登陆

打开Cheat Engine.exe

在CE中,点击【小电脑快捷图标】→【微信进程】,并在右侧扫描区输入登录的昵称并扫描数据。
(1)用昵称扫描一次,最好找到绿色的地址。绿色的地址一般是基址,红色是动态地址。
(2)找到后,双击绿色的地址,添加到下方的监控区域中。
(3)这一步没有显示绿色地址的话,用登录账号的省份试试,如:Jiangsu。

(4)右击添加好的地址条,选择【浏览相关内存区域】
(5)在弹框【内存浏览器】下方的地址信息里,可以上下滚动,能查看到登录微信的地址、手机号等信息。

双击监控区域的地址数据,并找个txt文本之类的记录绿色地址和弹框里的地址。如:65F472DC、WeChatWin.dll+13972DC——这两个就是昵称地址。

2.1.2 dll模块基址
在CE中添加刚才复制的地址:WeChatWin.dll,双击【描述】,并修改描述为:WeChatWin.dll基址;记录下WeChatWin.dll基址的地址:66BB0000。

WeChatWin.dll基址的地址:十六进制66BB0000, 被称为dll模块基址
微信昵称找到了,那么微信号、手机号等也不远了。但在CE用同样的方法扫描数据,却无法获取,我这里只扫描到个人地址信息:

Jiangsu:65F473C8,WeChatWin.dll+13973C8
Taizhou:65F473E0,WeChatWin.dll+13973E0
这时我们需要用到另一个工具:OD

2.2 用OD查看个人信息
OD基础操作的可以参考这个文章:OD 简介
大家也可以将鼠标放在字母快捷键上查看说明:

2.3 内存查看命令
-内存查看命令统一为d*格式: d[类型] [地址范围]

d代表Display,类型包括:字符、字符串、双字等。具体来说,d*命令共有这几种:d、 da、db、dc、dd、dD、df、dp、dq、du、dw、dW、dyb、dyd、ds、dS。

命令 格式
基本类型
dw 双字节WORD格式
dd 4字节DWORD格式
dq 8字节格式
df 4字节单精度浮点数格式
dD 8字节双精度浮点数格式
dw 指针大小格式,32位系统下4字节,64位系统下为8字节
dp 指针大小格式,32位系统下4字节,64位系统下为8字节
基本字符串
da ASCII字符串格式
du UNICODE字符串格式
db 字节 + ASCII字符串
dW 双字节WORD + ASCII字符串
dc 4字节DWORD + ASCII字符串
高级字符串
ds ANSI_STRING类型字符串格式
dS UNICODE_STRING类型字符串格式
2.4 头像地址
先来查找头像地址,打开吾爱破解[LCG].exe
附加微信进程
(1)【菜单】→【文件】→【附加】;
(2)点击【附加弹框】的标题部分,并输入WeChat;选中WeChat.exe,并点击【附加】。

(4)附加完之后,一定要点击下三角形的运行小按钮,否则微信无法显示窗口。

(5)在OD下方的命令输入框内输入:dc 昵称基址,上下滚动来查看相关信息。

(6)在OD下方的命令输入框内输入:dd 昵称基址,并往下翻一翻,找到头像url。单击头像url,右击→【复制】→【到剪贴板】,并在浏览器中打开头像地址。复制其中一个就够了。

3 地址偏移
3.1 用CE查看地址
我们在上一步复制的内容如下:65F475A4 0F590D18 ASCII “http://wx.qlogo.cn/mmhead/ver_1/BdevmwKtgCib16q8RKJLgJXnz8qla3nNg2T48yP1MexU2ibAJILSQSQAErXaNiaGKMnU”;

将头像url放到浏览器中查看,但现在看不了头像了。

首先,我们在CE里扫描头像url:http://wx.qlogo.cn/mmhead/ver_1/BdevmwKtgCib16q8RKJLgJXnz8qla3nNg2T48yP1MexU2ibAJILSQSQAErXaNiaGKMnU,但没有出现绿色的基址;
接着,点击【添加地址】,试一下刚才复制到的头像地址信息,如第一个:65F475A4和0F590D18;
结果,65F475A4显示乱码;而0F590D18是红色的,不是我们想要的绿色基址;

3.2 指针
刚才试一试的结果告诉我们:65F475A4和0F590D18都是地址。就是说,65F475A4这个地址里,放的是另一个地址;而另一个地址0F590D18里放的才是具体数据。这种地址里装了另一个地址的,就叫指针。如下表所示。

地址 数据
65F473C8 Jiangsu
65F473E0 Taizhou
65F472DC 你看花开啦
65F475A4 0F590D18
0F590D18 http://wx.qlogo.cn/mmhead/ver_1/BdevmwKtgCib16q8RKJLgJXnz8qla3nNg2T48yP1MexU2ibAJILSQSQAErXaNiaGKMnU
3.3 计算地址偏移量
利用模块基址+偏移量的方式,可以获取到数据基址。 我们可以用微信头像的指针地址减去模块基址就能得到微信的头像偏移,即微信头像的指针地址 – WeChatWin.dll模块基址 = 偏移量。
(原理待更新·····我现在还是有点不懂)

WeChatWin.dll基址的地址:64BB0000
打开【计算器】,选择【程序员】→【HEX】。
输入:65F475A4 – 64BB0000 ,得到 139 75A4,那么微信头像地址是:WeChatWin.dll+13975A4。

此时,我们可以用CE扫描到的城市地址( Taizhou:65F473E0,WeChatWin.dll+13973E0)来验证下:65F473E0 – 64BB0000 = 13973E0,证明计算方法是正确的。
3.3.1 其他信息地址
获取微信号地址:65F47264-64BB0000 = 1397264

获取手机号地址:65F47310 – 64BB0000 = 1397310
3.4 登录信息地址
将前面几个小节扫描到的和计算得到的地址都加到CE中,得到如下地址列表。
指针地址很明显,其数值里是另一个地址,而另一个地址里,装着我们要的数据。

————————————————
版权声明:本文为CSDN博主「qq_32886245」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_32886245/article/details/103711543