2010. 4. 18. 23:06
개인적인 프로그램 제작에 있어서 multi-platform 동영상 재생 stub을 만들어야 할 일이 생겼다.

언어는 ruby를 사용해서 어느 정도 cross platform을 구현할 것이고, UI는 맥에도 리눅스에도 심지어 윈도에도 포팅된 GTK를 쓰면 요 세 군데는 무난하게 cross platform 어플이 만들어 지기 때문에 큰 걱정을 안했다.

근데 문제는 동영상 재생이었다. Ruby-GNOME2에 포함된 Ruby/GStreamer는 기본적으로 윈도용에는 컴파일이 되지 않고, 설혹 구현된다 하더라도 윈도에서는 GStreamer 런타임이 '보통' 없는데다 쓰는 어플도 없으니 쓸데없이 파일만 커진다. 따라서, DirectShow로 구현한 동영상 재생 stub을 루비로 가져와서 쓴다면 플랫폼에 따라 GStreamer와 취사선택해서 쓸 수 있을테니 좋을 거라는 생각에 C extension을 짜봤다.

짜 보니까, 생각보다 ruby extension만들기는 간단했다 -_-... 소스파일을 첨부한다.

사용하기 전에 DSPlayer.init를 통해 CoInitialize를 호출해 주어야 하고, 다 쓰고 나면 DSPlayer.cleanup으로 CoUninitialize를 호출해 주는 것이 좋다. 참고로 GC에서 제거될 때 생성한 COM object들을 해제해주어야 하지만 구체적으로 손대기 귀찮았기 때문에 각 객체를 close 메서드로 필히 닫아 주어야 한다.

DSPlayer.new로 객체를 생성하면 DSPlayer.open으로 동영상을 열 수 있다. 여기서 주의할 점은, GTK와의 integration을 목적으로 짠 녀석이라, 패러미터로 넘어가는 파일은 utf-8로 인코딩 되어 있어야 한다.

파일을 열었으면 play, pause, stop으로 제어가 가능하고, position 속성을 조작하여 초 단위로(Float) seeking이 가능하다. duration 속성도 사용 가능하다. 마찬가지로 단위는 초다.

snapshot 함수를 사용하게 되면 현재 스냅샷을 bmp 포맷으로 찍어준다. 스트링으로 반환하는데 파일로 저장하면 bmp 파일이 된다.

다른 창에 embeding하기 위해서는 owner_id에 대상 창의 hwnd를 int 형으로 변환해서 넣어주고 재생하면 된다. left, top, width, height 속성으로 동영상 창을 조절할 수 있다.

컴파일 환경은 ruby 1.8.7 (2008-05-31 patchlevel 0) [i386-mswin32]에 Microsoft Platform SDK February 2003, Microsoft DirectX 9.0 SDK Update (Summer 2004), Microsoft Visual C++ 6.0 에서 컴파일했다. 사실 ruby가 MSVC6.0이라 더 최신의 SDK에서 컴파일이 안되는 바람에 저 구시대의 유물들을 발굴하느라 고생 좀 했다 -_-...

'프로그래밍 > Ruby' 카테고리의 다른 글

Fortune Delivery  (0) 2010.06.17
Ruby-GNOME2-0.19.4 for Windows  (1) 2010.04.27
ruby 동영상 플레이어 라이브러리 with DirectShow  (0) 2010.04.18
Ruby-GTK2 0.19.3 for Windows  (0) 2010.04.13
네이트온 자동 로그인  (0) 2009.12.04
공개 한글 폰트 다운로더  (0) 2009.11.16
2010. 4. 13. 16:13
최근에 개인적인 사정으로 인해 DirectShow와 GTK를 ruby로 건드려야 하는 일이 생겼다. 근데 다 좋은데, DShow로 비디오 재생시킬 때 GTK쪽 윈도의 HWND를 알아야 할 필요가 생겼다.

물론 DShow extension에서 창을 하나 만들고 그 창에 비디오창을 임베딩 시킨 다음 그 hwnd를 GTK로 넘겨주어도 좋지만, 앞으로의 활용을 위해서 ruby-gtk2 측에서 hwnd를 반환하도록 수정시켜봤다.

