Featured image of post 某琴吧EXE播放器破解记录

某琴吧EXE播放器破解记录

背景

本文接另一篇文章 某琴吧Flash播放器破解记录

由于某琴吧改版正式使用exe播放器之后, 删除了所有的.ypa2资源, 导致我没有及时下载的乐谱都无法继续使用Flash播放器播放了, 忍不了, 只能破解exe播放器了

目标

破解该站的exe播放器, 使其能够免费播放VIP乐谱, 且可以完全脱机使用

开始破解

首先安装网站的乐谱播放器, 打开安装目录发现文件结构如下

image-3
image-3

其中xxxchrome.exe相当于一个浏览器, 可以访问某琴吧, 点击"客户端播放"按钮即可打开播放器, 如下图

image-2
image-2

xxplayer.exe才是播放器, 播放器启动后, 可以从任务管理器看到新增的进程

image-1
image-1

剩下的安装文件和库文件还有配置文件, 就不介绍了.

首先播放一个免费的乐谱, 在任务管理器中查看一下xxplayer.exe的启动参数

image-4
image-4

完整的启动参数如下

"C:\Program Files\马赛克music\out1\马赛克player.exe" "file=http://www.马赛克.com/open_yp.php?ypid=73981&uid=999999999&token=fbf0ab24ee47bfa1cb460e41c1f61fdb&ypid=73981"

可以看到启动参数主要为

  • file 官网的乐谱页地址
  • ypid 乐谱ID
  • uid 用户ID
  • token 凭证

如果从xxxchrome.exe打开一个VIP乐谱, 则会显示

image-5
image-5

那么如果我不用他的xxxchrome.exe, 直接从命令行启动xxplayer.exe, 传入一个VIP乐谱ID, 会发生什么呢?

将刚才的启动参数中的ypid参数修改为一个VIP乐谱ID, 通过命令行

xxplayer.exe "file=http://www.马赛克.com/open_yp.php?ypid=33418&uid=999999999&token=4c6930a47090c3f04c7e9db3bf214078"

启动xxplayer.exe之后如下

image-6
image-6

非常的正常, 但是至少验证了 xxplayer.exe是可以脱离xxxchrome.exe独立运行的, 那么既然要鉴权, 不请求服务器肯定是不行的吧? 于是打开Charles开始抓包, 首先用命令行打开一个免费的乐谱, 捕获到网络请求如下

image-7
image-7

可以看出主要请求接口为/codeindex.php, 通过参数m做不同功能的权限认证

m=index, 验证成功返回权限信息

{
	"responseCode": "1000",
	"message": "\u6b63\u5e38",
	"power": {
		"openPower": "1",
		"printPower": "0",
		"printCount": "30",
		"vstPower": "0",
		"pdfPower": "0"
	}
}

m=getYpdsUrl, 验证成功返回.ypdx格式的播放文件地址

{
	"data": {
		"code": "1000",
		"message": "\u83b7\u53d6\u6210\u529f",
		"result": {
			"ypdsUrl": "http:\/\/马赛克.马赛克.com\/yuepuku\/148\/74205\/74205_cfcbahia.ypds",
			"ypdxUrl": "http:\/\/马赛克.马赛克.com\/yuepuku\/148\/74205\/74205_cfcbahia.ypdx"
		}
	}
}

最后再请求资源地址http:\/\/马赛克.马赛克.com\/yuepuku\/148\/74205\/74205_cfcbahia.ypdx, 得到.ypdx格式的播放文件

现在再来通过命令行启动一个VIP乐谱,看看请求和响应会是怎样的

image-8
image-8

可以看到这次只有一个m=index的请求, 且返回码不同于免费乐谱的1000, 之后播放器弹出没有权限的弹窗, 那么思路就来了

如果我将篡改请求的响应,播放器应该就会认为我是有权限的,那不就可以播放了吗?

首先使用Charles进行断点, 针对m=index的请求, 修改播放器请求VIP乐谱的返回值

iamge-9
iamge-9

和我预料的一样, 修改m=index的响应后, 这次发送了m=getYpdsUrl的请求

