티스토리 툴바




이전 분석한 결론은 다음과 같다.

1, LogonUser API는 내부적으로 LogonUserExExW를 호출한다.
2. 계정정보를 담을 수있는 구조체가 있는데 그구조체에 계정 정보 등 보안 명세 등을 담는다.
3. LogonUserExExW 는 정보가 있는 구조체에서 로컬 계정인지 원격지 계정인지 확인한다.

여기까지 해서 0x75739835 Intruction이 마지막 이었으나 다시 올리면 어짜피 주소가 달라질테다.


75669835    57              PUSH EDI                                 ; TestLogo.__native_startup_lock
75669836    83CF FF         OR EDI,FFFFFFFF
------------------------------------------------------------------------------------
EDI = 0040337C || 0xFFFFFFFF = 0xFFFFFFFF;
HANDLE hEventHdl = INVALID_HANDLE_VALUE;
핸들 선언하고 초기화 하는작업인데, 이전에 JMP로 분기되어 넘어 왔기때문에.
if(!(struct.Int & DEFINE_VALUE_MODE_@))
{
     struct.int = DEFINE_VALUE_MODE_@;
     //아마도 제가 짠소스에서 로칼 PC에 있는 계정에 들어갈때 사용되는 값이 2 인가 봅니다.
//그럼 선언문은 대충 #define LOCALHOST_ACCOUNT_LOGON_MODE 2 이정도 되겐네요
}
...
else
{
...
}

{
HANDLE hEventHdl = INVALID_HANDLE_VALUE;
}

하나의 함수 내에서 지역구분자 뒤로 넘어와서 선언된 변수 인듯.
그런데 생각해보니 PUSH EDI 만 했으니... 전역 변수 그냥 초기화 문일지도 모르겠다는 생각 막드네..;;;
------------------------------------------------------------------------------------
75669839    393D 40706775   CMP DWORD PTR DS:[75677040],EDI
7566983F  ^ 0F84 22F2FFFF   JE 75668A67
------------------------------------------------------------------------------------
75677040  FF FF FF FF = 0xFFFFFFFF
그러니 아까 OR연산으로 초기화된 핸들이 진짜 인발리드 한 핸들인지 확인해보니깐
아마 다음에는 점프 해서 갈 75668A67 여기에서는 핸들을 오픈 한다든지 하는 작업 하겠지.
if( hEventHdl == INVALID_HANDLE_VAUE)
{
//.... 여기가기 이 Instruction 위치임.
//__asm{  JE 75668A67 }
}
------------------------------------------------------------------------------------