많이 수정한 것은 아니고, gtk/rbgdkdraw.c에서 387행 #endif 밑에
388 #ifdef GDK_WINDOWING_WIN32
389 #include <gdk/gdkwin32.h>
390 static VALUE
391 gdkdraw_get_hwnd(self)
392     VALUE self;
393 {
394     return ULONG2NUM(GDK_WINDOW_HWND(_SELF(self)));
395 }
396 #endif
넣어주고, 이 함수를 루비 메서드로 하기 위해서 추가한 뒤의 행번호로 472행 #endif 밑에
473 #ifdef GDK_WINDOWING_WIN32
474     rb_define_method(gdkDrawable, "hwnd", gdkdraw_get_hwnd, 0);
475 #endif
넣어주면 끝. 컴파일 가이드는 http://ruby-gnome2.sourceforge.jp/hiki.cgi?compile_mingw 이 사이트를 보고 그대로 따라했고, cairo.h를 못 찾는 문제는 Makefile에 -I 옵션을 줘서 카이로 지정해주고, 기본 헤더파일에서 INT32나 boolean 중복 선언 문제는 기본 mingw 헤더를 수정해서 패스했다.

압축파일에 있는 .rb파일은 루비가 설치된 디렉토리가 C:\ruby일 때 C:\ruby\lib\ruby\site_ruby\1.8 에 넣고, so 파일은 C:\ruby\lib\ruby\site_ruby\1.8\i386-msvcrt 에 넣으면 큰 문제 없이 동작한다.

DirectShow를 ruby에서 쓰는건, 딱히 필터나 필터그래프를 있는 그대로 래핑해서 넣진 않았고, 당장 나에게 필요한 플레이어 기능만 짜넣어서 익스텐션을 만들고 있다. 나중에 완성되면 여기다 공개하도록 하겠다.

2009. 8. 1. 14:18
인터넷을 뒤지다가 같은 제목의 글이 맨 앞에 나오길래 패스했었는데, 읽어보니 내 쪽이 더 간단한거 같아서 글로 남길만한 가치가 있을 거 같아 다시 쓰기로 했다 -_-..

차례
  • 소개
  • 소스코드
  • 설명
  • 참고
소개

어떤 프로그램을 해킹하려 할 때는, 그 프로그램의 메모리에 접근하는 것이 필수적이다. 옛날 DOS와 같은 메모리 보호 기능이 없던 OS에서는 어렵지 않게 해당 프로그램의 메모리에 접근할 수 있었지만, 요즘과 같이 멀티태스킹과 메모리 보호가 기본인 상황에서는 그렇게 접근하는 것이 쉽지 않다. 그래서 자주 이용되는 방법이 디버거를 사용하는 방법이다.

하지만 이 디버거를 사용하는 경우에는 상당히 번거로운 것이 사실이다. 간단한 작업만 하면 되는데 그 커다란 디버거를 불러와야한다거나, 디버깅 API를 사용하려면 여러가지로 준비할 것이 많다거나, 해당 프로그램에서 디버거 디텍션 루틴을 넣어놨다면 또 복잡해지기도 해서 좀 더 간단한 방법을 사용할 수 있다면 쓰는 편이 낫다.

여기서 소개하려는 방법은 CreateRemoteThread()라는 API 함수를 통해 타겟 프로세스에 내가 만들어둔 DLL을 주입시키는 방법이다. DLL은 별도의 프로그램이지만, LoadLibrary 함수 등을 통해서 동적으로 링크되면 해당 프로세스의 주소 공간 내에 매핑되어 마치 처음부터 원래 프로그램과 같이 실행된 것 처럼 메모리 공간을 헤집을 수 있다는 특징이 있다. 매번 디버깅 API를 통해 메모리에 접근하는 것이 아니라, 이러한 부분을 DLL로 만들어서 주입함으로써 더 간단하게 타겟 프로세스를 주무를 수 있다.

