反向工程亚马逊的Whispersync

2020-10-15 22:36:42

我是我的Kindle的超级粉丝。这是我要带到沙漠地区的设备。我也相当频繁地使用突出显示功能,在该功能中,您可以选择一些您喜欢的文本并将其保存。不过,有一件事困扰着我:Kindle上没有开放的API来检索关于我的数据。亚马逊在Kindle上提供My-Clippings.txt文件和“通过电子邮件导出我的注释”功能,但这两个功能都不能在后台自动完成,也不提供最后阅读位置。我想要摩尔!

找回我最后一次阅读的位置,看看我生命中哪些时期我倾向于阅读最多(阅读较少将是一个很好的压力时期的指示器)。

为了将数据放入我自己的个人空间:我的舒适(我在那里工作)。例如,我可以在我的舒适之家随机显示我最喜欢的语录,或者有一个“书籍”应用程序,上面有一个公共页面,上面有我所有的书籍。那将是一个相当于书架的数字设备。书架是分享你读过的好书的一种方式,随着Kindle的消失。我希望我能很容易地和我的朋友们分享我的阅读清单。Goodreads.com是实现这一目标的另一种方式,但我更愿意让这些数据随时可用。

可能性是无穷无尽的…。但首先,我需要访问这些数据。很久以前,我试图通过kindle网站访问这些数据,但无法通过抓取的方式通过登录屏幕:如果我记得的话,在应用程序端有凭证加密,但我没有成功地重做。

最近,我看了一个关于使用Mitmproxy发现应用程序使用的HTTPAPI的演示文稿。这给了我重新开始这个项目的动力:这一次,我会试着理解Kindleca是如何与亚马逊沟通的,并试图模仿同样的HTTP请求来访问我的数据,而不是刮网页。

免责声明:不要试图对非您自己的设备或帐户进行中间人攻击。要成功执行此项目,您需要物理访问设备以安装SSL证书颁发机构。

要查看安卓设备和基于HTTPAPI的远程API之间的通信流程,事情要比从Web浏览器更复杂一些:在Web世界中,我们可以访问网络检查器,它可以帮助我们记录数据、重放调用、查看请求和响应等…。Android或iOS没有这样的东西。解决方案是对我们的设备进行中间人攻击:代理并记录通过受控接入点的所有流量。

通过接入点的每个数据包重定向到Mitmproxy(以透明模式方式)

多亏了安装在Whispersync客户端设备上的自定义CA授权,HTTPS中间人成为可能。

安装在Android设备上的Kindle for Android(Play商店可以安装在GenyMotion上)。

ℹ️也可以使用ios,但访问虚拟安卓设备会更容易,因为你不必拥有一台mac。

ℹ️在安卓上,在安卓7Nougat之前都可以使用用户证书。在Android 7 Nougat之后,应用程序将不会默认使用用户证书。更多信息请点击此处。

在完成所有这些设置之后(有点长而乏味,但并不难),记录Kindle应用程序和亚马逊之间的流量的HTTP转储是可能的。

Ssh [email protected] a-t 0#启动mitmdump并写入对outfilemitmdump的请求/响应--mode透明-w outfile#使用Kindle应用程序生成流量#ctrl-c停止代理退出#。

Mitmproxy使用自定义格式存储转储。它的互操作性不是很强:从转储中查询和提取数据有点困难。HAR是标准格式,例如在Chrome和Firefox中使用(Network Inspector Tabin两种浏览器都可以导入/导出HAR格式)。底层的HAR格式是JSON,这使得它具有可读性,并且可以与jq之类的工具互操作,这些工具对于过滤/查询非常方便。

Git clone mitmproxy#要访问HAR转换脚本,请安装mitmproxy#以使用mitmdump命令#从Raspberry Pirsync-APRs [email protected]:outfile dump提取转储。mitmproxy#将mitmproxy格式转换为HAR格式(基于json的格式)mitmdump-vvv-n-r infile.dump-s Mitmproxy/Examples/Examples/Complex/har_Dump.py--set hardump=./outfile.har(将https://github.com/mitmproxy/mitmproxy.git格式转换为HAR格式(基于json的格式)mitmdump-vvv-n-r infile.dump-s mitmproxy/Examples/Complex/har_dump p.py--set hardump=./outfile.har