75668A67    BE 50716775     MOV ESI,75677150
75668A6C    56              PUSH ESI
75668A6D    FF15 A0106675   CALL DWORD PTR DS:[756610A0] ntdll.RtlEnterCriticalSection
------------------------------------------------------------------------------------
RtlEnterCriticalSection 을 호출하기전 전역적으로 선언된 75677150 이변수에 초기화를 한다.
그치만 그럼 이함수의 원형을 보면
void EnterCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection
);
그러면 여기서 PUSH 한 ESI 의 값은 LPCRITICAL_SECTION 그이 되게 된다. 한마디로 크리티컬 섹션 처리를 하는 거니깐 전역 변수중 해당 구조체로 선언된 녀서의 포인터가 되게된다.
typedef struct _RTL_CRITICAL_SECTION {
    PRTL_CRITICAL_SECTION_DEBUG DebugInfo;

    //
    //  The following three fields control entering and exiting the critical
    //  section for the resource
    //

    LONG LockCount;
    LONG RecursionCount;
    HANDLE OwningThread;        // from the thread's ClientId->UniqueThread
    HANDLE LockSemaphore;
    ULONG_PTR SpinCount;        // force size on 64-bit systems when packed
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;

이구조체의 포인터다. 그냥 이건 이렇게 보면 된다는거만 보여줄라고 쓴거임 별내용없이 크리티컬 섹션 처리 해주는거 한마디로 특정작업을 할때 전역적인 데이터 레이스 방지를 위해서 처리 한거니 그닥 중요하진 않다.
(코드짤때는 멀티 쓰레드 환경에서 데이터 레이스 현상은 매우 중요할지 몰라도 분석할땐 이딴거 크게 중요 하지 않음 ㅋㅋㅋㅋ)
------------------------------------------------------------------------------------
75668A73    393D 40706775   CMP DWORD PTR DS:[75677040],EDI
75668A79  ^ 0F85 96BDFFFF   JNZ 75664815
------------------------------------------------------------------------------------
이부분에서 아까 점프하기전 초기화한 전역 변수 핸들 hEventHdl 이 IVALID_HANDLE_VALUE인지 확인 하는군요 그리고 INVALID면 75664815 로 가게 되는데 아까 초기화 해서 유효하지 않은 값을 넣었으니 같다 그럼 CMP Instruction은 참이되고 그럼 ZeroFlag는 1로 셋팅. 이말인 즉슨

if(hEventHdl != INVALID_HANDLE_VALUE)
{
       //75664815 여기
}
그래서 이부분에서는 그냥 넘어가게되고 아래 명령문 실행 될꺼임.

어짜피 이런코들은 거의 핸들 초기화하고 넘어온 핸들 현재 상태를 확인해서 핸들 오픈할려하는거 같은데, 왜 초기화하고 확인다시 하냐는건 API 자체가 멀티스레드 환경을 지원한다면 항상 전역 변수에 대한 값이 이전에 확인한 값이라는 보장을 하지 못하게된다. 그러면 항상 확인하고 더이상 그값에 대해 다른 쓰레드가 바꾸지 않게끔 해줘야 한다. 그래서 앞전에 크리티컬 섹션 처리를 해서 다른 쓰레드는 대기 할수 있도로 한거.
------------------------------------------------------------------------------------
75668A7F    E8 19000000     CALL 75668A9D
------------------------------------------------------------------------------------
예상한대로 이부분에서 핸들을 열지 않을까 한다.
그러니 아래에 브레이크를 건다음에 안으로 스텝인투 해보자
------------------------------------------------------------------------------------
75668A84    85C0            TEST EAX,EAX ; 브레이끼
75668A86    0F84 7A730000   JE 7566FE06
75668A8C    56              PUSH ESI
75668A8D    FF15 9C106675   CALL DWORD PTR DS:[7566109C]  ntdll.RtlLeaveCriticalSection
75668A93    E9 B90D0000     JMP 75669851

75668A9D    8BFF            MOV EDI,EDI
75668A9F    55              PUSH EBP
75668AA0    8BEC            MOV EBP,ESP
//항상 함수의 std콜중 마소 지네들 패치 편하자고 5바이트 의미 없는코드 넣는다 별로 의미 없음.
75668AA2    83EC 10         SUB ESP,10 //스택 세그먼트 맞추기
/////////////////////////////////////////////////////////////////////////////////////////
75668AA5    56              PUSH ESI
75668AA6    57              PUSH EDI
75668AA7    68 70716775     PUSH 75677170
75668AAC    E8 E6EEFFFF     CALL 75667997
------------------------------------------------------------------------------------
ESI = 75677150 ->00 57 30 00  = 00305700("^\0^")
EDI = FFFFFFFF
75677170  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
75677180  93 1F 66 75 74 25 67 75 94 73 66 75 45 34 67 75  ?fut%gu봲fuE4gu
75677190  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
756771A0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
756771B0  00 00 00 00 00 10 00 00 00 00 01 00 FF FF FE 7F  ..........?
756771C0  0F 00 00 00 04 00 00 00 4A 02 00 00 00 00 01 00  ......J.....

지금 ESI 값은 아까 크리티컬 섹션 처리 할때 썻던 매게 변수인데 이거 전역 변수이다.
그리고 CALL 안으로 가보자
------------------------------------------------------------------------------------
75668AB1    8BF8            MOV EDI,EAX < 브레이끼

 75667997    8BFF            MOV EDI,EDI
75667999    55              PUSH EBP
7566799A    8BEC            MOV EBP,ESP
7566799C    83EC 34         SUB ESP,34
//스텍 세그먼트 맞추는 작업
7566799F    53              PUSH EBX
756679A0    56              PUSH ESI
756679A1    57              PUSH EDI
756679A2    68 40796675     PUSH 75667940                         
[75667940  ; UNICODE "\SECURITY\LSA_AUTHENTICATION_INITIALIZED"]
756679A7    8D45 E4         LEA EAX,DWORD PTR SS:[EBP-1C]
756679AA    33FF            XOR EDI,EDI
756679AC    50              PUSH EAX
756679AD    897D FC         MOV DWORD PTR SS:[EBP-4],EDI
756679B0    FF15 88106675   CALL DWORD PTR DS:[75661088] ntdll.RtlInitUnicodeString
------------------------------------------------------------------------------------
Unicode String Initialyze Routine
 -> ZwOpenEvent (열려는 대상은\SECURITY\LSA_AUTHENTICATION_INITIALIZED
     호출 하기위해서 매계변수들에 대한 처리 작업중인데 이부분은 우선 두가지 작업
- RtlInitUnicodeString을 호출하여 연결대상 네임버퍼를 초기화하고있다.

UNICODE_STRING ustrEventNamedPipe = {0,};
RtlInitUnicodeString(&ustrEventNamedPipe,
                             L"
\\SECURITY\\LSA_AUTHENTICATION_INITIALIZED");

와 같은 소스코드로 바꿀수 있다.

------------------------------------------------------------------------------------
756679B6    8B1D 8C106675   MOV EBX,DWORD PTR DS:[7566108C]          ; ntdll.ZwOpenEvent
------------------------------------------------------------------------------------
ZwOpenEvent의 함수 포인터를 지역변수로 옮긴다.
PFN_ZW_OPEN_EVENT pfn_ZwOpenEvent = (PFN_ZW_OPEN_EVENT)ZwOpenEvent;
------------------------------------------------------------------------------------
756679BC    8D45 E4         LEA EAX,DWORD PTR SS:[EBP-1C]
756679BF    8945 D4         MOV DWORD PTR SS:[EBP-2C],EAX
756679C2    8D45 CC         LEA EAX,DWORD PTR SS:[EBP-34]
756679C5    50              PUSH EAX
756679C6    68 00001000     PUSH 100000
756679CB    8D45 F8         LEA EAX,DWORD PTR SS:[EBP-8]
756679CE    50              PUSH EAX
756679CF    C745 CC 1800000>MOV DWORD PTR SS:[EBP-34],18
756679D6    897D D0         MOV DWORD PTR SS:[EBP-30],EDI
756679D9    C745 D8 4000000>MOV DWORD PTR SS:[EBP-28],40
756679E0    897D DC         MOV DWORD PTR SS:[EBP-24],EDI
756679E3    897D E0         MOV DWORD PTR SS:[EBP-20],EDI
------------------------------------------------------------------------------------
OBJECT_ATTRIBUTE 구조체 초기화
OBJECT_ATTRIBUTE objattr = {0,};
//구조체 초기화
InitializeObjectAttributes(objattr,&ustrEventNamedPipe,100000,...);
머 이런작업 세세한건 각자 해보시길...귀차네...
------------------------------------------------------------------------------------
756679E6    FFD3            CALL EBX
------------------------------------------------------------------------------------
 pfn_ZwOpenEvent(&hEventHdl,0xFFFFFFFF/*Desired 값 귀찮음....확인하기*/,)

머 이렇게 대충해서 Call일이 난뒤에 로그인 이벤트가 완료 되는걸 기다리겠지.

이밑으로는 다음에 WinDbg로 커널 디버깅해서 또 블러깅 하겠음 머 암튼 이렇게해서 누구인지는 몰라도 해당 이벤트를 받아 처리하게 된다.
이 이벤트를 처리하는 녀석이 누구일까 싶어 후킹해서 알아볼려 한다. 다음에는 후킹을 통해서 오브젝트 네임 파이프가 \\SECURITY\\LSA_AUTHENTICATION_INITIALIZED 이녀석일때 어떤짓을 하는지 확인해보려한다. 물론 ZwOpenEvent를 후킹 해야 할것이다. 궁금한분들 한번 해보시길 ㅋㅋㅋ 윈도우 로그인 과정 파보면 먼가 재밌는것도 할수 있을듯 한데 말이지. ㅋㅋㅋ
머 어쨋든 여기까지 정리 한번 해보겠음.

1. 누가 받아 처리하는 이벤트 인지는 몰라도 APC레벨로 돌아가는 이벤트를 하나 깨움으로해서
로그인 과정이 이루어 지는듯하다.
2. 윈도우 로그인 과정은 LogonUser를 호출한 프로세스에서 Dispatch되지 않고 다른 시스템 프로세스에 로그인 이벤트를 알려 처리된다.
3. 로그인이벤트 과정중에 \\SECURITY\\LSA_AUTHENTICATION_INITIALIZED 이러한 파이프 네임이 무언지는 몰라도 보안 속성관련되어 이벤트를 이르킨다.

그리고 하나로 지금까지는 유저모드에서 분석 결과를 받았지만 앞으로 해야 할것은 아래와같다.
1. ZwOpenEvent가 호출되었으므로 커널로 진입하였다.
2. 진입할때 Event를 호출하여 이미 IRQL 은 APC레벨이니깐 분명 이벤트 Set되어 처리될것이다.
3. 그럼 2를 수행하기위해 특정 프로세스가 해당 이벤트를 처리 하기위해 다시한번 올라오게 될텐데. 이걸 확인하기 위해서는 ZwOpenEvent가 어느 루틴으로 흘러가는냐가 중요할것이다.

사실 Event핸들을 등록할때는 네임 파이프가 커널로 진입하면서 이벤트들이 활성화되는걸로 안다.
그럼 ZwOpenEvent에서는 CallBack이라든지 유저 프로세스또는 서비스와 노티할수 있는 Call이 생길것이다. 아마 Noti를 위해서 Noti Event변수를 깨우지 않을까 하는 짐작을 해본다.

어쨋든 이렇게 해서 대충 결론은 내려진듯하다.

------------------------------------------------------------------------------------

1. LogonUser API는 호출되면 해당 프로세스에서 처리되는것이 아니라 특정 이벤트에 의해 처리된다.
2. LogonUser API 이벤트 활성화 API다.
3. 그럼 커널로 진입할때 ZwOpenEvent를 통해 진입

다음 분석 블로깅할때는 ZwOpenEvent를 후킹해서 저녀석을 뜯어 봐야 할듯하다.

그럼 아직 끝난게 아니니 오늘은 바쁘기도 하고 아무튼 해서 여기까지 하고 다음 블로깅땐 더 자세하게 파해쳐 봐야 게다.

 다들 즐프~

음 머 일단 이건 관심있는사람들만 보시길.
어제부터 이슈가 하나 생겨서 그거 해결할려고 대략 API하나를 좀 뜯어봣는데 재밌는걸 봤음.
BOOL LogonUser(
  LPTSTR lpszUsername,
  LPTSTR lpszDomain,
  LPTSTR lpszPassword,
  DWORD dwLogonType,
  DWORD dwLogonProvider,
  PHANDLE phToken
);

머 대략 이렇게 생겨 먹은놈임.
이녀석 쫌까봐야 할일이 생겨서 까봤음

우선 MSDN보면 이녀석은 윈도우 사용자로 로그인 할수 있고 로그온된 계정 통해 해당 사용자 오너의 프로세스도 만들수 있고 머 이딴놈임.

크게 머 나랑 상관은 없지만 분석한김에 이제 슬슬 블로깅도 해볼까해서 올림.

우선 소스코드를 조금 짜봤음

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])
{

 HANDLE hToken = NULL;
 BOOL bIsLogOn = FALSE;
 bIsLogOn = LogonUserW(L"TestAccount",
L"",
L"",
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
&hToken);
  
 if(bIsLogOn == TRUE)
 {
  printf("Success");
  CloseHandle(hToken);
 }

 return 0;
}

우선 LogonUser Call 하는 부분에서 BP하고 안으로 쫒아 가봤지
7608C5C9 >  8BFF            MOV EDI,EDI                              ; TestLogo.__native_startup_lock
7608C5CB    55              PUSH EBP
7608C5CC    8BEC            MOV EBP,ESP
......
7608C5EA    E8 09000000     CALL ADVAPI32.7608C5F8
......
->
7608C5F8    8BFF            MOV EDI,EDI                              ; TestLogo.__native_startup_lock
7608C5FA    55              PUSH EBP
.......
7608C61A    FF75 0C         PUSH DWORD PTR SS:[EBP+C]
7608C61D    FF75 08         PUSH DWORD PTR SS:[EBP+8]
7608C620    E8 99FFFFFF     CALL ADVAPI32.7608C5BE
...

7608C5BE  ^\FF25 A8300F76   JMP DWORD PTR DS:[760F30A8]; ADVAPI32.76082BC6
----------------------------------------------------------------------------------------
0012FEBC   7608C625  RETURN to ADVAPI32.7608C625 from ADVAPI32.7608C5BE
0012FEC0   004020F8  UNICODE "TestAccount"
프로그램 스택상황
----------------------------------------------------------------------------------------

76082BC6    B8 A8300F76     MOV EAX,ADVAPI32.760F30A8
76082BCB    E9 A4020100     JMP ADVAPI32.76092E74
->
76092E74    51              PUSH ECX - 0x12FF44
76092E75    52              PUSH EDX - 0x00000000
76092E76    50              PUSH EAX - 0x760F30A8
76092E77    68 74E50E76     PUSH ADVAPI32.760EE574
76092E7C    E8 FA8E0000     CALL ADVAPI32.7609BD7B
----------------------------------------------------------------------------------------
콜위치까지 총4개정도의 값을 푸시함 그럼 저 함수의 파라미터는 4개
0012FEAC   760EE574  ADVAPI32.760EE574
0012FEB0   760F30A8  ADVAPI32.760F30A8
0012FEB4   00000000
0012FEB8  /0012FF44
0012FEBC  |7608C625  RETURN to ADVAPI32.7608C625 from ADVAPI32.7608C5BE

스택상 주소값으로 보이는 메모리
760EE574  01 00 00 00  - 0x00000001
760F30A8  C6 2B 08 76 - 0x76082bc6 -> 76082BC6  B8 A8 30 0F - 0x0F30A8B8
----------------------------------------------------------------------------------------
7609BD7B    8BFF            MOV EDI,EDI                              ; TestLogo.__native_startup_lock
...
7609BDD0    E8 BA000000     CALL <JMP.&KERNEL32.LoadLibraryExA>
-------------------------------------------------------------------
0012FE60   760EE6A0  |FileName = "SspiCli.dll"
0012FE64   00000000  |hFile = NULL
0012FE68   00000000  \Flags = 0

이정도 값이 들어가고 SspiCli.dll 을 로딩했네요
-------------------------------------------------------------------
...
7609BE34    E8 40000000     CALL <JMP.&KERNEL32.GetProcAddress>
-------------------------------------------------------------------
0012FE64   75A20000  |hModule = 75A20000
0012FE68   760EEB7C  \ProcNameOrOrdinal = "LogonUserExExW"
LogonUserExExW 이녀석을 받아서 처리 하는데 그럼 이녀석의 주소로 들어가는
콜은 당연히 봐야 하니 이 값이 저장된 EAX를 확인 75A297D3
-------------------------------------------------------------------
...
7609BE63    C2 0800         RETN 8

여기 까지 쫒아 와보다 보니 너무 잡다한 녀석을 쫓아 온듯했는데 하나 건진건 중간단계 벗어나서
75A297D3 에서부터 스텝밟으면 된다는 성과는 궈둔셈. 그런데 중간에 컴이 뻗어서 주소 -_- 다시 디버깅 최종적으로 757397D3 부터 밟아봄.
그러면 처음에 쫓아 드간 녀석내부에서 하는 일은 LogonUserExExW 를 얻기위한 작업 이라는!

이제부터 본격적으로 LogonUser의 실채를 낮낮히 파해쳐 봐욜
757397D3    8BFF            MOV EDI,EDI                              ; TestLogo.__native_startup_lock
757397D5    55              PUSH EBP
757397D6    8BEC            MOV EBP,ESP
....
757397F5    395D 08         CMP DWORD PTR SS:[EBP+8],EBX
757397F8    74 14             JE    SHORT 7573980E
----------------------------------------------------------------------------------------
여기에서는 0012FEC0   |004020F8  UNICODE "TestAccount"
이값이 NULL인지 체크 하는 부분임 한마디로
if(strArg == NULL)
{
__asm{
              JE SHORT 7573980E
          }
}
---------------------------------------------------------------------------------------
757397FA    8B45 0C         MOV EAX,DWORD PTR SS:[EBP+C]
757397FD    3BC3            CMP EAX,EBX
757397FF    0F84 D3650000   JE 7573FDD8
---------------------------------------------------------------------------------------
EPB = 0012FEB8 + C = 0012FEC4 -> 004020F4
EBX = 00000000

004020F4 00 00 00 00 54 00 65 00 73 00 74 00 41 00 63 00  ....T.e.s.t.A.c.
00402104  63 00 6F 00 75 00 6E 00 74 00 00 00 53 75 63 63  c.o.u.n.t...Succ
00402114  65 73 73 00 48 00 00 00 00 00 00 00 00 00 00 00  ess.H...........
00402124  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00402134  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00402144  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

CMP EAX,EBX 실행 후 Jump Equal이 성립되지 않고 아래 코드실행
 ---------------------------------------------------------------------------------------
75739805    66:3918         CMP WORD PTR DS:[EAX],BX
75739808    0F84 CA650000   JE 7573FDD8
---------------------------------------------------------------------------------------
드디어 점프 합니다. 7573FDD8 여기로 점프~ 이제 먼가 진짜 파라미터 체크 작업 완료하고 진짜 알짜알짜배기로 갈모양임.
보면 거의 이런코드는 switch() 로 되었다고 보면 되고 아래에 분기분들은 이제 필요 없이 넘어감.
---------------------------------------------------------------------------------------

...
이제 LogonUserExExW 에 진짜 머하면서 하는지 볼차례인듯

지금 부터는 7573FDD8 이 주소에서 부터 디버깅~

7573FDD8    6A 40           PUSH 40
7573FDDA    FF75 08         PUSH DWORD PTR SS:[EBP+8] 
7573FDDD    FF15 08107375   CALL DWORD PTR DS:[75731008]             ; msvcrt.wcschr
// 이부분에서 찾는 '@' 이문자를 찾는데 계정정보에 '@' 이 붙으면 멀까? 아마 원격지 로그인 을 뜻하는듯하다.
7573FDE3    59              POP ECX
7573FDE4    59              POP ECX
7573FDE5    85C0            TEST EAX,EAX
7573FDE7  ^ 0F85 219AFFFF   JNZ 7573980E
7573FDED    8975 18         MOV DWORD PTR SS:[EBP+18],ESI
7573FDF0  ^ E9 D949FFFF     JMP 757347CE
//여기서 757347CE로 쩜프하는데 아마 원격지 컴퓨터 접근이 아니면 다른 루틴에서 처리되나 보다.

757347CE    837D 18 04      CMP DWORD PTR SS:[EBP+18],4
757347D2    0F86 36500000   JBE 7573980E
---------------------------------------------------------------------------------------
EBP = 0012FEB8 + 18 = 0012FED0 -> 00000002

if( 2 <= 4 )
{
   __asm{JBE 7573980E}
}
음 7573980E 여기로 찾아 가겠군요.
--------------------------------------------------------------------------------------
....

7573980E    8B45 14         MOV EAX,DWORD PTR SS:[EBP+14]
75739811    2BC6            SUB EAX,ESI
75739813  ^ 0F84 F4AFFFFF   JE 7573480D
...
7573480D    8975 14         MOV DWORD PTR SS:[EBP+14],ESI
75734810    E9 20500000     JMP 75739835
--------------------------------------------------------------------------------------
EBP = 0012FEB8 + 14 = 0012FECC -> 00000002
ESI = 00000002
EAX = 2 - 2
EAX = struct.Int - ESI
if(!(struct.Int & DEFINE_VALUE_MODE_@))
{
     struct.int = DEFINE_VALUE_MODE_@;
     //아마도 제가 짠소스에서 로칼 PC에 있는 계정에 들어갈때 사용되는 값이 2 인가 봅니다.
//그럼 선언문은 대충 #define LOCALHOST_ACCOUNT_LOGON_MODE 2 이정도 되겐네요
}
...
else
{
...
}
//여기로 오게됨 - 75739835

이렇게 되면 75739835 로 다시 점프한다. 보니 분기가 엄청 많나 보다 계속 점프 점프 읔...
--------------------------------------------------------------------------------------
여기 까지 분석 내용은 대충 아래와 같다.

1, LogonUser API는 내부적으로 LogonUserExExW를 호출한다.
2. 계정정보를 담을 수있는 구조체가 있는데 그구조체에 계정 정보 등 보안 명세 등을 담는다.
3. LogonUserExExW 는 정보가 있는 구조체에서 로컬 계정인지 원격지 계정인지 확인한다.
4. 그리고 다음 라인은 내일 올리고 퇴근한다. ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

//find first file
   ntStatus = ZwQueryDirectoryFile(
           hRootHdl,
           NULL,
           NULL,
           NULL,
           &IoStatusBlock,
           pBothDir,
           sizeof(BOTH_DIR_INFO),
           FileFullDirectoryInformation,
           TRUE, 
           NULL,
           FALSE
           );


//find next File
ntStatus = ZwQueryDirectoryFile(
            hRootHdl,
            NULL,
            NULL,
            NULL,
            &IoStatusBlock,
            pBothDir,
            sizeof(BOTH_DIR_INFO),
            FileFullDirectoryInformation,
            TRUE,
            NULL,
            FALSE
            );

이렇게 하면 Enum이된다 마지막 인자와 뒤에서 3번째 인자가 중요함.ㅋ

아 디렉토리는 열거 할일이 드라이버에서는 없었는데 생기다 보니 캐삽질만 했구려.

이젠 알겟으~ 또하나 건져다. ㅎ

IAT / EAT 후킹

 - IAT / EAT 후킹의 경우 Process Memory 영역 내의 Import Address Table (IAT) / Export Address Table 영역의 API 또는 Export 된 Function 과 바꾸는 기법으로 RootKit의 기법으로 많이 활용된다.

1. IAT Hooking
   - 악성 코드들은 실행 프로세스(MMF image)에 직접 IAT영역을 찾는다.
   - 이때 IAT영역중 자신이 수행하고자하는 API 와 기존의 Import 되어 있던 API와 바꾸게 된다.
   - 바꾸어진 IAT 영역의 주소는 서비스 될때에 악성 코드의 API를 거친후 원래의 API로 통하수 있게 구현되어진다.

   * 이때 중요한것은 IAT후킹이 된 PE MMF Image는 자신의 기능을 수행할수 있으나 악성코드도 함께 실행되게 된다.

2. EAT Hooking
   - 실행중인 Process영역의 Load된 DLL Image의 EAT를 찾은 후 IAT와 같은 방법으로 주입이 가능하다.
 

진단 방법.
   * 원래의 실행파일(PhysicalDrv 내의 파일)을 MMF(Memory Mapped File)Image로 생성한뒤에 이생성된 Image의 IAT
     주소들과 실행중인 Process Memory 영역의 MMF Image 내에 IAT 주소와 1:1 맵핑을 통하여 진단해낼수 있다.
    - EAT후킹도 IAT와 같은 방법으로 찾아 낼수 있다.

2. SSDT Hooking

SSDT Hooking의 경우 NativeAPI(SYSTEM CALL Service API)를 Hooking하는 방법으로 실제 Windows System에서는 SYSENTER / INT 2E와 같은 방법으로 User Mode -> Kernel 모드로 진입하게 된다. 이때 KiServiceDescriptorTable의
Address Table에 의해 System Service Function의 실제 위치를 알수 있으며 SSDT 후킹은 이런한 Service Table을 조작함으로 Hooking이 가능해진다.(물론 Ring 0의 권한으로 구현되므로 Driver 로 제작 되어지며 Driver 외에 Driver Loader와 필요하다면 DLL도 존재한다.)

설계는 IAT/EAT와 같이 자신이 원하는 코드를 실행할수 있게끔 설계 되어진다.

이러한 SSDT후킹은 대부분 은닉을 위한 방법으로 사용되어지며 악성파일들을(악성코드에게는 자신의 유용한 정보를 감추기위한 은닉) 자신을 숨기기 위한 방법으로 많이 사용되어진다.

이러한 은닉을 위한 방법을 사용하게 되면 Process / File / Service 까지도 조작이 가능해지며, hooking이 되게 되면 해당 NativeAPI 를 호출하는 WIN32API의경우 이들을 찾아낼수 있는 방법이 없게 된다.

진단 방법.
 SSDT 후킹의 진단은 인터넷에서 많이 찾아 볼수 있다. 그중 가장 많이 사용되면서도 효율적인 방법은(필자가 생각하기에는) IAT/EAT와 같은 방법으로 ntoskrnl.exe / ntkrnlpa.exe 같은 System Service의 Export된 Function들을 가상으로 Mapping하여 찾는 방법이 있다.

또다른 한가지는 Shadow SSDT 를 활용한 방법으로 실제와 같은 모양의 Process영역을 만들어 실행 하게끔 돌려 놓는것이다. 이방법은 매우 강력하지만 실제로 또다시 후킹이 이루어질경우 또다른 영역을 만들어야 될거 같다...

이 외에도 몇가지의 방법이 있으며 다른 방법들도 연구하여 고안해 낼수 있을거같다...ㅋㅋㅋ 어렵겠지만...;;

-- 펌금지 보기만 하세요..-_-;; 잘몰라서...ㅋㅋㅋㅋ







#include <windows.h>
#include <stdio.h>
#include <winver.h>

#pragma once
#pragma comment(lib, "version")

CHAR * GetCoporationString(CHAR *szFileName,CHAR *szCorpName){
 DWORD dwNull, dwSize, x;
 UINT dwBytes;
 VS_FIXEDFILEINFO *VersionInfo;
 PBYTE pData;
 DWORD *pLangs;
 char sQuery[200];
 char *CompanyName = NULL;
 dwSize = GetFileVersionInfoSize(szFileName,&dwNull);
 
 pData = (BYTE *)malloc(dwSize);
 
 if(pData == NULL)
 {
  return NULL;
 }
 if(!GetFileVersionInfo(szFileName,NULL,dwSize,pData))
 {
  if(pData != NULL){
   free(pData);
  }
  return NULL;
 }
 if(!VerQueryValue(pData,TEXT("\\"),(void**)&VersionInfo,&dwBytes))
 {
  if(pData != NULL){
   free(pData);
  }
  return NULL;
 }
 if(!VerQueryValue(pData,TEXT("\\VarFileInfo\\Translation"),(void**)&pLangs,&dwBytes))
 {
  if(pData != NULL){
   free(pData);
  }
  return NULL;
 }
 
 sprintf(sQuery,"\\StringFileInfo\\%04x%04x\\CompanyName",LOWORD(pLangs[0]),HIWORD(pLangs[0]));
 if(!VerQueryValue(pData,TEXT(sQuery),(void**)&CompanyName,&dwBytes))
 {
  if(pData != NULL){
   free(pData);
  }
  return NULL;
 }

 szCorpName = (char *)malloc(strlen(CompanyName)+1);
 memset(szCorpName,0x00,strlen(CompanyName)+1);
 strcpy(szCorpName,CompanyName);

 if(pData != NULL){
  free(pData);
 }
 
 return szCorpName;
}

회사 이름 문자열 정보를 획득할수 있다.

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!실제 사용을 위해서 맞춰진 함수!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

숨겨진 프로세스를 찾는 방법들

1. EPROCESS의 ActiveProcessLinks Linked List를 이용해서 Traverse.
( 왠만한 루트킷들은 이 값을 조작하므로 별로 소용없을지도 모르지만, ZwQuerySystemInformation()을 후킹하여 결과값을 조작하는 식으로 숨기는 경우는 이 방법으로 손쉽게 찾아낼 수 있습니다. )

2. ZwOpenProcess() Brute-Force Detection
유효 PID인 0L부터 0xFFFFL까지 4의 배수들을 모두 Open해서 성공적으로 열어지는 프로세스를 감지합니다. 단, 프로세스가 종료되었으나 핸들이 닫히지 않은 경우에도 Open되므로 추가적인 확인이 필요합니다.

3. PspCidTable Traverse
Windows NT에는 PspCidTable이라는 Unexported Symbol이 존재하는데, 프로세스와 스레드에 대한 개체 포인터들을 저장하고 있는 핸들 테이블의 일종입니다. 이를 트레버싱하여 숨겨진 프로세스를 찾을 수도 있습니다.
( 개체 포인터만 저장되어있으므로, 포인터-0x18 한 값이 가리키는 OBJECT_HEADER 헤더의 Type 필드가 PsProcessType인지 검사해줄 필요가 있습니다. )

4. Process Handle Table Link Traverse
EPROCESS에는 HandleTable 필드가 존재하고 이 안에는 링크드 리스트가 존재합니다.
이 리스트를 이용해서 트레버싱하면 모든 EPROCESS를 찾을 수 있습니다.

5. CSRSS.EXE의 Handle Table Traversing
CSRSS.EXE 프로세스는 프로세스 시작을 커널에 통지하고 그 뒷처리를 하는 역할을 하기도 합니다.
(BaseSetProcessCreateNotify라는 Unexported/Undocumented Symbol을 이용합니다.
이 심볼 포인터를 후킹함으로써 User Mode에서 폴링(Polling) 과정 없이 프로세스 시작을 감지하는 방법을 구현한 적이 있습니다. http://blog.naver.com/startgoora/130026875156 )

때문에 CSRSS.EXE에는 모든 프로세스에 대한 핸들이 저장되어있습니다.

* 참고적으로 이를 반대로 이용하여 NtOpenProcess()를 거치지 않고 프로세스를 여는 방법중의 하나로 NtQuerySystemInformation()으로 모든 핸들을 얻어온 후 NtDuplicateObject()를 사용하여 핸들을 복사할 수 있습니다.

6. Memory Scanning
Process 개체 이전에 붙는 OBJECT_HEADER로 Scan하는 방법입니다.
http://vbdream.tistory.com/entry/Kernel-탐구-숨겨진-EPROCESS를-찾는-방법-1-Memory-Scanning에서 PoC(Proof-of-Concept) 코드로 올린 바가 있습니다.

7. ETHREAD로 부터 프로세스 개체 얻기
ETHREAD::ThreadsProcess 필드를 응용하면 EPROCESS의 Pointer를 취득할 수 있습니다.
PspCidTable이나 링크드 리스트를 이용해서 ETHREAD를 구할 수 있습니다.
그리고 EPROCESS의 UniqueProcessId가 조작되어도 ETHREAD::Cid를 이용하면 원래 PID를 구할 수도 있습니다.

8. PsSetCreateProcessNotifyRoutine()
프로세스 생성과 종료에 대한 콜백을 등록할 수 있습니다.
드라이버 로드시에 모든 프로세스를 얻어놓고 이 콜백에 의존하여 리스트를 빼거나 추가함으로써 순수한 프로세스 리스트를 얻을 수가 있겠죠.
(비슷한 것으로 PsSetCreateThreadNotifyRoutine와 PsSetLoadImageNotifyRoutine가 있습니다.)
 
9. CSRSS.EXE::BaseSetProcessCreateNotify Hook
위에서도 잠깐 언급하였으며 http://blog.naver.com/startgoora/130026875156 를 참고하세요.
이 방법은 프로세스 시작만 감지할 수 있다는 단점이 있습니다.
( 뭐... CSRSS.EXE의 ZwClose를 후킹하면 될지도 모른다는 생각이 드는군요. :-) )
1 

글 보관함

카운터

Total : 23,673 / Today : 5 / Yesterday : 1
get rsstistory!