Audacityの改造とLRC/字幕エディターとしての利用

Audacityはオープンソースのツールから、ソースを編集して再構築すればいいですが。再構築は面倒そうなので直接バイナリを編集する方法を使った。
うほっ!WAVEエディターです。
LRCの歌詞作りたいとき、Audacityで時間を確認したら精確なタイミングを取得できる。でもやつ自身はカーソルのところのタイミングを取得するプログラミングインターフェースがなかった。
私、こいつを改造してLRCエディターの一部として使う。

最初に思いついたのは、もしAudacityはカーソルのタイミングをとあるグローバル変数に保存すれば、そのアドレスを探せば十分です。Cheat Engine使って、型は「Double」そして検索モードはUnknown Initial Valueにして、探しは難しくないはず。

いくつがある。0x2ce9a50のようなアドレスはグローバル変数に見えそうじゃない。

ええと、それじゃあどこの指令でこの変数の値を変わるって探す。0x2ce9a50を下のアドレス配列を追加する。そしてCheat engineの「Find out what writes to this address」機能を使って、Audacityでカーソルの場所を変わって簡単に探した。

指令を探したら後はaudacityを改造するのだ。

私の方法は、Audacityの起動するとき共有メモリブロックを開く。成功したら毎度fst qword ptr[ecx]した後qword ptr [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のところで実行する。
このコードはnasm -f binでコンパイルしてバイナリコードになる。
CFF Explorerというツールを利用してAudacityのSection HeaderにAdd Section (File Data)。このバイナリコードをaudacityにインポートする。Change section flagsでIsReadableとIsWritableとIsExecutable全部On。そしてCFF ExplorerのImport Adder機能でCreateFileMappingAとMapViewOfFileとCloseHandleのインポートテーブルを作る。セーブ。

まだまだ足りない。ollydbgの出番だ。さっき言ったことを編集した後AudacityをOllydbgで開いて、


007080B3 > $ E8 E0030000 CALL audacity.00708498
007080B8 .^ E9 36FDFFFF JMP audacity.00707DF3
こういうコードを見える。JMP 00707DF3を編集して、さっきインポートするファイルにたくさんの90(NOP)があるだろう?そこにジャンプする。
あと、004B1F8Cのところのコードは編集して(2つのコードをオーバーライトする)replaceCodeの部分にcallする。これで現有コードの編集は終わりました。セーブして(copy to executable -> all modifications)。

再度OllyDbgでAudacity.exeを開く。今度はさっきインポートしたファイルの部分を編集する。まずあの90等。一部をPUSH 00707DF3に編集する。そうするとretの時元のところに戻れる。
後、インポートしたファイルのコードに全部のshmemName、shmemHandle、shmemAddrのような所のオペランドを修復しなければならない。nasmでコンパイルする時基準アドレスは0にしてたからね。
最後、インポートしたファイルのコードに全部のff15(call)指令のオペランドをつめる。さっき追加したインポートテーブルに対応のアドレスで。
そしてセーブする(編集したところ全部選択してそして copy to executable -> selection)


こうして、カーソルのタイミングは共有メモリから取得できる。例え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;
}
}
}
}
}

改造したAudacityおよびソースコード:
http://www1.axfc.net/uploader/so/2751946