揭露骗子利用微信“聊天记录中图片不可变”的骗局

一、起因

那是一个阴冷的夜晚,我的一个老乡怒气冲冲的给我拨了个微信视频,说他在微信上被人骗了。

他给我转发了一条骗子给他发的聊天记录,在点开之前是这样的:

图1 聊天记录

看上去是一个图片。点进去一看,果然是个图片。里面写了 4个数字。

从图上的“错一罚十”来看,貌似是某些“大神”给人推荐的一些“神秘数字”。

值得注意的一个细节是,当我点开菜单想用浏览器去打开这个图片的时候,发现很多菜单都被隐藏了。

图2 图片

老乡怒吼到:“奶奶的,老子前天找人花了一万块钱买的这个买马的推荐单,结果今天看到的数字,跟我昨天看到的数字完全不一样!”

这么一说我就明白了。原来骗子发了个图片来推荐受害人买马,里面手写了几个数字号称“预测绝对准确”。

然而,结果出来后,骗子又第一时间把图片改掉了,从而实现“绝对正确”。

我在心里说道“。。。卧槽,这你都能信”,嘴里说道:“。。这个在原理上当然可以实现啊,图片换掉了而已。”

老乡囔囔说:“现在这骗子说,这个是前天发出来的,聊天记录有时间作证,改不了的!他说除非我能发一个这样的记录给他,如果确实能够随便变图片,他就把钱还给我。”

我叹了口气,声色俱厉的教育了他,世界上没有未卜先知的大神。

老乡唯唯诺诺道:“好的我知道了!你才是大神,快给我做一个这样的记录出来!”

二、原理

首先可以确认的一点是,图1这种聊天记录,普通微信用户是做不出来的。

因为他既不是文字,又不是图片,他是微信公众号给用户发送的文章(Article)。

而骗子为了蒙骗不懂技术的老百姓,让人认为这就是个图片,在这个文章上做了几个细节:

1、聊天窗口上显示的文章卡片既没有标题,也没有链接,只有一个图片的缩略图,在心理上给人一种暗示,这就是一个图片。

2、文章点开后,是一张大图铺满屏幕,菜单里面没有“使用浏览器打开”、“复制链接”之类的菜单,因此很难让人想到这是一个网页。

因此,要复现这个骗局,需要:

1、有一个自己的通过认证的公众号。

2、使用该公众号给受害者推送一个没有标题、链接,只有缩略图的文章。

3、文章里的链接指向自己服务器的一个网页,网页域名要备案,网页内的图片要能变化

4、这个网页隐藏了微信菜单,如“使用浏览器打开”等。

现在基本原理已经了解了,就可以尝试复现这个骗局了。

三、技术点

骗子敢信誓旦旦的说有本事你弄一个给我看,底气在于对普通百姓而言,要复现这一套骗术还是相当有技术门槛和成本的。

下面一个一个讲解。

1、已认证的公众号

公众号如果不通过认证,在调用接口来隐藏微信菜单的时候会有局限性(由于我已经认证了,这一点没有亲自证实。)

认证需要的最简单的材料也需要个体工商户营业执照、300元的认证费用。

这部分就是费时费力费钱,跟技术关系不大。

2、给用户推送特殊的文章。

公众号给用户推送的所有信息都是用xml来描述的。可查阅微信官方文档

不过值得吐槽的是,这个官方文档恰恰漏了推送文章的xml描述方式:

    <xml>
    <ToUserName><![CDATA[{target}]]></ToUserName>
    <FromUserName><![CDATA[{source}]]></FromUserName>
    <CreateTime>{time}</CreateTime>
    <MsgType><![CDATA[news]]></MsgType>
    <Content><![CDATA[{content}]]></Content>
    <ArticleCount>{count}</ArticleCount>
    <Articles>{items}</Articles>
    </xml>

