겸손한 개발을 위한 자양분


함수 호출로 인한 스택 구성시,
처음에, 이전의 EBP 레지스터 값을 스택에 저장한다.

이를 이용해 현재의 EBP + 0x4 에서 함수의 Return Address 를 찾을 수 있다.
이 방법을 반복하여, 함수 콜 관계를 확인할 수 있다.


예제 )
void __fastcall WhoCallMe()
{
 PVOID pEBP;
 PVOID pStackFrame;
 DWORD dwReturnAddr;
 DWORD dwArgs[3];
 CHAR sMsg[512];
 //__asm mov eax, [ebp+4]
 //__asm mov dword ptr [dwReturnAddr], eax
  __asm mov  pEBP, ebp
 
 do{
 
  pStackFrame = pEBP;
  pEBP  = (PVOID)*(PDWORD)(pEBP);
  
  dwReturnAddr = *(PDWORD)((DWORD)pStackFrame +4);
  dwArgs[0] = *(PDWORD)((DWORD)pStackFrame +8);
  dwArgs[1] = *(PDWORD)((DWORD)pStackFrame +12);
  dwArgs[2] = *(PDWORD)((DWORD)pStackFrame +16);
  sprintf(sMsg, "[DBG] StackPtr:0x%08x CallAddr:0x%08x Args[0]:%08x Args[1]:%08x Args[2]:%08x\n",
   pEBP, dwReturnAddr, dwArgs[0], dwArgs[1], dwArgs[2]);
  OutputDebugString(sMsg);  
 }while(dwReturnAddr != 0);
 
}
void fnC(void *a, void *b, void *c)
{
 WhoCallMe();
 return;
}
void fnB(char a, char b)
{
 fnC((PUCHAR)1, (PUCHAR)2, (PUCHAR)3);
 return;
}
BOOL fnA(int a)
{
 fnB('1', '2');
 return 0;
}
int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
  // TODO: Place code here.
 fnA(1);
 return 0;
}

결과
[2416] [DBG] StackPtr:0x0012ff04 CallAddr:0x004010aa Args[0]:0012ff18 Args[1]:004010ba Args[2]:00000001
[2416] [DBG] StackPtr:0x0012ff18 CallAddr:0x004010ba Args[0]:00000001 Args[1]:00000002 Args[2]:00000003
[2416] [DBG] StackPtr:0x0012ff28 CallAddr:0x004010cb Args[0]:00000031 Args[1]:00000032 Args[2]:0012ff34
[2416] [DBG] StackPtr:0x0012ff34 CallAddr:0x004010dc Args[0]:00000001 Args[1]:0012ffc0 Args[2]:00401205
[2416] [DBG] StackPtr:0x0012ffc0 CallAddr:0x00401205 Args[0]:00400000 Args[1]:00000000 Args[2]:00142426
[2416] [DBG] StackPtr:0x0012fff0 CallAddr:0x7c816ff7 Args[0]:00360033 Args[1]:00340037 Args[2]:7ffd8000
[2416] [DBG] StackPtr:0x00000000 CallAddr:0x00000000 Args[0]:00401137 Args[1]:00000000 Args[2]:78746341



단, 최적화 옵션을 사용하여, 빌드할 경우,
스택프레임을 구성하지 않을 수 있으므로,
일부 프로그램에서 실제 Call 구조와 다른 결과를 나타낼 수 있다.
( 기타 디버거 역시 갖고 있는 한계 )

서브버전과 함께 사용하는 WebSVN을 연동해보면,
SVN의 ParentPath가 모두 보이는것을 확인할 수있다.

내가 잘못알고 있어서인지
SVN 폴더 내부의 authz, svnserve.conf 파일을 수정해도
WebSVN에서는 모든 저장소가 리스트되어
보안성을 가져야하는 저장소(WebSVN에 나타나지 않지만 실제 존재하는)
문제 때문에 사용에 제약이 있었다.

웹에서도 문제 해결에 대한 답을 확인 할 수 없어
소스코드 수정으로 해결하였다.

내가 사용한 방법은,
보안성을 주려는 폴더에 특정 PREFIX를 두어
해당 PREFIX를 갖는 폴더는 화면상에 나타나는 List에
추가하지 않는것이다.

다음은 "_preserve_"라는 문자열로 시작되는 저장소를 숨기도록 수정하는 코드이다.

< WebSVN \ index.php >