소스코드
hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
if(hProc == NULL)
{
    MessageBox(hWnd, TEXT("Failed to inject DLL!\nCannot open the process"), NULL, MB_ICONERROR);
    break;
}
pRemoteDll = VirtualAllocEx(hProc, NULL, sizeof(char) * MAX_PATH, MEM_COMMIT, PAGE_READWRITE);
if(!pRemoteDll)
{
    CloseHandle(hProc);
    MessageBox(hWnd, TEXT("Failed to inject DLL!\nCannot allocate memory in target process"), NULL, MB_ICONERROR);
    break;
}
if(!WriteProcessMemory(hProc, pRemoteDll, szDllName, sizeof(szDllName), NULL))
{
    VirtualFreeEx(hProc, pRemoteDll, 0, MEM_RELEASE);
    CloseHandle(hProc);
    MessageBox(hWnd, TEXT("Failed to inject DLL!\nCannot write memory in target process"), NULL, MB_ICONERROR);
    break;
}
hKernel32 = LoadLibrary(TEXT("KERNEL32.DLL"));
pfnLoadLibrary = (LPTHREAD_START_ROUTINE)GetProcAddress(hKernel32, TEXT("LoadLibraryA"));

hThread = CreateRemoteThread(hProc, NULL, 0, pfnLoadLibrary, pRemoteDll, 0, NULL);
if(!hThread)
{
    VirtualFreeEx(hProc, pRemoteDll, 0, MEM_RELEASE);
    CloseHandle(hProc);
    FreeLibrary(hKernel32);
    MessageBox(hWnd, TEXT("Failed to inject DLL!\nCannot create remote thread"), NULL, MB_ICONERROR);
    break;
}
WaitForSingleObject(hThread, INFINITE);

VirtualFreeEx(hProc, pRemoteDll, 0, MEM_RELEASE);
CloseHandle(hProc);
FreeLibrary(hKernel32);
if(GetExitCodeThread(hThread, &dwRet))
{
    TCHAR out[1024];
    _stprintf(out, TEXT("Injection finished.\nExit code: 0x%08lX"), dwRet);
    MessageBox(hWnd, out, TEXT("Finished"), MB_ICONINFORMATION);
}
설명

여기서 사용한 핵심 API는 다음과 같다.
HANDLE WINAPI CreateRemoteThread(
__in HANDLE hProcess,
__in LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in SIZE_T dwStackSize,
__in LPTHREAD_START_ROUTINE lpStartAddress,
__in LPVOID lpParameter,
__in DWORD dwCreationFlags,
__out LPDWORD lpThreadId
);
LPVOID WINAPI VirtualAllocEx(
__in HANDLE hProcess,
__in_opt LPVOID lpAddress,
__in SIZE_T dwSize,
__in DWORD flAllocationType,
__in DWORD flProtect
);
CreateRemoteThread()는, 원래는 “다른 프로세스에 스레드를 생성할 때” 사용하는 함수다. 맨 앞에서 지정해주는 hProcess로 대상 프로세스를 지정해 줄 수 있다. 그런데 여기서, 스레드의 시작 주소를 지정해주는 lpStartAddress를 살펴보면 재밌는 사실을 하나 발견할 수 있다.

LPTHREAD_START_ROUTINE의 정의는 다음과 비슷하다.
DWORD WINAPI ThreadProc(
__in LPVOID lpParameter
);
그리고 DLL를 불러오는 함수인 LoadLibrary의 정의는 다음과 같다.
HMODULE WINAPI LoadLibrary(
__in LPCTSTR lpFileName
);
보다시피, 타입만 약간 다르고 실질적인 함수 호출에 있어서는 두 함수가 서로 똑같다는 것을 알 수 있다. 만약 타겟 프로세스 내의 LoadLibrary() 주소를 알 수 있다면 CreateRemoteThread()에 스레드 시작 주소로 LoadLibrary()를 넘겨주어 어렵지 않게 DLL을 주입할 수 있다는 것이 된다. 그리고 또 하나의 희소식은, Windows는 빠른 동적 링크를 위해서 각 DLL마다 선호하는 Base Address가 기록되어 있으며 충돌하지 않는 한 Base Address는 항상 고정되어 있다는 것이다. 따라서, 제일 먼저 로드되기 마련인 KERNEL32.DLL의 base address는 어떤 프로세스가 됐든 특별한 일 없이는 전부 똑같다는 말이 된다!

