DeepL Api设计中的欺骗战术

作者: sobyte
来源: https://www.sobyte.net/post/2022-04/deepl-api/
时间: 2022-04-06

这篇博文本应在去年我完成反转DeepL 客户端时发表,但我迟迟没有开始,因为我担心相关细节一旦公开,就会被其他人广泛采用,而被DeepL 官方封锁。前段时间,我发布了Free Api的DeepL Docker镜像,还在GitHub上公开了二进制文件。我相信DeepL 很快就会投入使用,所以我想现在是时候公开细节了。

我逆向的是DeepL 的Windows客户端,因为它是由C#依赖.net开发的,没有混淆和加壳,所以我很容易逆向源代码。通过前段时间与其他一些研究人员的交流,我觉得已经有很多感兴趣的人也在进行逆向,也许是有默契,都怕DeepL 中发现修改,所以我们没有对外开放,目前网络上搜索不到任何相关内容。这篇文章的目的是给相关的伙伴们一点思考,但希望大家还是不要直接公开代码,继续欺骗DeepL,相信还没有人发现他们的把戏。

在我对DeepL Free Api的实现中,我发现DeepL并没有像我之前看到的一些接口设计那样通过签名等手段来避免接口滥用,相反,他们使用一些欺骗性的手段来混淆视听,从而试图让抓包的分析师放弃,本文将围绕这一点进行讨论。

过程

进入研究生阶段后,为了方便阅读论文,我为自己开发了一个划痕翻译工具,在众多翻译引擎中,DeepL,效果特别好。DeepL 的官方 Api 需要绑定信用卡进行认证,但它不在中国大陆经营业务,所以不支持国内信用卡。我也试过买别人的账户用国外的信用卡认证,但费用很高,而且没有滥用,DeepL,两个月内就把我的账户封了,所以我决定用其他的方法。

考虑到DeepL,提供支持网络的免费版翻译服务,而且Windows、安卓和iOS都有相应的客户端,我想我将使用这些客户端使用的免费界面。毫不奇怪,随着打包和混淆技术的广泛使用,DeepL 的网页端js代码并不是人类可以阅读的,但通过简单抓取包,我发现它的界面参数非常清晰,而且完全没有额外的认证技术,比如签名和令牌。我想我应该可以轻松搞定,也许几行Python代码就可以完成界面对接工作。

但经过测试,我发现在修改翻译内容时,很有可能遇到429太多请求,一旦出现429,以后的请求都会是429。

{
    "jsonrpc": "2.0",
    "error":{
        "code":1042902,
        "message":"Too many requests."
    }
}

在搜索了GitHub之后,我发现之前已经有人尝试利用免费接口DeepL,他们早在2018年就遇到了这个429问题,直到现在还没有解决。

我尝试转向客户端的免费接口,这在苹果设备上很容易被MITM,所以我在iPad上的DeepL 客户端上抓了一个数据包。令我惊讶的是,客户端的请求出乎意料地比网页端简单,接口参数的数量也只有规定的几个,这对利用非常有利。我再次认为我可以轻松地处理它。

经过一个简单的测试,我又傻眼了。伪造的请求显然与客户发起的请求相同,但只要我替换了翻译的内容,返回就立即变成了429。 他妈的!我开始怀疑自己。我开始怀疑自己了。

{
    "jsonrpc": "2.0",
    "method": "LMT_handle_texts",
    "params": {
        "texts": [{
            "text": "translate this, my friend"
        }],
        "lang": {
            "target_lang": "ZH",
            "source_lang_user_selected": "EN",
        },
        "timestamp": 1648877491942
    },
    "id": 12345,
}

你自己看,这个界面很清楚,但怎么就不能伪造呢?

我想了又想,可疑的是id,因为我不知道这个参数是怎么产生的,是随机的还是按照某些规则计算的,我们无从得知。但从目前的结果来看,似乎这个随机id并没有被服务器识别。

当然,我也考虑过其他服务器端判断滥用的方法,比如某些http头、ssl级别的方法(比如以前Go实现的SSL协商过程中加密算法的顺序,等等)。我也曾尝试过伪造,但就是不成功。我当时很累,不想再搞了。

第二天,我突然想起他的Windows客户端,惊讶地发现它是C#的,还没有加壳,于是决定用dnSpy来分析,结果发现没有混乱。经过分析,一切都清楚了,原来DeepL,只不过是想让你觉得自己可以做到。

看一下界面的参数,我认为我可以轻松处理的原因是这个界面它是如此简单。它不像有些公司那样使用缩写,但是这里的每一个参数,它的名字都告诉我它的意思,它的作用以及它是如何产生的。

jsonrpc是版本号,method是方法,一个固定的字符串。params有文本,是要翻译的多段文本,lang有翻译的语言选项,是枚举类型。timestamp是UNIX风格的时间戳,id是序列号。一眼望去,其中只有id最可疑,这的确是我最初犯的错误。

真相

现在我将向你展示DeepL 实际上是如何进行认证的。(以下不是DeepL 客户端的代码,是我为利用Rust而写的代码,但逻辑是一样的)。

fn gen_fake_timestamp(texts: &Vec<String>) -> u128 {
    let ts = tool::get_epoch_ms();
    let i_count = texts
            .iter()
            .fold(
                1, 
                |s, t| s + t.text.matches('i').count()
            ) as u128;
    ts - ts % i_count + i_count
}

哈哈! 没想到吧! 它的时间戳是不真实的!

DeepL 首先计算所有是文本中的数字,然后对真实的时间戳ts - ts % i_count + i_count进行一个小操作,这几乎只改变了时间戳的毫秒。如果由人眼来验证,这种变化甚至不会被注意到,它似乎是一个普通的时间戳,不会关心毫秒之差。

但DeepL,得到这个修改后的时间戳,并能与真实时间(毫秒误差)进行比较,还能通过一个简单的操作(是否是i_count的整数倍)确定它是否是一个伪造的请求。这真是太棒了!

还有更精彩的呢! 继续阅读。

let req = req.replace(
    "\"method\":\"",
    if (self.id + 3) % 13 == 0 || (self.id + 5) % 29 == 0 {
        "\"method\" : \""
    } else {
        "\"method\": \""
    },
);

怎么说呢?我想我从一开始就被骗了,他们的id是一个纯粹的随机数,只是后续的请求会在第一个随机id的基础上加一个,但这个id也决定了文本中一个微不足道的小空格。

按照正常的思路,为了方便人的阅读和分析,我第一次拿到请求时,会先把它放到编辑器里,把Json格式化,哪想到这正好破坏了deeple用于认证的功能,所以无论我怎么努力都很难找到。

总结

根据我以往的经验,接口防滥用,要么是用户专用的令牌,要么是签名或加密的请求。这些防滥用的方法都是显性的,就是明确告诉你,我有一个签名,怎么签名,你去分析一下,但是我代码混乱,你慢慢折腾吧。

还是高级点,技术性更强,利用某些客户端特有的实现方式引起的认证特点,我印象最深的是Go的SSL协商过程的算法序列。这类方法需要更高的技术水平,分析起来当然也更难,找到这样的方法本身就不容易。

从DeepL 的方法中,我发现了另一种思维方式。利用人的心理弱点,让人一开始感觉非常容易,但无论如何也得不到想要的结果,给分析者造成心理上的冲击和自我怀疑,使人容易放弃。同时,它利用了人的行为惯性,使其自行破坏某些关键信息,从而给分析工作带来难以发现的障碍。

有趣的是,除了技术之外,还有这样一条路!这也是我们的优势。

posted on 2023-03-08 12:29  euclov  阅读(585)  评论(0)    收藏  举报

导航