며칠 전 우연히 감동적인 강좌를 하나 읽게 되었다. 


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 프로젝트 파일을 만들어 보았다. 


Winsock_Proxy_Src.7z




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__();
asm
  jmp [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 생성기"




ProxyDllGen.7z



실행시키고 DLL을 끌어다 던지면 Export 된 함수 목록을 보여준다. 

"Generate" 버튼을 누르면 해당 DLL의 델파이 버전 프록시 코드를 생성해준다. 


라자루스로 만들어봤는데, 못 본 사이에 참 좋아졌다. 터보델파이를 대체하기에는 조금 아쉽지만...





2017. 02.16


github 에 코드 올려둠. 

https://github.com/oranke/proxy-dll-generator



저작자 표시 비영리 변경 금지
신고
      Tag - , ,
      탐구생활/Delphi  |  2016.06.17 00:05
2016.06.23 13:12 신고 댓글에 댓글수정/삭제
대~한민국! ↖(^^)↗
2017.04.04 15:59 신고 수정/삭제
짝짝~짝짝~짝
.
2017.01.02 11:26 신고 댓글에 댓글수정/삭제
오!! 오랫만에 포스트가 올라왔군요!!
2017.02.16 11:47 신고 수정/삭제
행님~~ 오랫만에 방문해주셨군요~~ ^^
.
name ::   password :: blog :: secret
등록



오랑캐꽃's Blog is powered by Daum