有了JQ,我们可以提取请求URL,这对于开始了解Amazon和我们设备之间的通信流很方便。

$CAT Dumps/Dump.har|JQ-rc';.log.entry|MAP(.request.url)';";https://54.239.22.185/FirsProxy/registerDevice";,";https://54.239.22.185/FirsProxy/registerDevice";,";https://54.239.22.185/FirsProxy/registerDevice";,";https://54.239.22.185/FirsProxy/registerDevice";,";https://54.239.22.185/FirsProxy/getStoreCredentials";,";https://52.46.133.19/FionaTodoListProxy/syncMetaData";,#唯一URLScat转储/dump p5.har|JQ';.log.Entries|MAP(.request.url)|Sort|Unique|#39;#筛选具有特定URLcat转储/Dump5.har|JQ';.log.Entries|MAP(SELECT(.request.url==";<;MY_URL&>";))';

设备在登录时发出的第一个调用是registerDevice调用。它包含登录名和密码,用于在Amazon上注册设备。在通过cURL重播此请求后,我收到了“密码错误”的响应。这是因为Amazon发送了一封双因素身份验证电子邮件,您必须(再次)在密码字段中使用双因素身份验证电子邮件中的值重放registerDevice调用才能通过。

在这一点上,亚马逊Kindle网站🙌上可以看到一款新设备。下一件要做的事是去取书单。

现在的问题是,我们可以看到,对Amazon的每个后续请求都有一个X-ADP-Request-Digest:每个请求都是在registerDevice调用中接收到的证书的帮助下“签名”的。

下面,您可以看到用于获取图书列表的syncMetadata路由上的示例请求:

{";startedDateTime";:";2020-05-10T14:21:42.217752+00:00";,";时间";:6532,";请求";:{";方法";:";获取";,";URL";:";https://52.46.133.19/FionaTodoListProxy/syncMetaData";,";httpVersion";:";HTTP1.1";,";标题";:[{";名称";:";X-adp请求摘要";,";值";:";SIG1tis85OWFqJqbzy0Z0xBzBCI3/88e9p/2jr8UvTAUQCuil5ED0833peNeKPp1dIMdVAs/INcUR//xvCJu+ngyP9olVSda/IBBxM2fftVGIDEVuQqMSC9P+O/pZMhaAJpvxIm78M52OB+lNIYXjE0Kr1OB0mmOo4iVu45aRio8hZDlmDG07zjVHnlQHE5sUjzOMnYBFC6VXw+srjYfo6dTptwSKNX11A0naG+tjcuxnglAE3R9U8/+pVr/uFNT4ou+0cQs2KbV0/4tYEIbOogC1JgjNNt4hyb2l91QED7Aj+A/DFcKBT+XNkjAUAAI1//HhCtxqCNtbu1E1sRReQ==:2020-04-10T14:21:40Z";}]}}。