但是m=getYpdsUrl仍然对用户权限进行了判断, 因此返回码不是正常的1000, 且result节点没有返回.ypdx播放文件的资源地址, 看样子不是VIP用户是无法获得资源地址了

但是

在文章 某琴吧Flash播放器破解记录 中, 使用getURL生成的乐谱信息url去请求乐谱服务器得到的返回值里, 是包含.ypa2格式的资源地址的, 只不过现在.ypa2资源已经被删了, 不过观察刚才免费的乐谱.ypdx资源地址后, 会发现这和.ypa2的资源地址高度相似! 于是我将返回的.ypa2资源地址修改后缀名为.ypdx进行请求之后, 顺利得到了.ypdx格式的播放文件!

//ypa2请求地址
http://oss.马赛克.com/yuepuku/115/57806/57806_hhdafigb.ypa2

//ypdx请求地址
http://oss.马赛克.com/yuepuku/115/57806/57806_hhdafigb.ypdx

得到.ypdx文件之后, 开启一个本地HTTP服务器, 开放一个地址用于返回.ypdx文件, 然后用Charles对VIP乐谱请求中m=getYpdsUrl断点, 并修改响应中result里对应的资源地址为本地HTTP服务器地址

{
    "data":
    {
        "code":"1000",
        "message":"\u83b7\u53d6\u6210\u529f",
        "result":
        {
            "ypdsUrl":"http:\/\/127.0.0.1:7777\/yuepu\/57806_hhdafigb.ypds","ypdxUrl":"http:\/\/127.0.0.1:7777\/yuepu\/57806_hhdafigb.ypdx"
        }
    }
}

再次测试, 成功打开VIP乐谱, 并开始播放, 所有功能正常!

image-10
image-10

确定伪造响应的方案确定可行之后, 下一步就是要让播放器请求我指定的地址了, 因为刚才是通过Charles修改响应的, 但我不可能每次都使用Charles断点, 我需要反编译xxplayer.exe, 并修改服务请求地址为我监听的地址, 然后由我监听的地址返回所需数据

使用OllyDbg(以下简称OD)反汇编xxplayer.exe, 在菜单栏依次点击 “插件” -> “中文搜索引擎” -> “搜索UNICODE”, 以Charles捕获到的url中的关键字codeindex搜索, 发现一共有三处

iamge-11
iamge-11

Warning: 此处我没有使用虚拟机打开, 建议各位反汇编的时候用虚拟机, 因为被反汇编的程序如果有反调试的代码, 轻则闪退重则格盘. 另外OD本身是不带"中文搜索引擎"插件的, 需要自行下载安装

双击搜索到的第一处字符串, 跳转到引用代码段, 通过字符串格式可以看出, 其中%s是要用参数替换的, 且通过前面的push可以看出, 它自身也是作为参数被传递的, 那么只要跟踪下方离他最近的call之前的变量, 就能确定需要修改哪一行了, 在可疑的push处按F2断点, F8单步调试后, 发现push [local.7]正是请求的域名

image-12
image-12

为了修改字符串, 有两种方式

  1. 如果修改前后字符串长度相同, 可以直接定位到字符串地址进行修改
  2. 如果长度不同, 可以在程序空白处, 新增一段字符串, 然后将之前对字符串的地址引用改为新增的字符串地址

程序空白处是指在OD左下角的数据面板, 拉到最下面, 可以看到一堆00 00 00 00 00 00 ..., 00在汇编中就是啥都没有, 啥都不干, 也就是空白的意思

这里暂时不知道[local.7]哪儿来的, 所以我选择第二种方式, 查看OD左下角数据面板, 拉到最下面, 找到程序空白处, 双击空白地址, 在弹出的编辑窗口中取消勾选"Keep size", 在UNICODE栏输入localhost:7777

image-13
image-13

image-14
image-14

Q1: 为什么不在011808E0行的6C 65 57后面的00处添加, 而是在00的后面?

因为汇编中字符串是以00结尾, 且每个字符后面都跟一个00, 为了尽量避免覆盖前面的数据块, 这里空开一个00位置, 如果前面数据块明显是字符串,需要空开三个00, 因为结束符00后面也会跟一个00