可以发现<ArticleCount>定义了一个推送里有几篇文章,每一篇文章可以组装到<Articles>中,每个文章的描述如下:

    <item>
    <Title><![CDATA[{title}]]></Title>
    <Description><![CDATA[{description}]]></Description>
    <PicUrl><![CDATA[{img}]]></PicUrl>
    <Url><![CDATA[{url}]]></Url>
    </item>

因此,只需要把Title、Description都置空,把PicUrl设置成骗子要显示的缩略图,Url设置为骗人图片的网页链接,把文章发送给用户就完成了。

3、网页显示图片,且图片可变

这里要解决的一个问题是:如果用户已经打开过这个网页,那么手机里必然有缓存。那么即使你在服务器上换掉了图片,用户还是能看到之前的图片。

解决图片缓存的思路很多。如果不想改变图片文件名,一种思路是给图片加上随机的后缀,如 1.png 改成 1.png?123456

这样,骗子只需要在适当的时候后台把1.png换掉即可。

另外一种思路是把正确的结果做成另外一个名字的图片,使用代码,设计成某种条件下切换要展示的图片文件名。

为方便演示,我准备了5张png图片,随机生成文件名来展示。JS代码如下:

<script type="text/javascript" src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.0.js">
</script>    
<script type="text/javascript">
        // 随机选择5个图片
      $(function(){
        var randName = Math.floor(Math.random() * 65535) % 5
        $('.img_randPng').each(function(){
          this.src = this.src + randName + '.png';
                console.log(this.src)
        });
      });
</script>

然后在img标签里做修改即可:

<img src='https://********' class='img_randPng' />

4、隐藏微信菜单

最后一步是隐藏微信自带浏览器的部分菜单。这就要使用JS-SDK来实现了。

而麻烦的地方在于:所有需要使用JS-SDK的页面必须先注入配置信息,否则将无法调用:

<script src='https://res.wx.qq.com/open/js/jweixin-1.6.0.js'>
</script>

wx.config({
  debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
  appId: '', // 必填,公众号的唯一标识
  timestamp: , // 必填,生成签名的时间戳
  nonceStr: '', // 必填,生成签名的随机串
  signature: '',// 必填,签名
  jsApiList: ['hideAllNonBaseMenuItem'] // 必填,需要使用的JS接口列表
});

wx.ready(function(){
          // 必须要放在这里才会生效
          wx.hideAllNonBaseMenuItem();
});

其中你需要调用JS的哪些api,都必须写到jsApiList中进行注册。

例如这里调用了'hideAllNonBaseMenuItem'接口来隐藏所有非必须的菜单。这个api必须在wx.read中调用。

上述配置信息注入最麻烦的地方在于需要签名,而签名需要向微信服务器请求获得jsapi_ticket,而jsapi_ticket又需要请求获得access_token。

官方文档中《获取access_token》说的足够详细了。https请求如下,要带上自己的appid和secret:

https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

access_token拿到之后再请求一个jsapi_ticket。https请求如下,要带上上面获得的access_token:

https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi

终于拿到jsapi_ticket,接下来就是签名算法了。可参阅《JS-SDK使用权限签名算法》

Python代码如下:

# 当前访问的页面的完整URL
        url = current_url
        parameters = {
            "noncestr" : noncestr,
            "jsapi_ticket" : self.jsapi_ticket,
            "timestamp" : timestamp,
            "url" : current_url 
        }
        # 对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串
        unsinged_str = '&'.join(['{}={}'.format(key.lower(), parameters[key]) for key in sorted(parameters)])
        # 进行sha1签名,得到signature
        signedstr = hashlib.sha1(unsinged_str.encode("utf-8")).hexdigest()

noncestr为数字字母构成的随机数。

timestamp为时间戳,必须和请求jsapi_ticket的时间戳保持一致。

self.jsapi_ticket为上文获取到的jsapi_ticket。

url为准备调用JS-SDK接口的网页的url。

timestamp和noncestr随机数生成代码如下:

timestamp = int(time.time())
noncestr  = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(15))

将上述noncestr、timestamp、signedstr、appid插入到你的html模板中即可。

