일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
- SCP
- WinDBG
- elf 헤더
- Android
- fastbin
- cmake
- pwndbg
- HOS
- libc.so
- ioploaddrivers
- brop
- house of force
- libc-database
- randtbl
- 계산기
- JOP
- windows kernel
- windows
- patchelf
- dangling pointer
- top chunk
- return to libraty
- canary leak
- PLT
- frida-dump
- ntwritefile
- kernel debug
- kaslr
- RAO
- sgerrand
- Today
- Total
sh711 님의 블로그
Windbg 프로그램 분석 본문
코드는 다음과 같다
// gcc -g -o test test.c => symbolic debug를 위해 symbolic 정보 포함하여 빌드
#include <stdio.h>
int main() {
printf("Hello Windbg!!");
return 0;
}
printf 가 언제 호출되는지 찾아보았다
test 프로그램의 main 함수 확인
실행 후 해당 함수에서 printf 호출이 되어서 브레이크를 잡고 실행시켰다
syscall 수행을 확인하였고 rax 값은 0x43이었다
https://j00ru.vexillium.org/syscalls/nt/64/
해당 링크에서 윈도우 syscall table을 확인할 수 있다
Windows X86-64 System Call Table (XP/2003/Vista/7/8/10/2022/11)
Windows X86-64 System Call Table (XP/2003/Vista/7/8/10/2022/11) Author: Mateusz "j00ru" Jurczyk (j00ru.vx tech blog) See also: Windows System Call Tables in CSV/JSON formats on GitHub Special thanks to: MeMek, Wandering Glitch Layout by Metasploit Team NtA
j00ru.vexillium.org
로컬 윈도우 버전은 24H2이고 0x0043 syscall은 NtContinue였다
NtContinue
https://n4r1b.com/posts/2019/03/system-calls-on-windows-x64/
System calls on Windows x64 :: Up is Down and Black is White — n4r1b
Those who know me know that I’ve been always interested in Kernel stuff, but due to lack of time I’ve never been available to focus on this topic. But this year I took the decision to spend my free time looking into this (I even bought Windows Internal
n4r1b.com
windows x64에서 일반적인 함수 호출 규약은 Microsoft x64 calling convention 을 사용한다
인자 전달 순서는 rcx, rdx, r8, r9 순이다
syscall 호출 규약
x86 환경
rax : syscall number
rcx, rdx, r8, r9, rsp, rsp+8, rsp + 16 순으로 syscall 인자 전달
syscall 전 rcx를 r10에 백업 시키는 이유는 syscall 이후 리턴 주소를 rcx에 반환하기 때문이다
NtContinue 함수는 프로그램 동작 도중 예외 발생 시 호출되는 함수이다
첫 번째 인자로 ContextRecord를 받고 두 번째인자는 시스템 경고를 발생시킬지 여부를 지정하는 Boolean 값을 전달한다
NTSTATUS NtContinue(
const CONTEXT *ContextRecord,
BOOLEAN RaiseAlert
);
ContextRecord 구조체는 다음과 같다
typedef struct _CONTEXT {
ULONG ContextFlags; // 컨텍스트 정보의 종류를 나타내는 플래그
ULONG Dr0, Dr1, Dr2, Dr3, Dr6, Dr7; // 디버그 레지스터들
ULONG Rax, Rbx, RcX, Rdx, Rbp, Rsi, Rdi, R8, R9, R10, R11, R12, R13, R14, R15; // 범용 레지스터들
ULONG Rip; // 명령어 포인터 (Instruction Pointer)
ULONG SegCs; // 세그먼트 레지스터 (Code Segment)
ULONG EFlags; // 플래그 레지스터
ULONG Rsp; // 스택 포인터 (Stack Pointer)
ULONG SegSs; // 스택 세그먼트
ULONG64 ExtendedRegisters[512]; // 확장 레지스터 (일반적으로 SSE, AVX 등)
} CONTEXT;
NtContinue syscall 전 rcx(ContextRecord)의 구조체 형태 확인시 다음과 같이 확인되었다
0:000> dt _CONTEXT 000000000061FAD0
ntdll!_CONTEXT
+0x000 P1Home : 0
+0x008 P2Home : 0
+0x010 P3Home : 0
+0x018 P4Home : 0
+0x020 P5Home : 0
+0x028 P6Home : 0
+0x030 ContextFlags : 0x10001b
+0x034 MxCsr : 0x1f80
+0x038 SegCs : 0x33
+0x03a SegDs : 0
+0x03c SegEs : 0
+0x03e SegFs : 0
+0x040 SegGs : 0
+0x042 SegSs : 0x2b
+0x044 EFlags : 0x200
+0x048 Dr0 : 0
+0x050 Dr1 : 0
+0x058 Dr2 : 0
+0x060 Dr3 : 0
+0x068 Dr6 : 0
+0x070 Dr7 : 0
+0x078 Rax : 0
+0x080 Rcx : 0x4014e0
+0x088 Rdx : 0x32f000
+0x090 Rbx : 0
+0x098 Rsp : 0x61ffd8
+0x0a0 Rbp : 0
+0x0a8 Rsi : 0
+0x0b0 Rdi : 0
+0x0b8 R8 : 0
+0x0c0 R9 : 0
+0x0c8 R10 : 0
+0x0d0 R11 : 0
+0x0d8 R12 : 0
+0x0e0 R13 : 0
+0x0e8 R14 : 0
+0x0f0 R15 : 0
+0x0f8 Rip : 0x00007ffb`1713bf00
+0x100 FltSave : _XSAVE_FORMAT
+0x100 Header : [2] _M128A
+0x120 Legacy : [8] _M128A
+0x1a0 Xmm0 : _M128A
+0x1b0 Xmm1 : _M128A
+0x1c0 Xmm2 : _M128A
+0x1d0 Xmm3 : _M128A
+0x1e0 Xmm4 : _M128A
+0x1f0 Xmm5 : _M128A
+0x200 Xmm6 : _M128A
+0x210 Xmm7 : _M128A
+0x220 Xmm8 : _M128A
+0x230 Xmm9 : _M128A
+0x240 Xmm10 : _M128A
+0x250 Xmm11 : _M128A
+0x260 Xmm12 : _M128A
+0x270 Xmm13 : _M128A
+0x280 Xmm14 : _M128A
+0x290 Xmm15 : _M128A
+0x300 VectorRegister : [26] _M128A
+0x4a0 VectorControl : 0
+0x4a8 DebugControl : 0
+0x4b0 LastBranchToRip : 0
+0x4b8 LastBranchFromRip : 0
+0x4c0 LastExceptionToRip : 0
+0x4c8 LastExceptionFromRip : 0
syscall 수행 후 레지스터 값을 확인해보면 _ContextRecord 값으로 복구된 것을 확인할 수 있다
이후 test 프로그램 코드 영역 내 call 0x401670 및 call 0x401180이 호출된다
각각 _security_init_cookie() 함수와 윈도우에서 프로그램의 main 함수를 호출하는 __tmainCRTStartup() (elf 에선 __libc_start_main과 같은 함수) 함수인 것을 확인할 수있다.
이어서 아이다에서 확인시 main 함수가 호출되는 곳은 0x4013c2 이다
_tmainCRTStartup 내부에서 main 호출
windbg에서 해당 부분 브레이크 후 접근 => 처음에 찾은 main 함수
main 함수 진입
0x401630 은 전역 객체 생성자가 생성되었는지 확인하는 함수이다
eax 값이 0일 경우 0x401640으로 분기 후, 0x407030 주소에 1을 넣고 0x4015C0으로 분기한다
0x4015C0은 __do_global_crors 함수로 일반적으로 프로그램 초기(main 호출 전)에 실행되어 전역 객체가 생성된다.
전역 객체란, 프로그램의 시작부터 끝까지 유지되는 객체로 일반적으로 전역 변수와 같은 항상 존재하는 값을 의미한다.
int _do_global_ctors()
{
unsigned int i; // eax
void (**v1)(void); // rbx
__int64 *v2; // rsi
i = (*refptr___CTOR_LIST__)[0];
if ( i == -1 )
{
for ( i = 0; (*refptr___CTOR_LIST__)[i + 1]; ++i )
;
}
if ( i )
{
v1 = (void (**)(void))&(*refptr___CTOR_LIST__)[i];
v2 = &(*refptr___CTOR_LIST__)[i - (unsigned __int64)(i - 1) - 1];
do
(*v1--)();
while ( v1 != (void (**)(void))v2 );
}
return atexit((void (__cdecl *)())_do_global_dtors);
}
이 후, "Hello windbg!!" 문자열의 주소를 가져오고 printf 함수를 호출한다
진입 시 .idata 영역인 0x40833c로 점프하고
해당 위치에는 주소가 쓰여있다
확인 시 msvcrt 라이브러리의 printf_s 함수로 확인된다
현재 인자 rcx 값은 "Hello windbg!!" 인것을 알 수 있다
printf_s 함수 내부에서 msvcrt 라이브러리의 ftbuf는 파일 스트림에 대한 버퍼를 처리하는 전역 변수이다.
(ftbuf를 다루는 파일 입출력 함수가 호출되는 것)
이어서 출력을 위해 msvcrt의 flush 함수가 호출되며 인자는 파일 디스크립터인것을 확인할 수 있다
write 함수를 호출한다
write 함수 원형은 다음과 같다
int _write(
int fd,
const void *buffer,
unsigned int count
);
이어서, _write_nolock 함수 호출
write_nolock 함수 내에서 외부함수 WriteFile 함수 호출
진입 시 KERNELBASE 라이브러리의 WriteFile 함수 호출
이어서 외부 함수인 NtWriteFile 함수 호출
이제 유저모드에서 최종적인 함수인 NtWriteFile이 호출된다
syscall을 통해 출력이 된다
이때 NtWriteFile 함수 원형은 다음과 같다
__kernel_entry NTSYSCALLAPI NTSTATUS NtWriteFile(
[in] HANDLE FileHandle,
[in, optional] HANDLE Event,
[in, optional] PIO_APC_ROUTINE ApcRoutine,
[in, optional] PVOID ApcContext,
[out] PIO_STATUS_BLOCK IoStatusBlock,
[in] PVOID Buffer,
[in] ULONG Length,
[in, optional] PLARGE_INTEGER ByteOffset,
[in, optional] PULONG Key
);
따라서 4번째 인자까지는 rcx, rdx, r8, r9로 전달될 것이고 나머지는 스택으로 전달된다.
스택 트레이스 확인 시 커널 모드로의 전환까지 유저 모드에서 진행된 과정을 확인할 수 있다
syscall 수행 시 권한이 커널로 넘어가며 출력을 확인할 수 있다
커널로 제어권이 넘어가면 내부적으로 ntoskrnl.exe 모듈을 통해 작업이 이루어진다
'Study > Windows' 카테고리의 다른 글
Windows Kernel Driver Load (0) | 2025.03.14 |
---|---|
windows API 프로그래밍 (0) | 2025.03.13 |
Windbg 윈도우 커널 분석 - 1 (0) | 2025.03.10 |