Q2: 为什么取消勾选"Keep size"

Keep size的作用是保持长度字符串长度一致, 因为如果超过原来的长度会造成不可预知的错误, 但是这里是程序空白处, 后面都是空白行, 所以不需要保持长度, 有多少写多少

此时点击新增的字符串头6C可以在底部栏看到新字符串的起始地址为0x11808E4, 回到汇编面板, 双击刚才的push [local.7]所在行, 在弹出的面板中修改为 push 0x11808E4

由于修改后的代码较之前的代码更少, 需要勾选"Fill with NOP`s"来保持长度一致

image-15
image-15

可以看到在汇编面板右侧已经自动显示了标记, 提示当前push的值为localhost:7777

image-16
image-16

现在选中刚才在数据面板添加的字符串十六进制代码块, 右键点击"复制到可执行文件", 再选中汇编面板所有修改的行, 右键点击"复制到可执行文件" -> “选中行”, 在弹出的面板中右键"备份" -> “保存数据到文件"即可保存修改后的exe文件

image-36
image-36

执行修改后的程序, 发现程序异常退出, 用OD打开修改后的文件, 再次找到刚才修改push的地方, 发现右侧没有显示localhost:7777的标记

image-17
image-17

再看看跳转地址0x11808E4, 也不是刚才修改的字符串内容

image-18
image-18

虽然不知道为什么, 但是看来push [local.x]形式的代码不能这样简单的修改. 那么换个角度想想[local.7]的值是哪里来的, 这时突然想到播放器启动的时候, 带的参数里是有一个网址的

file=http://www.马赛克.com/open_yp.php?ypid=73981&uid=999999999&token=fbf0ab24ee47bfa1cb460e41c1f61fdb&ypid=73981

我将这个网址修改为百度的网址

file=http://www.baidu.com/open_yp.php?ypid=73981&uid=999999999&token=fbf0ab24ee47bfa1cb460e41c1f61fdb&ypid=73981

重新运行, 抓包发现它真的去请求百度的/codeindex.php接口了

image-19
image-19

于是我再将参数换成localhost:7777, 重新运行, 结果发现程序异常退出, 进行跟踪调试发现[local.7]的实际值被截取了不包含端口号的部分, 为localhost

image-20
image-20

尝试跟踪截取部分的代码, 但是太麻烦了决定放弃. 这时候我想到直接将整个url

%s://%s/codeindex.php?d=api&c=check马赛克playerPower&m=%s&ypid=%d&uid=%d&token=%s

中第二个%s直接替换成localhost:7777不就可以了? 但是这样就少了一个%s, 参数对不上后面的代码一定会报错, 于是我想到在?后面再加一个参数, 参数名随意, 参数值就用[local.7], 反正对于串来说, %s的数量能够对应参数的数量即可, 而对于HTTP请求来说, 多一个参数也没什么影响. 不过修改后字符串的长度就对不上了, 所以还是在程序空白处添加了一个新的字符串如下

%s://localhost:7777/codeindex.php?a=%s&d=api&c=check马赛克playerPower&m=%s&ypid=%d&uid=%d&token=%s

image-21
image-21

其中a就是新增的无用参数, a后面跟的%s将会被[local.7]的值替换, 这样一来无论启动参数中的域名是什么都无所谓了. 记录新字符串的地址为0x11808E4, 找到push原来字符串的汇编代码, 修改push地址为新的字符串地址

image-22
image-22

再次保存执行, 程序还是异常退出, 用OD打开修改后的程序发现地址正确, 字符串正确, 但是还是会报错, 于是我怀疑不能用localhost作为域名. 将localhost:7777修改为127.0.0.1:7777, 再次运行, 这次终于在后台收到了来自播放器的HTTP请求!

image-23
image-23

所有参数也都正常接收, 接下来只要返回正确的响应就行了, 对于m=index的直接返回

{"responseCode":"1000","message":"\u6b63\u5e38","power":{"openPower":"1","printPower":"0","printCount":"30","vstPower":"0","pdfPower":"0"}}

对于m=getYpdsUrl 的则返回