if ($config->flatIndex)
{
   // Create the flat view
  
   $projects = $config->getRepositories();
   $i = 0;
   $listing = array ();
   foreach ($projects as $project)
   {
      // if ($project->hasReadAccess("/", true))      
      if ( $project->hasReadAccess("/", true)                      
&&
           strncmp($project->getDisplayName(),"_preserve_",10) != 0 )
      {
         $url = $config->getURL($project, "/", "dir");
     
         $listing[$i]["rowparity"] = $i % 2;
         $listing[$i++]["projlink"] = "<a href=\"${url}sc=$showchanged\">".$project->getDisplayName()."</a>";
      }
   }
   $vars["flatview"] = true;
   $vars["treeview"] = false;  
}
else
{
   // Create the tree view
  
   $projects = $config->getRepositories();
   reset($projects);
   $i = 0;
   $listing = array ();
   $curgroup = NULL;
   $parity = 0;
   foreach ($projects as $project)
   {
      // if ($project->hasReadAccess("/", true))      
      if ( $project->hasReadAccess("/", true)                       &&
           strncmp($project->getDisplayName(),"_preserve_",10) != 0 )

      {
         $listing[$i]["rowparity"] = $parity % 2;
         $url = $config->getURL($project, "/", "dir");
 ...
기존 코드는 주석처리하고 붉은색 코드를 추가하였다.

코드 수정 전에는 다음과 같이 모든 저장소가 표시된다.
사용자 삽입 이미지


코드 수정후에는 다음과 같이 해당 PREFIX에 대하여 숨겨진것을 확인할 수 있다.
사용자 삽입 이미지


편의에 따라 헤더파일을 생성하여,
PREFIX별 권한 설정도 가능할 것으로 판단된다.

~ System Process 가 가진 ObjectTable 을 찾아본다.
~ System Process 의 PID가 4이므로, 4번 PID 의 EPROCESS 확인

0: kd> !process 4 0
Searching for Process with Cid == 4
PROCESS 867b5830  SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
    DirBase: 06e40020  ObjectTable: e1003ea8  HandleCount: 1321.
    Image: System

~ 디버거에서 ObjectTable 을 반환하지만
~ EPROCESS 구조체에서 직접 확인
0: kd> dt nt!_EPROCESS 867b5830
   +0x000 Pcb              : _KPROCESS
   +0x06c ProcessLock      : _EX_PUSH_LOCK
   +0x070 CreateTime       : _LARGE_INTEGER 0x0
   +0x078 ExitTime         : _LARGE_INTEGER 0x0
   +0x080 RundownProtect   : _EX_RUNDOWN_REF
   +0x084 UniqueProcessId  : 0x00000004
   +0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x86476558 - 0x805627b8 ]
   +0x090 QuotaUsage       : [3] 0
   +0x09c QuotaPeak        : [3] 0
   +0x0a8 CommitCharge     : 7
   +0x0ac PeakVirtualSize  : 0x537000
   +0x0b0 VirtualSize      : 0x1e4000
   +0x0b4 SessionProcessLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x0bc DebugPort        : (null)
   +0x0c0 ExceptionPort    : (null)
   +0x0c4 ObjectTable      : 0xe1003ea8 _HANDLE_TABLE
   +0x0c8 Token            : _EX_FAST_REF
   +0x0cc WorkingSetLock   : _FAST_MUTEX
...

~ HandleTable 값 확인
0: kd> dt _HANDLE_TABLE 0xe1003ea8
nt!_HANDLE_TABLE
   +0x000 TableCode        : 0xe18b3001
   +0x004 QuotaProcess     : (null)
   +0x008 UniqueProcessId  : 0x00000004
   +0x00c HandleTableLock  : [4] _EX_PUSH_LOCK
   +0x01c HandleTableList  : _LIST_ENTRY [ 0xe1010f84 - 0x80563aa8 ]
   +0x024 HandleContentionEvent : _EX_PUSH_LOCK
   +0x028 DebugInfo        : (null)
   +0x02c ExtraInfoPages   : 0
   +0x030 FirstFree        : 0x1740
   +0x034 LastFree         : 0
   +0x038 NextHandleNeedingPool : 0x1800
   +0x03c HandleCount      : 1321
   +0x040 Flags            : 0
   +0x040 StrictFIFO       : 0y0

~ TableCode 값이 Handle Table Entries (최종엔트리) 인지 확인
~ 0x03 과 & 연산에서 0 인경우 최종엔트리
0: kd> ? e18b3001 & 0x3
Evaluate expression: 1 = 00000001

~ 최종 엔트리가 아닌경우
~ 포인터 테이블의 주소값 연산
0: kd> ? e18b3001 & 0xfffffffc
Evaluate expression: -510971904 = e18b3000

~ ObjectTable Pointer 값 확인
0: kd> dd e18b3000
e18b3000  e1004000 e18b4000 e1a3e000 00000000
e18b3010  00000000 00000000 00000000 00000000
e18b3020  00000000 00000000 00000000 00000000
e18b3030  00000000 00000000 00000000 00000000
e18b3040  00000000 00000000 00000000 00000000
e18b3050  00000000 00000000 00000000 00000000
e18b3060  00000000 00000000 00000000 00000000
e18b3070  00000000 00000000 00000000 00000000

~ 다시 최종 엔트리인지 확인
0: kd> ? e1004000 & 0x03
Evaluate expression: 0 = 00000000

~ 최종 ObjectTable(HandleTableEntries) 확인
0: kd> dd e1004000
e1004000  00000000 fffffffe 867b5819 001f0fff
e1004010  867b4009 00000000 e14e3469 000f003f
e1004020  e1011459 00000000 e14ea419 00020019
e1004030  e14f1419 00020019 e1023441 0002001f
e1004040  e14e9129 00020019 e14e2151 00020019
e1004050  e14f6441 00020019 e14f2419 0002001f
e1004060  e14ff441 00020019 867e8239 001f0003
e1004070  8634ae89 0012019f 86366cd1 0012019f
0: kd> dd e18b4000
...
0: kd> dd e1a3e000
...

~ ENTRY 구조는 다음과 같이
  4바이트 오브젝트(_OBJECT_HEADER), 4바이트 GrantAccess 구조이다
0: kd> dt nt!_HANDLE_TABLE_ENTRY
   +0x000 Object           : Ptr32 Void
   +0x000 ObAttributes     : Uint4B
   +0x000 InfoTable        : Ptr32 _HANDLE_TABLE_ENTRY_INFO
   +0x000 Value            : Uint4B
   +0x004 GrantedAccess    : Uint4B
   +0x004 GrantedAccessIndex : Uint2B
   +0x006 CreatorBackTraceIndex : Uint2B
   +0x004 NextFreeTableEntry : Int4B
typedef struct _HANDLE_TABLE_ENTRY {
 union {
  PVOID Object;
  ULONG ObAttributes;
 };
 union {
  union {
   ACCESS_MASK GrantedAccess;
   struct {
    USHORT GrantedAccessIndex;
    USHORT CreatorBackTraceIndex;
   };
  };
 LONG NextFreeTableEntry;
 };
} HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY;

~ 단, 8바이트 오브젝트 포인터의 마지막 비트가 Lock Bit 이므로
~ 사용할때 & ~1 연산이 필요하다
~ 엔트리중 e1004000 + sizeof(_HANDLE_TABLE_ENTRY).
~ 즉, 첫번째 값을 보면
0: kd> ? 867b5819 & 0xfffffffe
Evaluate expression: -2038736872 = 867b5818
0: kd> dt nt!_OBJECT_HEADER 867b5818
   +0x000 PointerCount     : 89
   +0x004 HandleCount      : 2
   +0x004 NextToFree       : 0x00000002
   +0x008 Type             : 0x867b5e70 _OBJECT_TYPE
   +0x00c NameInfoOffset   : 0 ''
   +0x00d HandleInfoOffset : 0 ''
   +0x00e QuotaInfoOffset  : 0 ''
   +0x00f Flags            : 0x22 '"'
   +0x010 ObjectCreateInfo : 0x80562860 _OBJECT_CREATE_INFORMATION
   +0x010 QuotaBlockCharged : 0x80562860
   +0x014 SecurityDescriptor : 0xe1001bd2
   +0x018 Body             : _QUAD
0: kd> dt nt!_OBJECT_TYPE 0x867b5e70
   +0x000 Mutex            : _ERESOURCE
   +0x038 TypeList         : _LIST_ENTRY [ 0x867b5ea8 - 0x867b5ea8 ]
   +0x040 Name             : _UNICODE_STRING "Process"
   +0x048 DefaultObject    : (null)
   +0x04c Index            : 5
   +0x050 TotalNumberOfObjects : 0x1e
   +0x054 TotalNumberOfHandles : 0x79
   +0x058 HighWaterNumberOfObjects : 0x20
   +0x05c HighWaterNumberOfHandles : 0x80
   +0x060 TypeInfo         : _OBJECT_TYPE_INITIALIZER
   +0x0ac Key              : 0x636f7250
   +0x0b0 ObjectLocks      : [4] _ERESOURCE

~ Process Type의 Object 이므로
~ Object Header + sizeof(_OBJECT_HEADER) 주소, 즉 Body 주소를 캐스트한다
0: kd> dt nt!_EPROCESS 867b5818+0x18
   +0x000 Pcb              : _KPROCESS
   +0x06c ProcessLock      : _EX_PUSH_LOCK
   +0x070 CreateTime       : _LARGE_INTEGER 0x0
   +0x078 ExitTime         : _LARGE_INTEGER 0x0
   +0x080 RundownProtect   : _EX_RUNDOWN_REF
   +0x084 UniqueProcessId  : 0x00000004
   +0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x86476558 - 0x805627b8 ]
   +0x090 QuotaUsage       : [3] 0
   +0x09c QuotaPeak        : [3] 0
   +0x0a8 CommitCharge     : 7
   +0x0ac PeakVirtualSize  : 0x537000
   +0x0b0 VirtualSize      : 0x1e4000
   +0x0b4 SessionProcessLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x0bc DebugPort        : (null)
   +0x0c0 ExceptionPort    : (null)
   +0x0c4 ObjectTable      : 0xe1003ea8 _HANDLE_TABLE
   +0x0c8 Token            : _EX_FAST_REF
   +0x0cc WorkingSetLock   : _FAST_MUTEX
   +0x0ec WorkingSetPage   : 0x6dc6
   +0x0f0 AddressCreationLock : _FAST_MUTEX
   +0x110 HyperSpaceLock   : 0
   +0x114 ForkInProgress   : (null)
   +0x118 HardwareTrigger  : 0
   +0x11c VadRoot          : 0x867b11e8
   +0x120 VadHint          : 0x867b11e8
   +0x124 CloneRoot        : (null)
   +0x128 NumberOfPrivatePages : 3
   +0x12c NumberOfLockedPages : 0
   +0x130 Win32Process     : (null)
   +0x134 Job              : (null)
   +0x138 SectionObject    : (null)
   +0x13c SectionBaseAddress : (null)
   +0x140 QuotaBlock       : 0x80562860 _EPROCESS_QUOTA_BLOCK
   +0x144 WorkingSetWatch  : (null)
   +0x148 Win32WindowStation : (null)
   +0x14c InheritedFromUniqueProcessId : (null)
   +0x150 LdtInformation   : (null)
   +0x154 VadFreeHint      : (null)
   +0x158 VdmObjects       : (null)
   +0x15c DeviceMap        : 0xe1002170
   +0x160 PhysicalVadList  : _LIST_ENTRY [ 0x867b5990 - 0x867b5990 ]
   +0x168 PageDirectoryPte : _HARDWARE_PTE
   +0x168 Filler           : 0
   +0x170 Session          : (null)
   +0x174 ImageFileName    : [16]  "System"
   +0x184 JobLinks         : _LIST_ENTRY [ 0x0 - 0x0 ]
...


~ "system" process 의 핸들임을 확인할 수 있다.
~ 마찬가지로 다른 핸들테이블엔트리를 확인하여 찾을 수 있다.
~ 끝


[참고]

1. 핸들 테이블 항목의 구조

      0        8        16       24bit

    [ -------- -------- -------- -------- ] // 개체 헤더에 대한 포인터
      L                               AIP

    [ -------- -------- -------- -------- ] // 엑세스 마스크

    총 4B + 4B 의 8바이트로 구성됨.
    L : Lock Bit
    A : Audit Bit
    I : Inherit Bit
    P : Protect Bit

2. Win2K 의 경우
   핸들테이블 -> 핸들테이블포인터 -> 핸들테이블포인터 -> 핸들테이블엔트리리스트
   의 3단계 구조

   WinXP/2k3 의 경우
   1단계~3단계 가능하므로 0x3 Mask 로 확인

3. 핸들 테이블 항목의 최대 개수는
   4KByte(1Page) 에 들어갈 수 있는 최대 개수로 결정되므로
   핸들테이블엔트리사이즈(8Byte) / 4K(1Page) -> 511개 ( 맨 앞단 1개는 사용되지 않음 )