对请求进行签名意味着我需要使用正确的密钥进行签名(我非常确信在registerDevice调用中接收的Private Key就是要使用的密钥)签名正确的数据=从请求中查找用于生成请求指纹的字段(以及按什么顺序

幸运的是,经过一些搜索之后,我发现了olsborn(readsync和fiona-client)的存储库,它们实现了Whispersync API的请求签名。

Data=";%s\n%s";%\(method,url,time,postdata,sel.adp_Token)rsa=RSA.load_key_string(str(sel.Private_pem))crypt=rsa.Private_ENCRYPT(hashlib.sha256(Data).digest(),RSA.pkcs1_padding)sig=base64.b64encode(Crypt)。

从这段代码中,我可以看到使用了哪些字段以及使用的顺序。我现在只需要“只”把它转换成Javascript。

在node-forge和node-rsa的帮助下,经过一番努力,我设法获得了正确的签名。我发现即使有两个示例实现也很难获得完全正确的签名(我不是密码技术方面的专家,也不了解这两个库,所以我花了一点时间试图在每个可能的🙃中阻塞证书和值)。

签名有问题并不是一目了然的,因为您必须向Amazon发送检查请求(如果签名有问题,Amazon会发送内部服务器错误)。使用记录的转储获得正确签名的示例,并将其用于具有固定日期(因为该日期在请求中使用)的Jest测试中非常有用,因为反馈循环非常快。

⚠️我必须使用两个不同的库才能获得相同的签名。不过,我相信只使用节点伪造是可能的。ℹ️私钥是用base64编码的DER格式的pkcs8证书。❓我想知道为什么要签署请求,这些设备不会生成私钥/公钥对并将公钥传输到亚马逊:在网络上有私钥是有点不寻常的。我想知道为什么要在请求上签名,设备不会生成私钥/公钥对并将公钥传输到亚马逊:在网络上飞行的私钥是有点不寻常的。

在破解签名部分之后,可以调用syncMedataroute,并使用适当的摘要头来获取我们图书馆的所有图书(耶!)。

但是,挑战还没有完成:我最感兴趣的是获取书上的元数据:我的亮点、注释和最后一页阅读。我需要找到检索所有这些数据的电话。

当在转储中搜索高亮显示的内容时,我找不到任何🤔。当使用图书的标识符(Amazon术语中的ASIN)过滤转储的URL时,一些URL看起来很有趣:

FSDownloadContent是获取图书内容的调用。但是侧车URL是什么呢?侧车听起来像是装在书边上的东西,可能包含注释?

响应的正文是base64编码的,在base64--decode之后,答对了!我可以在我的终端机上阅读我的书中的集锦内容。

#使用临时命令行界面工具获取二进制格式的边车并对其进行解码$YAYN-s CLI FETCH SIDCAR--no-parse B01056E716|Base64--decodeCR!3WET39TJ553PXCAHWBHXQ1RT_PAR^̵]^̵]BPARMOBI&;&;<;@6^^!B#f$%*f";V FBPAR8FYDATApce plaisir puis dans l agressivit ainsi que cet amour de la servilit grgaire nDATAC tait tait la Mme幻想,prodant de la Mme volont de s flsionner soi-mme.8DATAn avait pavait pencore ininvle me actuel qui consistence e assomens de materDATAafftenant.。这是一件很重要的事,我要告诉你,我的新天地是新的,安格列特的人是最棒的,所有的人都是这样的人,所以我才是真正的爱国者,因为他们是最伟大的人,他们都是这样的。Ĉ的数据是迷人的,你的努力是沉默的,热情的是你的热情,你的热情不会让你更快乐,更灿烂,更灿烂,更好的形象。PDATAnous qui attons de chaque jour qui-lve des disfies pires encore que cell de la veille,nous somets nettement加上Succiquant la Possibilit deޭde home的教育士气。BKMK4W*WW*ޭ$#BKMK4^,^,ޭBKMK4~y:~yޭBKMK4ޭ!(译:~y BKMK4ޭ!!)(注:~y BKMK4,^,BKMK4~y:~y BKMK4ޭ~y:~y BKMK4ޭ!(注:~y BKMK4ޭ!)。BKMK4`ޭBKMK1ޭBKMK4。

如您所见,问题在于解码的内容有点乱码:缺少重音,注释没有正确分隔。事实证明,亚马逊对其侧车使用了自定义的二进制格式。经过加密、二进制解码,多么冒险的🤠啊!

在搜索SideCar应用程序类型时,我偶然发现了KSP(Kindle Server Proxy),这是一个将Calibre库无缝连接到Kindle的项目。它作为亚马逊和您的Kindle之间的中间件,实现Whispersync API的路由。它根据需要提供来自您的Calibre数据库或亚马逊的内容:如果一本书来自亚马逊,则内容从亚马逊提供,否则它从本地数据库提供。

KSP不需要阅读侧边栏,因为它只传递内容而不阅读内容就足够了。不过,它确实需要知道如何编写侧边栏。代码提供了关于所使用的二进制格式的信息,但我知道我需要编写一个侧栏解析器。

在这一点上,我决定以图形形式查看字节,以便更好地理解正在发生的事情。我最近读到关于可视化二进制颜色的文章,所以我想我可以试着做同样的事情。通过重新实现,我将对可视化有更多的控制,并且希望它能在调试解析器时发挥作用。

我启动了codesandbox并编写了一个应用程序,当给定base64字符串时,它会输出根据值着色的字节。我使用具有控制色调的字节的值的HSL颜色空间,以便近值字节具有相似的颜色。它和Cortesi的binvis非常相似,但是这个我可以更容易地破解来满足我的需要。

查看彩色数据有助于查看二进制格式的不同部分并找到模式。例如,我们可以在这里看到底部的红色/绿色格子图案。这是一个双字节的模式,我们知道在这一部分,我们应该有文本,所以文本用UTF16编码是一个很好的线索。

这个应用程序在编写解析器时真的很有帮助,因为我可以更好地理解解析器在哪部分遇到了困难。

为了编写解析器,我使用了二进制解析器。它的API使得创建隔离程序变得很容易。为了能够将其与二进制查看器结合起来,我对代码进行了修补,以便将之前/之后的索引添加到每个字段,这样我就可以在查看器中单独看到每个字段。

Const sidecarParser=new Parser()。字符顺序(大)。字符串(';GUID';,{Length:32,stripNull:true})。寻求(4)。缓冲区(';v1';,{Length:4})。缓冲区(';v2';,{Length:4})。缓冲区(';v3';,{Length:4})...。Uint16(';next_id';)。寻求(4)。Uint16(';index_count';)。Seek(2)。Uint16(';bpar_ptr';)。SaveOffset(';bpar_ptr_index_';)。

Const rawData=new buffer()//二进制数据const data=sidecarParser。Parse(数据)//&>;{索引计数:16,下一个ID:5,bpart_ptr:300,GUID:‘cpar…。.‘}。

为了格式化从数据部分提取的字节,我使用了浏览器中的Uint{8/16}ArrayAPI,该浏览器充当字节缓冲区上的视图。这对于Uint8/16整数缓冲区很有效。我面临的问题是字符串缓冲区:Uint{8/16}Array API在平台字符顺序中工作(在可能的情况下使用nativeAPI)。我的电脑是小端的,而批注是用utf16大端编码的。这意味着如果我想用String.fromCharCode读取缓冲区,我必须交换缓冲区的字节顺序,或者使用DataView(独立于平台字节顺序的Javascript API来读/写数据)。我当时不知道DataView,所以我交换了字节,但是DataView应该可以工作,因为您可以在读取值时选择字节顺序。

为了测试解析器,我通过Whispersync Clienti构建的SideCar下载了侧车,并将它们的内容转储到测试文件夹中。当试图解析它们时,解析器很困难,我可以看到许多“EF BF BD”字节以蓝色显示。

这些字节主要出现在包含指向注释位置的指针的二进制部分。这导致解析器失败。在努力尝试移动数据或忽略这些字节后,我搜索了“EF BD BD”。

EF BF BD是用于FFFD的UTF-8,它是Unicode替换字符(用于无法将字符转换为特定代码页时的数据损坏)。Https://stackoverflow.com/questions/47484039/java-charset-decode-issue-in-converting-string-to-hex-code>;

我发现用于获取HTTP响应的请求JS库在默认情况下尝试将数据解码为UTF-8,但失败了(因为侧车是以二进制数据在网络上传输的)。解决方案是将{ENCODING:NULL}作为请求选项,以便它返回一个缓冲区。然后可以用Base64对缓冲区进行编码,以便将其打印到控制台,而不会出现乱码。这个base64输出可以在转储到磁盘之前通过管道传输到base64--解码(控制台。记录缓冲区将尝试将数据解码为utf-8)。

在某种程度上,为了能够浏览注释,在我看来,CLI在轻松浏览内容方面受到了一定的限制。我添加了一个提供图书索引的HTTP服务器和一个显示注释的图书页面。如果内容不在磁盘上,则从Amazon获取内容,然后将其保存为JSON格式以供以后访问。

我使用了Fastify,这是一种替代方式,可以用来表达承诺要快得多的内容。就我的简单目的而言,它工作得很好。

有很多障碍,但我真的很兴奋,因为恢复的数据对我很重要:我喜欢阅读,现在很高兴我的亮点将以一种我可以在很长一段时间内阅读的格式存在。我也很高兴它们采用简单的JSON文本格式,我可以使用我熟悉的JQ、rg或grep工具进行查询。

使用Whispersync客户端创建一个舒适的连接器,让我的注释自动同步到我的舒适🚀。

对于请求签名和启动Javascript客户端的Javascript结构非常有用(事实证明,Scala转换为Javascript的速度相当快)。