Ganlv 发表于 2018-5-19 17:35

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 个样本,以及使用到的代码



3448797081 发表于 2018-5-23 22:02

https://pan.baidu.com/s/1Rfm96a_nfIdDGFmBcMvxOw

洛丹伦的夏天 发表于 2018-5-20 10:11

我发现,这样穷举密钥找关键字会有问题,如果作者是几层加密,里层故意放部分不加密代码,这样子会有问题。总之还是有key源码来的快,直接return 后面的值赋给一个字符串然后写入到其他文件里

dk2008 发表于 2018-5-20 20:23

回复看隐藏

mmEXP 发表于 2018-5-19 17:39

先占位,在慢看!{:1_918:}

xuwenyi 发表于 2018-5-19 17:55

先占位,在慢看!

tzzhj1 发表于 2018-5-19 18:05

躺下慢慢看

骚碟。 发表于 2018-5-19 18:08

这个不错!!支持

kingwl 发表于 2018-5-19 18:19

        感谢发布原创作品,吾爱破解论坛因你更精彩!

clf3211147 发表于 2018-5-19 18:37

看不懂lua,感谢分享,支持一下

吞茶嚼花 发表于 2018-5-19 19:13

是噢……256取余数……范围只有0-256 我给忘了。。

w5645060 发表于 2018-5-19 19:17

17602053348 发表于 2018-5-19 19:19

用一句话总结一下上面我们分析得到
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: lua脚本解密6:YIC加密