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#代码、汇编代码可以从这里下载:

