폼 디자이너 갖구 놀기
탐구생활/Delphi델파이의 폼디자이너를 흉내내보았던 작업.
무려 17년 전 골동품이라 생소한 코딩과 풋풋한 코멘트가 어색하네.
마치 오래 전 일기장을 꺼내 읽는듯한 아스라한 느낌이 재미있다.
델파이 5로 만들어졌으며 최신버전에서 빌드되는지 여부는 체크해보지 않았다.
김현수님이 델파이 최신버전(10.2 도쿄)에서 동작한다고 확인해주심. (캄솨~ 압도적 캄솨~~)
델파이의 폼디자이너를 흉내내보았던 작업.
무려 17년 전 골동품이라 생소한 코딩과 풋풋한 코멘트가 어색하네.
마치 오래 전 일기장을 꺼내 읽는듯한 아스라한 느낌이 재미있다.
델파이 5로 만들어졌으며 최신버전에서 빌드되는지 여부는 체크해보지 않았다.
김현수님이 델파이 최신버전(10.2 도쿄)에서 동작한다고 확인해주심. (캄솨~ 압도적 캄솨~~)
며칠 전 우연히 감동적인 강좌를 하나 읽게 되었다.
http://www.codeproject.com/Articles/16541/Create-your-Proxy-DLLs-automatically
양병규 형님이 "API Hooking 요점정리" 의 13번 항목에서 "단순 무식하고 쉽지만 실용성이 없다"고 소개한 그 기술에 무려 "실용성"을 부여하는 강좌 되시겠다.
프록시 DLL을 만들 때 가장 어려운(귀찮은) 점은, DLL의 모든 함수에 대해 호출규약과 인자를 맞춰 원래 함수를 불러주는 일인데, 이 강좌에서는 이런 귀차니즘을 "__declspec(naked) " 라는 지시자를 써서 간단하게 해결하고 있다. 이 지시자는 VC++의 x86에서 지원되며, 호출시점의 스택상태를 그대로 유지하는 기능을 가진단다. (이번에 처음 구경함)
델파이에서 이런 효과를 내려면 어떻게 하나 고민해봤는데, 스택 구조만 유지한다면 대충 이렇게 해도 잘 되는군.
procedure Test();
asm
jmp [원래주소]
end;
몰라도 되는 녀석들은 이런식으로 처리하고, 실제 가로챌 함수들에 대해서만 신경 쓸 수 있다는 얘기.
그럼 이걸 어디에 쓰면 좋을까??
저 강좌와 마찬가지로 만만한 윈도 소켓 DLL을 가지고 놀아 보자.
소켓 API를 사용하면 대충 wsock32.dll -> ws2_32.dll -> mswsock.dll 의 흐름으로 관련 라이브러리들이 링크된다.
wsock32나 ws2_32 의 프록시를 어플리케이션과 동일한 디렉토리에 넣어두고 send() 나 recv() 같은 함수를 가로채면 패킷을 쉽게 감청할 수 있다.
델파이 코드를 뱉도록 강좌의 소스를 살짝 수정해 wsock32, ws2_32에 대한 프록시 DLL 프로젝트 파일을 만들어 보았다.
wsock32.dpr 의 주요부분은 다음과 같다.
library wsock32;
uses
Windows, SysUtils;
......procedure __DLLProc(Reason: Integer);
begin
case Reason of
DLL_PROCESS_ATTACH:
begin
hl := LoadLibrary(PChar(GetSysDirectory + '\wsock32.dll'));
if hl = 0 then Exit;
OrgFuncs.Arr[0] := GetProcAddress(hl, 'accept');
OrgFuncs.Arr[1] := GetProcAddress(hl, 'bind');
......OrgFuncs.Arr[73] := GetProcAddress(hl, 'AcceptEx');
......// AcceptEx
procedure __E__73__();asmjmp [OrgFuncs.Base + SIZE_OF_FUNC * 73]end;......
exports
__E__73__ index 1141 name 'AcceptEx',
__E__74__ index 1142 name 'GetAcceptExSockaddrs';
......
원본 DLL과 동일한 이름의 함수들을 Export 하고, 이 함수가 불릴 때 OrgFuncs 변수에 담아둔 원래 함수들의 주소로 점프시키고 있다. 호출시점의 스택구조를 유지하므로 원본 함수의 호출규약이나 인자값, 리턴값도 신경 쓸 필요가 없다.
이제 실제로 AcceptEx 함수의 동작에 간섭해보자. WinSock 유니트를 열어 이 함수가 어떻게 정의되어있는지 배껴온다.
function AcceptEx(sListenSocket, sAcceptSocket: TSocket;
lpOutputBuffer: Pointer; dwReceiveDataLength, dwLocalAddressLength,
dwRemoteAddressLength: DWORD; var lpdwBytesReceived: DWORD;
lpOverlapped: POverlapped): BOOL; stdcall;
__E__73__() 프로시저를 위 내용 그대로 재정의 해 보자.
// AcceptEx
function __E__73__(sListenSocket, sAcceptSocket: IntPtr;
lpOutputBuffer: Pointer; dwReceiveDataLength, dwLocalAddressLength,
dwRemoteAddressLength: DWORD; var lpdwBytesReceived: DWORD;
lpOverlapped: POverlapped): BOOL; stdcall;
type
TAcceptEx = function (sListenSocket, sAcceptSocket: IntPtr;
lpOutputBuffer: Pointer; dwReceiveDataLength, dwLocalAddressLength,
dwRemoteAddressLength: DWORD; var lpdwBytesReceived: DWORD;
lpOverlapped: POverlapped): BOOL; stdcall;
begin
// 파라미터 가지고 노는 곳
// ..........
// 원래 함수 호출
Result :=
TAcceptEx(OrgFuncs.Arr[73])(
sListenSocket, sAcceptSocket,
lpOutputBuffer, dwReceiveDataLength, dwLocalAddressLength,
dwRemoteAddressLength, lpdwBytesReceived, lpOverlapped
);
end;
호출규약과 인자를 맞추어주고 다시 원래 함수에 그대로 전달하고 있다. TSocket 은 다시 선언하기 귀찮아 원래 타입인 IntPtr 형을 사용했다.
리턴시 스택을 유지해야 하므로 원래 함수를 호출한 뒤 되도록 아무 일도 하지 말자. 지역변수 선언 또한 금물이니 주의하자.
2016.06.28 추가.
dumpbin 으로 얻어진 TXT 파일을 줏어담는것은 아무래도 모양이 빠져서...
독립적으로 실행되는 간단한 유틸리티를 만들어 보았다. 이름하여 "프록시 DLL 생성기"
실행시키고 DLL을 끌어다 던지면 Export 된 함수 목록을 보여준다.
"Generate" 버튼을 누르면 해당 DLL의 델파이 버전 프록시 코드를 생성해준다.
라자루스로 만들어봤는데, 못 본 사이에 참 좋아졌다. 터보델파이를 대체하기에는 조금 아쉽지만...
2017. 02.16
github 에 코드 올려둠.
https://github.com/oranke/proxy-dll-generator
쓰레드 코딩을 하다보면... 디버깅창에 줄줄이 나타난 쓰레드가 어디서 만든 뭐하는 녀석인지 궁금할 때가 있다.
이 때 각 쓰레드마다 이름을 붙여둔다면 심적으로나마 편안함을 얻을 수 있지 않을까...
물론 디버깅에서만 유효하지만, 좋은 내용이 있어 공유한다.
http://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx
http://blogs.msdn.com/b/stevejs/archive/2005/12/19/505815.aspx
const
MS_VC_EXCEPTION = $406D1388;
type
TThreadNameInfo = record
dwType: DWORD; // Must be 0x1000.
szName: PAnsiChar; // Pointer to name (in user addr space).
dwThreadID: DWORD; // Thread ID (-1=caller thread).
dwFlags: DWORD; // Reserved for future use, must be zero.
end;
procedure SetThreadName(aThreadID: DWORD; const aThreadName: PAnsiChar);
var
tmi: TThreadNameInfo;
begin
if (DebugHook = 0) then Exit;
tmi.dwType := $1000;
tmi.szName := aThreadName;
tmi.dwThreadID := aThreadID;
tmi.dwFlags := 0;
try
RaiseException(MS_VC_EXCEPTION, 0, sizeof(tmi) div sizeof(LongWord), PDWORD(@tmi));
except
end;
end;
맥북에어를 데려왔다. 가볍고 빠르다. 좋다.
윈도를 올려봤다. 응? 한영키가 없네? 어차피 키보드 타입 3를 쓰니까 패스!
터보델파이를 깔았다. 컨트롤+스페이스가 안먹는다. 우측 컨트롤키가 없으니 좌컨트롤+우컨트롤+스페이스 신공도 안통한다.
영문 입력기를 추가하고 알트+쉬프트를 며칠 써보니... 익숙치 않은 키입력 때문에 손가락이 꼬이는 느낌이다.
타입 3 키보드에서 컨트롤+스페이스는 한자변환키이다.
가만히 생각해보니 IME모드가 영문일 때 이 키는 별로 필요가 없다.
그렇다면? 입력 포커스를 가진 윈도가 한글입력 상태가 아닐 때 이 한자변환키를 무력화하고
좌컨트롤+우컨트롤+스페이스 키스트로크를 조립해주면 되겠군.
그래서 탄생한 물건이 이 놈.
한글 IME 에서 영문모드일 때 컨트롤+스페이스 입력을 좌컨트롤+우컨트롤+스페이스 로 변환해준다.
한 번 실행시키면 동작하고 다시 실행시키면 정지한다.
이제 좀 살만하네~~
------------
터보델파이로 만든 32비트 어플이지만, DLL을 침투시키지 않기 때문에 64비트 프로세스에서도 잘 돌아간다.
64비트 윈도7에 띄운 64비트 라자루스에서도 동작함.
DLL 만들기 귀찮아서 게으름을 피웠는데... 의도하지 않은 "사이드 이펙트"... 훗~!
귀차니즘은 역시 프로그래머의 가장 큰 미덕이다.
관리자모드로 실행시킨 어플에서도 동작하게 하려면, 이 유틸리티도 관리자모드로 띄울 것.
PHPer 님의 의견대로, 아무 파라미터나 지정되면 메시지박스를 띄우지 않도록 수정.
새로 받으세요~~
차일피일 미루다 이제야 github에 등록함.