{
    "data":
    {
        "code":"1000",
        "message":"\u83b7\u53d6\u6210\u529f",
        "result":
        {
            "ypdsUrl":"http:\/\/127.0.0.1:7777\/yuepu\/57806_hhdafigb.ypds","ypdxUrl":"http:\/\/127.0.0.1:7777\/yuepu\/57806_hhdafigb.ypdx"
        }
    }
}

然后, 用同样的方式修改搜索codeindex结果中的第二处, 再次运行就能正常播放了.

继续破解

现在虽然播放曲谱没有问题了, 但是现在还有如下几个VIP功能用不了, 要白嫖就白嫖到极致

image-24
image-24

从音源开始下手, 点击"音源” -> “浏览"后, 播放器弹出没有权限的提示

image-25
image-25

查看Charles发现期间并没有发出网络请求, 推测应该是在之前的请求里就响应过了, 这时想到最开始m=index的请求中, power节点有很多为0的字段, 仔细一看这不就是对应的权限吗? 推测权限字段对应关系应该是

  • openPower 打开的权限
  • printPower 打印的权限
  • printCount 可打印的数量
  • vstPower 使用vst音源的权限
  • pdfPower 转换pdf的权限

参考正常响应中openPower为1, 其他的为0, 因此把后台响应中所有为0改为1, 再次点击音源, 发现可以正常选择了

{"responseCode":"1000","message":"\u6b63\u5e38","power":{"openPower":"1","printPower":"1","printCount":"30","vstPower":"1","pdfPower":"1"}}

image-26
image-26

接下来看看点击转换PDF功能, 通过Charles发现会发送一个权限请求

image-27
image-27

其中接口仍然是/codeindex.php, 但本次m取值为addDynamic, 在OD中发现就是刚才上文codeindex搜索结果中的第三处

image-28
image-28

双击搜索结果字符串转到汇编代码,00F63EAE 处的push地址就是将要用来替换成请求域名的参数, 这个地址指向了某琴吧的域名, 所以这里采用同样的办法, 先在程序空白处添加字符串127.0.0.1:7777, 然后修改push原来串的地址修改为新增的字符串地址

image-29
image-29

重新保存, 然后在乐谱工具中监听这个地址, 并参考m=index的响应以下结果

乐谱工具是我基于C#开发的一款桌面应用, 其中包括乐谱管理功能, 可以管理所有下载的乐谱并调用Flash播放器播放, 同时开启了一个HTTP监听服务, 监听所有来自localhost:7777的HTTP请求

{"data":{"code":"1000","message":"\u6b63\u5e38","result":{"printCount":"30"}}}

重新打开运行PDF保存, 发现可以转换并保存了

image-30
image-30

打印的功能和PDF的请求地址和参数m是一样的, 只要返回同样的响应就可以了

至此VIP功能全部破解

但是尚不完美

最后做脱机测试的时候发现播放器会先检查网络连接状态, 没有网络连接则无法播放

image-31
image-31

但是我的目标是播放器能够脱机使用, 而且实际上播放器被我修改后已经不需要再连上互联网, 只需要请求乐谱工具的HTTP服务地址就可以, 因此根据弹窗搜索字符串, 定位到相关代码

image-32
image-32

可以看到播放器在00F693E2处调用系统接口得到网络连接状态, 在00F693EC处, 如果有网络连接则会跳转到00F6940A处继续, 没有网络连接则会显示并中断运行, 那我只需要让这个判断失效就可以了, 双击00F693EC打开编辑窗口, 将jnz (不等于0则跳转) 修改为jmp (无条件跳转), 这样运行至此的时候将不会判断网络连接状态, 直接运行后面的代码

image-34
image-34

image-35
image-35

同样的, 在使用转换PDF功能时还会检测网络状态, 然后弹窗提示"没有检测到网络!”, 找到代码运行处, 修改00F75DDE处的jnzjmp即可

image-33
image-33

保存后重新运行, 现在脱机情况下播放器能够正常打开并请求乐谱工具的HTTP服务并正常播放乐谱了, 转存PDF文件等VIP功能也能够正常使用

至此exe版本宣告完美破解