2011년 3월 15일 화요일

다른 프로그램에서 wav 파일 재생시 가로채기

  • wav 파일 재생시에는 PlaySound, sndPlaySound 함수를 거의 100% 사용하는 듯. (PlaySoundA, PlaySoundW, sndPlaySoundA, sndPlaySoudW 는 당연한거고...-_-)
  • 해당 함수들은 winmm.dll 에 모여살고 있다. winmm.dll 은 System32 디렉토리 안에 살고 있다. ex) C:\Windows\System32\winmm.dll
  • ProxyDLL 개념을 도입해서 가로챌 수 있다. DLL 로딩 순서 상 실행파일이 위치한 디렉토리에 있는 DLL 이 최고 우선순위가 되므로, 가로챌 함수가 들어 있는 winmm.dll 을 한개 만들어서 wav 파일 재생을 가로채고 싶은 실행파일이 있는 디렉토리에 복사시키면 ok. 물론 wav 재생함수 호출시 뭔가 액션을 취할 것을 준비해야 함.
가정1. C:\Test\A.exe 라는 파일이 wav 파일을 재생하는 것을 가로채고 싶다.
가정2. A.exe 파일이 wav 파일을 재생하는데 사용하는 함수는 PlaySoundA() 이다. DependencyWalker 같은 프로그램으로 확인가능.
가정3. 내가 만든 winmm.dll 에서 wav 파일재생함수 호출시 SendMessage 를 통해 내가 만든 host 프로그램으로 보낸다. WM_COPYDATA 를 사용하며 Data 의 내용은 재생하는 wav 파일의 전체경로이다.
가정4. host 프로그램은 WM_COPYDATA 를 받으면 Data 에 들어있는 wav 파일을 대신 재생하며, 해당 전체경로의 문자열을 분석, 가공하여 하고 싶은 일을 한다.
DLL - winmm.dll
조낸 간단하다.
DLL 프로젝트 하나 생성하고 아래처럼 하고 컴파일하면 winmm.dll 이 쑴풍 나옴.
library WinMM;
uses
Windows,
SysUtils,
Forms,
Messages;
{$R *.res}

const
// WM_COPYDATA 를 받을 host 프로그램의 ClassName 과 Caption. FindWindow 에서 사용할 것.
WINCLS_NAME = 'TfrmWavPlayHookHost';
CAPTION_NAME = 'WavPlayHookHost';
// wav 파일 재생함수를 호출할 때 매개변수로 전체경로를 안넣는 놈들 때문에 만든 함수임. ㅠ_ㅠ
// 실상은 허접함. :\ 같은 드라이브 경로 문자열이 있는 지 보고 판단.
function GetFullPath(SoundFilePath: String): String;
begin
if Pos(':\', SoundFilePath)>0 then Result:=SoundFilePath
else Result:=ExtractFilePath(Application.ExeName)+SoundFilePath;
end;
// host 프로그램에 WM_COPYDATA 를 보내는 함수
procedure SendMSG(SoundFilePath: String);
var
DataBuf: TCopyDataStruct;
begin
DataBuf.cbData:=Length(Trim(SoundFilePath))+1;
DataBuf.lpData:=PChar(SoundFilePath);
SendMessage(FindWindow(WINCLS_NAME, CAPTION_NAME), WM_COPYDATA, 0, LParam(@DataBuf));
end;
// 두어개 밖에 확인을 안해봤지만 wav 파일을 재생하는 프로그램은 아래의 함수 6개를 피해가지 않는 것 같다.
// 각 함수들이 하는 일은 똑같다.
// OutputDebugString 을 통해 '무슨 함수가 호출되었는지', 'wav 파일 경로가 무엇인지' 를 확인하고
// host 프로그램에 메세지를 보낸다.
function PlaySound(pszSound: PChar; hmod: HModule; fdwSound: DWORD): BOOL; stdcall;
begin
Result:=True;
OutputDebugString(PChar('[MyDLL] PlaySound - '+GetFullPath(pszSound)));
SendMsg(GetFullPath(pszSound));
end;
function PlaySoundA(pszSound: PChar; hmod: HModule; fdwSound: DWORD): BOOL; stdcall;
begin
Result:=True;
OutputDebugString(PChar('[MyDLL] PlaySound - '+GetFullPath(pszSound)));
SendMsg(GetFullPath(pszSound));
end;

function PlaySoundW(pszSound: PChar; hmod: HModule; fdwSound: DWORD): BOOL; stdcall;
begin
Result:=True;
OutputDebugString(PChar('[MyDLL] PlaySoundW - '+GetFullPath(pszSound)));
SendMsg(GetFullPath(pszSound));
end;

function sndPlaySound(lpszSound: PChar; fuSound: UINT): BOOL; stdcall;
begin
Result:=False;
OutputDebugString(PChar('[MyDLL] sndPlaySound - '+GetFullPath(lpszSound)));
SendMsg(GetFullPath(lpszSound));
end;
function sndPlaySoundA(lpszSound: PChar; fuSound: UINT): BOOL; stdcall;
begin
Result:=True;
OutputDebugString(PChar('[MyDLL] sndPlaySoundA - '+GetFullPath(lpszSound)));
SendMsg(GetFullPath(lpszSound));
end;
function sndPlaySoundW(lpszSound: PChar; fuSound: UINT): BOOL; stdcall;
begin
Result:=True;
OutputDebugString(PChar('[MyDLL] sndPlaySoundW - '+GetFullPath(lpszSound)));
SendMsg(GetFullPath(lpszSound));
end;
// wav 파일을 재생할 때 대부분 이렇게 6개의 함수 중 하나를 이용하는 것 같다.
exports
PlaySound name 'PlaySound',
PlaySoundA name 'PlaySoundA',
PlaySoundW name 'PlaySoundW',
sndPlaySound name 'sndPlaySound',
sndPlaySoundA name 'sndPlaySoundA',
sndPlaySoundW name 'sndPlaySoundW';
begin
OutputDebugString('후후 병신...');
end.
HOST
WM_COPYDATA 로 wav 파일의 전체경로가 넘어온다. 적절히 이용하자.
WM_COPYDATA 메세지 핸들러를 아래처럼 작성해서 넘어온 파일을 재생해주고, 파일경로를 출력하기만 함.
사용하고자 마음만 먹으면 방법은 무궁무진하다.
procedure TfrmWavPlayHookHost.WmCopyData(var Msg: TMessage);
var
Data: PCopyDataStruct;
begin
Data:=Pointer(Msg.LParam);
PlaySound(Data.lpData, 0, SND_SYNC);
OutputDebugString(PChar('[Hook!] - '+PChar(Data.lpData)));
end;
  • DLL 레벨에서 wav 파일을 재생해줘야 진정한 의미의 Proxy 가 될 것 같은데 아무리 실제 winmm.dll 을 동적로딩하고 GetProcAddress 로 진짜 함수를 가져와서 호출해봐도 소리가 안남. 반환값이 False 인걸 보면 경로를 못찾는 것 같은데 잘 모르겠다.
  • API 전역 훅으로 해보면 어떨까.

댓글 없음:

댓글 쓰기