그러면 VirtualAllocEx()는 왜 필요할까? 바로 LoadLibrary()의 패러미터를 넘기기 위해 필요하다. LoadLibrary()는 고맙게도 시작 주소가 모든 프로세스마다 동일하지만 패러미터로 넘겨줄 DLL의 이름은 타겟 프로세스 내에서 찾아야 한다. 당연히, 이쪽의 주소를 넘겨주게 되면 해당 프로세스 내에서는 쓰레기 값이 참조되거나, 심지어는 잘못된 메모리 접근이 발생할 수도 있다. 그래서, 패러미터로 넘겨줄 DLL의 path를 넘기기 위해 VirtualAllocEx()로 타겟 프로세스 내에 메모리를 할당하고, 반환된 그 주소를 CreateRemoteThread()의 lpParameter로 넘겨주게 되면 LoadLibrary()의 스레드가 동작하면서 DLL을 주입시켜주게 되는 것이다. 주입과 동시에 실행되어야 할 코드들은 DLL 내의 DllMain() 함수 내에 집어넣으면 수행되게 된다. 구체적인 것은 MSDN의 DllMain() 항목을 참조하는 것이 좋을 것이다.

주의할 점은, 이 방법이 100% 동작하지 않을 수 있다.

참고

  • 해킹, 파괴의 광학 (와이미디어, 김성우 저) - 복잡한 방법밖에 실려있지 않지만, 상당한 참고가 될 것이다.
꽤 예전에 배워서 구현한 파일인데, 그러다보니 참조했던 곳이 다 사라져버렸다 -_-;;
여튼, 나중에 소개할지도 모르는 DirectShow 기반의 간단한 스트리밍 서비스 뚫기 등에서도 API 후킹에 많이 써먹었던 매우 유용한 툴이라, 많은 사람들이 유용하게 쓸 수 있으리라 믿는다.

'크래킹, 크랙미' 카테고리의 다른 글

AD free NateOn  (0) 2010.05.18
COM Object 생성 추적하기, COMTracer  (0) 2009.10.05
DLL injection with CreateRemoteThread()  (0) 2009.08.01
CBsearch Crack  (15) 2009.06.20
abexcrackme2  (0) 2007.02.17
2008. 12. 23. 21:45
보통 ruby-gd는 gem install ruby-gd 로 깔곤 했는데 win32에선 이게 안돼서 꽤나 고생했다.

그래서 찾아본 결과, 예전에는 찾지 못했던 해결법을 발견하여 여기다가 쓴다.

  1. GD2 다운로드 받기
    libgd 홈페이지에서 직접 받은 gd2는 정상적으로 link가 안된다.
    따라서 다음 홈페이지에서 다른 것을 받도록 한다.
    http://www.vortex-tech.com/blog/2008/05/19/ruby-gd2-on-windows/
    다운 받은 파일 중 dll파일을 시스템 폴더나 ruby 실행파일이 있는 디렉토리(보통 c:\ruby\bin)에 복사해 넣는다.
  2. ruby-gd2 설치하기
    적당한 커맨드 라인창을 띄운 다음에(ex> cmd.exe)
    gem install gd2

    를 입력한다.
  3. require 'gd2' 로 시작한다.
참 쉽다. 왜 이걸 몰랐을까 -_-

'프로그래밍 > Ruby' 카테고리의 다른 글

Ruby-GNOME2 0.18 One-Click Installer for Windows  (0) 2009.10.20
rubyscript2exe 로 ruby-gnome2를 이용한 어플 패킹하기  (0) 2009.02.02
GMemRB 공개  (0) 2009.02.01
Ruby-GD2 in WIN32  (0) 2008.12.23
Ruby-GNOME2  (0) 2008.12.09
Ruby on Rails  (0) 2008.05.28
GD, gd2, ruby, Win32, Windows