Audacity是开源的工具,本来说直接改源代码然后再编译就好了。不过这东西看起来总觉得没那么好编译的样子(虽然其实自己没编译过)于是就想着通过直接修改EXE来实现。
挺好的波形编辑器,做LRC歌词的时候可以通过波形来获取比单纯靠耳朵听更加精确的时间。但是这家伙自己不提供什么接口可以用来获取光标所在位置的时间。
然后我就对它进行一定的修改,使得它可以作为LRC编辑器的一部分使用。
最早我是这么想的,如果它用了一个全局变量来保存光标所在的时间,那么找出这个变量的内存地址就一切好办了。于是开启Cheat Engine,数据类型Double,模糊搜索模式,搜索“增加的数据”“减少的数据”这么找,很容易就能找到保存光标所在时间的地址。
找到这么些个地址。0x2ce9a50这样的地址看着不像全局变量啊……
那,如果能找到写入这个地址的指令,就可能有别的办法达到目的。把0x2ce9a50加入到下面那个列表里面。然后使用Find out what writes to this address的功能,到audacity里面改变一下光标所在位置,一下子就能找到这样的代码。
指令找到以后,就可以开始动手修改audacity了。
我用的方法,是在Audacity启动的时候,打开某块共享内存。成功的话每次fst qword ptr [ecx]这条指令执行之后就把 qword [ecx] 的值复制到这块共享内存之内。这块共享内存由自制的LRC编辑器之类的东西创建。这样的代码用汇编语言来写,像这样的:
BITS 32 _shmemName: db "81bdd671-bf82-43a9-8078-dd11039f8eeb", 0, 0, 0, 0 _shmemHandle: dd 0x12345678 _shmemAddr: dd 0 dd 0x90909090 dd 0x90909090 dd 0x90909090 dd 0x90909090 dd 0x90909090 dd 0x90909090 dd 0x90909090 dd 0x90909090 dd 0x90909090 dd 0x90909090 dd 0x90909090 _entry: xor eax, eax mov dword [_shmemHandle], eax push _shmemName push 0 push 2 ; call OpenFileMapping dw 0x15ff dd 0 cmp eax, 0 jle _fail mov dword [_shmemHandle], eax push 8 push 0 push 0 push 2 push eax ; call MapViewOfFile dw 0x15ff dd 0 cmp eax, 0 jz _fail_2 mov dword [_shmemAddr], eax jmp _fail _fail_2: mov eax, dword [_shmemHandle] push eax ; call CloseHandle dw 0x15ff dd 0 _fail: ret _replaceCode: fst qword [ecx] mov edx, dword [esi + 0x1c0] push eax mov eax, dword [_shmemAddr] test eax, eax jnz _cont pop eax ret _cont: push edx mov edx, dword [ecx] mov dword [eax], edx mov edx, dword [ecx + 4] mov dword [eax + 4], edx pop edx pop eax ret
entry处的代码,是要audacity启动的时候就执行的。replaceCode处的代码,是在0x4b1f8c处调用的。
这汇编代码用nasn -f bin编译一下,得到二进制代码。
有一个很方便的工具叫CFF Explorer,先用它来改Audacity的exe文件。在Section Header里面,右键菜单选择Add Section (File Data),把这个二进制代码文件导入进去。在Change Section Flags里面把可读可写可执行全部勾上。导入进去还不够,还要用Import Adder功能把Kernel32.dll里的CreateFileMappingA、MapViewOfFile和CloseHandle给添加到导入表去。搞完保存
这样改还不算完。接下来用ollydbg来改。用ollydbg打开Audacity.exe之后,看到这样的代码
007080B3 > $ E8 E0030000 CALL audacity.00708498 007080B8 .^ E9 36FDFFFF JMP audacity.00707DF3
其中JMP 00707DF3可以拿来改。刚才导入的那个二进制代码文件里面不是有一堆90吗,改成跳转到那里去。
再然后,004B1F8C那个地方(就是之前找到会写入光标位置的代码的地方),给改成call到replaceCode的地方(会覆盖掉两条原来的语句)。这些做完以后先copy to executable一下。
再用ollydbg开audacity,这次要改的是之前导入的那个二进制代码文件的部分。首先是刚刚jmp过来(启动时会执行的代码)的地方,把那些nop改出一条PUSH 00707DF3。因为之前把JMP 00707DF3改掉了吧,所以现在要让这些代码执行之后RET到刚刚那个地方去。
还有,导入的代码中所有引用到shmemName、shmemHandle、shmemAddr的地方,这些操作数都必须手工修复。因为nasm编译的时候默认基址是0,现在要改成正确的地址。
最后,填充导入的代码中所有ff15(call)指令的操作数。用新增加的导入表中的函数的对应地址。
这些搞完以后保存,这次没有all modifications给你选了,要手工选择改了的部分,然后copy to executable -> selection。
这样就可以通过共享内存来获取光标所在位置的时间了,可以自己写程序造一个LRC编辑器什么的了,比如用C#写这样的代码:
using System; using System.Runtime.InteropServices; namespace Sorayuki { public class AudacityPositionReader : IDisposable { [DllImport("Kernel32.dll", EntryPoint="CreateFileMappingA", CharSet=CharSet.Ansi, CallingConvention=CallingConvention.StdCall)] static private extern UInt32 CreateFileMappingA(UInt32 hFile, IntPtr lpAttributes, UInt32 flProtect, UInt32 dwMaximumSizeHigh, UInt32 dwMaximumSizeLow, string lpName); [DllImport("Kernel32.dll", EntryPoint="OpenFileMappingA", CharSet=CharSet.Ansi, CallingConvention=CallingConvention.StdCall)] static private extern UInt32 OpenFileMappingA(UInt32 dwAccess, UInt32 bInherit, string lpName); [DllImport("Kernel32.dll", EntryPoint="MapViewOfFile", CallingConvention=CallingConvention.StdCall)] static private extern IntPtr MapViewOfFile(UInt32 hFileMapping, UInt32 dwAccess, UInt32 dwOffsetHigh, UInt32 dwOffsetLow, UInt32 dwLen); [DllImport("Kernel32.dll", EntryPoint = "UnmapViewOfFile", CallingConvention = CallingConvention.StdCall)] static private extern UInt32 UnmapViewOfFile(IntPtr pBase); [DllImport("Kernel32.dll", EntryPoint = "CloseHandle", CallingConvention = CallingConvention.StdCall)] static private extern UInt32 CloseHandle(UInt32 handle); private UInt32 _hShmem; private IntPtr _pShmem; const string _shmemName = "81bdd671-bf82-43a9-8078-dd11039f8eeb"; public AudacityPositionReader() { UInt32 hShmem = OpenFileMappingA(3, 0, _shmemName); if (hShmem == 0) { hShmem = CreateFileMappingA(0, IntPtr.Zero, 4, 0, 8, _shmemName); if (hShmem == 0) throw new Exception("Can't create shared memory block"); } _hShmem = hShmem; IntPtr pShmem = MapViewOfFile(hShmem, 2, 0, 0, 8); if (pShmem == IntPtr.Zero) { CloseHandle(hShmem); _hShmem = 0; throw new Exception("Can't map shared memory block"); } _pShmem = pShmem; } ~AudacityPositionReader() { Dispose(); } public void Dispose() { UnmapViewOfFile(_pShmem); CloseHandle(_hShmem); } public double Position { get { unsafe { double* pVal = (double*)_pShmem; return *pVal; } } } } }
改造过的EXE和提到的C#代码、汇编代码可以从这里下载: