改造Audacity来制作LRC/字幕编辑器

 

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

audacity_202_sorayuki

 

发表评论