lua脚本解密6:YIC加密
本帖最后由 Ganlv 于 2018-5-22 15:56 编辑## 系列教程
* (https://www.52pojie.cn/thread-694364-1-1.html)
* (https://www.52pojie.cn/thread-697540-1-1.html)
* (https://www.52pojie.cn/thread-705854-1-1.html)
* (https://www.52pojie.cn/thread-723324-1-1.html)
* (https://www.52pojie.cn/thread-737347-1-1.html)
* (https://www.52pojie.cn/thread-741467-1-1.html)
## 样本
两个求破贴
样本 1 来源:<https://www.52pojie.cn/forum.php?mod=redirect&goto=findpost&ptid=737347&pid=20098089>
下载地址:<https://pan.baidu.com/s/1tKLaRdyQpz6ORmYCLZ48Aw>
```lua
--YIC新加密算法
print('加密由一炮、&@yic77455提供。\n如未能进入,可能密码错误!\n'..'加群免费获取')
function Yicjm(key,code)
return (code:gsub('..', function (h) return string.char((tonumber(h,16)+256-13 - key + 999999*256)%256) end))
end
local v_value=gg.prompt({'加群免费获取'},{=0},{='number'})
gg.toast('正在加载脚本……')
pcall(Yicjm(v_value, '1B1D14191FD3D2FCFCDEDEE0DBE1DCE4......'))
```
样本 2 来源:<https://www.52pojie.cn/forum.php?mod=redirect&goto=findpost&ptid=737347&pid=20161673>
下载地址:<https://www.lanzouj.com/i11pd4j>
```lua
--YIC新加密算法
goto aa
::aa::
gg.clearResults()
gg.searchNumber('1;1', gg.TYPE_AUTO, false, gg.SIGN_EQUAL, 0, -1)
gg.getResults(2000)
gg.toast('过保护已开启')
print('加密由饭、&@yic77455提供。\n如未能进入,可能密码错误!\n'..'友情更新刺激战场5.12最新版')
function Yicjm(key,code)
return (code:gsub('..', function (h) return string.char((tonumber(h,16)+256-13 - key + 999999*256)%256) end))
end
local v_value=gg.prompt({'友情更新刺激战场5.12最新版'},{=0},{='number'})
gg.toast('正在加载脚本……')
pcall(Yicjm(v_value, 'B3C2BBB0C1B6BCBB6DB0B5B2B0B8A1B6......'))
```
这两个样本都可以“YIC加密”的字样。
## 分析
看上去这个关键代码 `string.char((tonumber(h,16)+256-13 - key + 999999*256)%256)` 的解码算法不是特别麻烦,但是还是和 (https://www.52pojie.cn/thread-737347-1-1.html) 一样,没有秘钥是解不了的,必须得穷举。
因为这个解码算法比较简单,所以我们仔细分析一下。
> 如果你真的想学,你最好先去简单的学一下 Lua 语言。
>
> 这里假设你应该是可以看出哪段代码是我们解密的关键之处,哪段代码与解密无关。
>
> 我尽量用最通俗的语言解释。
>
> 碰到 Lua 相关语法,我会默认你已知(请自行去学习 Lua 的相关知识,自行去查阅手册)。
>
> 这个算法并不需要很高的数学水平,只要会算术就可以理解,小学生只要会加减乘除求余数就可以理解。
### 代码概况
关键代码如下:
```lua
function Yicjm(key,code)
return (code:gsub('..', function (h) return string.char((tonumber(h,16)+256-13 - key + 999999*256)%256) end))
end
local v_value=gg.prompt({'友情更新刺激战场5.12最新版'},{=0},{='number'})
gg.toast('正在加载脚本……')
pcall(Yicjm(v_value, 'B3C2BBB0C1B6BCBB6DB0B5B2B0B8A1B6......'))
```
这段代码的意思大概就是
1. 定义一个解密函数 `Yicjm`
2. gg修改器读取一个数值存到 `v_value`
3. 提示正在加载脚本
4. 然后用你刚才输入的那个数值 `v_value` 作为 `Yicjm` 函数的 `key` 参数来解密 `'B3C2BBB0C1B6BCBB6DB0B5B2B0B8A1B6......'` 这个字符串,得到真正的代码,然后执行。
### 解码函数
接下来我们来分析这个 `Yicjm` 函数。
这里的 `key` 就是秘钥了,`code` 是一个字符串,`code` 的形式就是一个十六进制字符串表示的数据。
然后我们就要分析这个算法了。
```lua
code:gsub('..', function (h) return string.char((tonumber(h,16)+256-13 - key + 999999*256)%256) end)
```
`code:gsub` 中的冒号(`:`)是 Lua 面向对象编程的一个语法糖,因为 `code` 是一个字符串(`string`)类型的变量,所以这句话相当于
```lua
string.gsub(code, '..', return string.char((tonumber(h,16)+256-13 - key + 999999*256)%256) end)
```
我们去查一下 `gsub` 的用法
<https://www.lua.org/manual/5.2/manual.html#pdf-string.gsub>
**`string.gsub (s, pattern, repl [, n])`**
Returns a copy of `s` in which all (or the first `n`, if given) occurrences of the `pattern` have been replaced by a replacement string specified by `repl`, which can be a string, a table, or a function. `gsub` also returns, as its second value, the total number of matches that occurred. The name `gsub` comes from *Global SUBstitution*.
If `repl` is a string, then its value is used for replacement. The character `%` works as an escape character: any sequence in `repl` of the form `%d`, with *d* between 1 and 9, stands for the value of the *d*-th captured substring. The sequence `%0` stands for the whole match. The sequence `%%` stands for a single `%`.
If `repl` is a table, then the table is queried for every match, using the first capture as the key.
If `repl` is a function, then this function is called every time a match occurs, with all captured substrings passed as arguments, in order.
In any case, if the pattern specifies no captures, then it behaves as if the whole pattern was inside a capture.
If the value returned by the table query or by the function call is a string or a number, then it is used as the replacement string; otherwise, if it is **false** or **nil**, then there is no replacement (that is, the original match is kept in the string).
Here are some examples:
x = string.gsub("hello world", "(%w+)", "%1 %1")
--> x="hello hello world world"
x = string.gsub("hello world", "%w+", "%0 %0", 1)
--> x="hello hello world"
x = string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1")
--> x="world hello Lua from"
x = string.gsub("home = $HOME, user = $USER", "%$(%w+)", os.getenv)
--> x="home = /home/roberto, user = roberto"
x = string.gsub("4+5 = $return 4+5$", "%$(.-)%$", function (s)
return load(s)()
end)
--> x="4+5 = 9"
local t = {name="lua", version="5.2"}
x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t)
--> x="lua-5.2.tar.gz"
这个解码函数使用的是 “`repl` is a function” 这种用法,每次匹配到样式,都会调用后面的函数,然后替换成返回值。
> 这里我把原文复制过来不是为了凑字数,而是为了说明如何查阅官方文档。
至于 Lua 的字符串匹配语法请自行参考官方手册 <https://www.lua.org/manual/5.2/manual.html#6.4.1>
这里的匹配样式就是 `'..'`,两个点就是两个字符,就是两个十六进制数,或者说就是 1 个字节的十六进制表示。
### 替换 callback 算法分析
```lua
function (h)
return string.char((tonumber(h,16)+256-13 - key + 999999*256)%256)
end
```
这里的 `h` 就是一个 2 个字符的字符串,比如 `'B3'`、`'C2'` 等等(以样本 2 的加密部分 `'B3C2BBB0C1B6BCBB6DB0B5B2B0B8A1B6......'` 为例)。
`tonumber(h,16)` 这个函数就是把字符串以 16 进制的形式转换为数字,`'B3'` 会被转换成为 `179`,我们暂时把这个数值记为 `b`(代表 byte)。
然后最关键的部分来了,看这段算法:
```lua
(b + 256 - 13 - key + 999999 * 256) % 256
```
这是一个求余数的运算。首先,它似乎等于(这个应该能看懂吧):
```lua
(b - 13 - key + 1000000 * 256) % 256
```
根据同余定理,它等于:
```lua
(b - 13 - key) % 256
```
> 如果你不懂的话,简单的说明一下:`3 % 7 = 3, 10 % 7 = 3, 17 % 7 = 3`。
>
> 发现什么规律了吗?如果 `x % 256 = y`,那么 `(x + 1000000 * 256) % 256 = y` 也成立。
因上面的式子是一个求除以 256 的余数,那么 `key` 的穷举范围只需要取 0 ~ 255 就够了,即使你想将 `key` 取在更大的范围,比如 `key` 取为 257,结果一定和 `key` 取 1 的时候是一样的。
同时我们也知道了,式子的结果也一定是 0 ~ 255 的范围,这个结果恰好被 `string.char` 转换成为字符。
## 解码
所以,请用一句话总结一下上面我们分析得到的结论是什么?
**** Hidden Message *****
然后我们就写一个穷举的代码
```lua
function Yicjm(key,code)
return (code:gsub('..', function (h) return string.char((tonumber(h,16)+256-13 - key + 999999*256)%256) end))
end
for key = 0, 255 do
print(key)
local code = Yicjm(key, 'B3C2BBB0C1B6BCBB6DB0B5B2B0B8A1B6')
if (string.find(code, "gg")
and string.find(code, "if")
and string.find(code, "then")
and string.find(code, "end")) then
print(key .. " may be the key.")
file = io.open("2.lua", "w")
file:write(code)
file:close()
break
end
end
```
这里就是检测了解码中的代码中是否包含 `gg`、`if`、`then`、`end` 这类特殊关键字,如果包含则解码成功了,输出到 `2.lua`。
## 成果
样本 1 成果
样本 2 成果
## 总结
秘钥量太小,轻易就能穷举出来,总之这个加密是非常不负责任的。
## 样本 3
[样本 3 求破贴](https://www.52pojie.cn/forum.php?mod=redirect&goto=findpost&ptid=741467&pid=20215312)
我下载看了一下,的确按照我的方法是无法找到秘钥的,为什么呢?
原因很简单,因为解密之后的代码不包含 `gg`、`if`、`then`、`end` 这类特殊关键字。
如果你尝试判断是否包括 `load` 关键字,那么你可以很轻松的得到秘钥。
```lua
for key = 0, 255 do
print(key)
local code = Yicjm(key, "4E4E4E71719D8D8729CEE429F3CA2BF2")
if (string.find(code, 'load')) then
print(key .. " YIC decode OK.")
file = io.open("2.lua", "w")
file:write(code)
file:close()
break
end
end
```
> 其实是在不行你就挨个试嘛...
那么有没有什么更好的方法呢?
既然读取脚本用的是 `load` 函数,这个函数在读取字符串成功时会返回读取结果(也就是一个函数),失败时则返回 `nil` 和错误信息,那么我们可以用这个函数来判断,解密之后的函数能否被读取运行。
```lua
for key = 0, 255 do
print(key)
local code = Yicjm(key, "4E4E4E71719D8D8729CEE429F3CA2BF2")
local func, err = load(code)
if (func) then
print(key .. " YIC decode OK.")
file = io.open("2.lua", "w")
file:write(code)
file:close()
break
end
end
```
这回可就是“为所欲为”了,即使出现 `precompiled chunk` 也不怕了,`load` 函数会自动处理的。
**注意:对于 lua 5.1 请使用 `loadstring` 函数**
### 样本 3 成果
这只是解密出的第 1 层,样本 3 的这个作者硬是用同样一个算法加密了 7 层,中间每层还把密码密码给出来了。
> 作者似乎不知道“同余”这种东西,`93987` 和 `93987 % 256`(也就是 `35`) 得到的结果是一样的,根本用不着那么长的密码。
> 能破的人不在乎多几层,破不了的人一层加密就足够了,真有意思。
## 相关链接
* (https://sourceforge.net/projects/luabinaries/files/5.2.4/Tools%20Executables/lua-5.2.4_Win32_bin.zip/download)
* [样本 1 求破贴](ttps://www.52pojie.cn/forum.php?mod=redirect&goto=findpost&ptid=737347&pid=20098089)
* [样本 1 下载地址](https://pan.baidu.com/s/1tKLaRdyQpz6ORmYCLZ48Aw)
* [样本 2 求破贴](https://www.52pojie.cn/forum.php?mod=redirect&goto=findpost&ptid=737347&pid=20161673)
* [样本 2 下载地址](https://www.lanzouj.com/i11pd4j)
* [样本 3 求破贴](https://www.52pojie.cn/forum.php?mod=redirect&goto=findpost&ptid=741467&pid=20215312)
* (https://www.lua.org/manual/5.2/manual.html#pdf-string.gsub)
* (https://www.lua.org/manual/5.2/manual.html#pdf-load)
* (https://www.lua.org/manual/5.2/manual.html#6.4.1)
## 附件
3 个样本,以及使用到的代码
https://pan.baidu.com/s/1Rfm96a_nfIdDGFmBcMvxOw 我发现,这样穷举密钥找关键字会有问题,如果作者是几层加密,里层故意放部分不加密代码,这样子会有问题。总之还是有key源码来的快,直接return 后面的值赋给一个字符串然后写入到其他文件里 回复看隐藏 先占位,在慢看!{:1_918:} 先占位,在慢看! 躺下慢慢看 这个不错!!支持 感谢发布原创作品,吾爱破解论坛因你更精彩! 看不懂lua,感谢分享,支持一下 是噢……256取余数……范围只有0-256 我给忘了。。 用一句话总结一下上面我们分析得到