四、效果

最后展示下效果。每次打开,图片中第一个推荐的数字都会随机变化。

我把这条消息转给了老乡,说:“拿好,去干骗子吧。”

 

对了,这里漏了一点,为什么这个页面要备案?

因为如果不备案的话,这个网页打开一般都是这种结果:

这种情况当然你可以点申请恢复访问,但是申请的时候官方也要看你是否备案。。。

五、后续

几天后。

“怎么样,骗子退钱了没。”我问老乡。

老乡眉飞色舞的说:“退了退了,对了,这骗子还问我要你的微信,说他有一些全新的业务可以和你开展合作!

我:“。。。。。。”

  • 13
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
由于QQ聊天记录是需要登录QQ账号才能查看的,因此需要使用QQ的API或者模拟登录的方式来实现爬取聊天记录的功能。以下是使用模拟登录的方式实现的Python代码: ```python import requests from bs4 import BeautifulSoup # 登录QQ账号,并获取cookie def login_qq(username, password): login_url = 'https://xui.ptlogin2.qq.com/cgi-bin/xlogin' headers = { 'Referer': 'https://xui.ptlogin2.qq.com/cgi-bin/xlogin?appid=715030901&daid=73&pt_no_auth=1&s_url=https%3A%2F%2Fid.qq.com%2Findex.html', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36' } data = { 'u': username, 'p': password, 'verifycode': '', 'webqq_type': '40', 'remember_uin': '1', 'login2qq': '1', 'aid': '715030901', 'u1': 'https://id.qq.com/index.html', 'ptredirect': '0', 'h': '1', 'ptlang': '2052', 'daid': '73', 'from_ui': '1', 'pttype': '1', 'dumy': '', 'fp': 'loginerroralert', 'action': '0-35-1495675005110', 'mibao_css': 'm_webqq', 't': '1', 'g': '1', 'js_type': '0', 'js_ver': '10231', 'login_sig': '', 'pt_rsa': '0', 'pt_qzone_sig': '0', 'pt_vcode_v1': '0', 'pt_verifysession_v1': '', 'pt_randsalt': '2', 'pt_jstoken': '2040674265' } session = requests.Session() response = session.post(login_url, headers=headers, data=data) return session.cookies.get_dict() # 获取QQ聊天记录 def get_qq_chat_history(qq_number, cookie): chat_url = 'https://user.qzone.qq.com/proxy/domain/r.qzone.qq.com/cgi-bin/tfriend/qqchat_list_v2' headers = { 'Referer': 'https://user.qzone.qq.com/{}/infocenter'.format(qq_number), 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36', 'Cookie': 'uin=o{}; skey={}'.format(qq_number, cookie['skey']) } params = { 'uin': qq_number, 'start': 0, 'num': 10, 'cgi_host': 'http://m.qzone.qq.com/cgi-bin', 'callback': 'handleTList', 't': '0.4908397081636454', 'g_tk': '5381' } session = requests.Session() response = session.get(chat_url, headers=headers, params=params) soup = BeautifulSoup(response.text, 'html.parser') chats = soup.find_all('li', class_='list_item') for chat in chats: sender = chat.find('a', class_='sender').text.strip() content = chat.find('div', class_='content').text.strip() print('{}: {}'.format(sender, content)) # 测试代码 if __name__ == '__main__': qq_number = '123456789' # QQ号码 username = 'your_qq_username' # 登录QQ账号 password = 'your_qq_password' # 登录QQ密码 cookie = login_qq(username, password) # 登录QQ账号,并获取cookie get_qq_chat_history(qq_number, cookie) # 获取QQ聊天记录 ``` 上述代码,`login_qq`函数用于模拟登录QQ账号,并获取cookie信息;`get_qq_chat_history`函数用于获取QQ聊天记录。通过调用这两个函数,即可实现获取QQ聊天记录的功能。需要注意的是,QQ聊天记录的获取需要登录QQ账号,因此需要提供正确的QQ账号和密码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值