Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일
 

(시리즈 글이 3개 있습니다.)
디버깅 기술: 213. Windbg - swapgs 명령어와 (Ring 0 커널 모드의) FS, GS Segment 레지스터
; https://www.sysnet.pe.kr/2/0/13853

디버깅 기술: 214. Windbg - syscall 단계까지의 Win32 API 호출 (예: Sleep)
; https://www.sysnet.pe.kr/2/0/13856

디버깅 기술: 215. Windbg - syscall 이후 실행되는 KiSystemCall64 함수 및 SSDT 디버깅
; https://www.sysnet.pe.kr/2/0/13859




Windbg - syscall 단계까지의 Win32 API 호출 (예: Sleep)

WinDbg로, Win32 API 호출 시 Ring 3에서 Ring 0로 바뀌는 syscall 단계까지의 호출을 확인해 보겠습니다. 디버깅을 돕기 위해 간단한 Sleep 함수를 호출하는 C++ Console 프로그램을 하나 작성하고,

// x64 + 디버그 모드로 빌드

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

extern "C"
{
    void FuncInThread1()
    {
        Sleep(1000);
    }

    int main()
    {
        while (true)
        {
            FuncInThread1();
        }
    }
}

커널 라이브 디버깅 상태에서 사용자 모드 문맥으로 연결해 FuncInThread1에 bp를 걸어둡니다.

// !process 0 0 ConsoleApplication1.exe
// .process /i ffffdd8c736f5080
// g
// .reload /user

1: kd> bp ConsoleApplication1!FuncInThread1

1: kd> g
Breakpoint 0 hit
ConsoleApplication1!FuncInThread1:
0033:00007ff7`28f11890 4055            push    rbp

BP가 걸리면 Disassembly 창을 띄워 해당 함수의 시작 부분을 어셈블리 코드로 확인할 수 있고,

    ConsoleApplication1!FuncInThread1:
00007ff7`03011890 4055           push    rbp
00007ff7`03011892 57             push    rdi
00007ff7`03011893 4881ece8000000 sub     rsp, 0E8h
00007ff7`0301189a 488d6c2420     lea     rbp, [rsp+20h]
00007ff7`0301189f 488d0d4e080100 lea     rcx, [ConsoleApplication1!__A0E3E448_ConsoleApplication1@cpp (7ff7030220f4)]
00007ff7`030118a6 e8f2faffff     call    ConsoleApplication1!@ILT+920(__CheckForDebuggerJustMyCode) (7ff70301139d)
00007ff7`030118ab 90             nop     
00007ff7`030118ac b9e8030000     mov     ecx, 3E8h
00007ff7`030118b1 ff1549f70000   call    qword ptr [ConsoleApplication1!__imp_Sleep (7ff703021000)]
00007ff7`030118b7 90             nop     
00007ff7`030118b8 488da5c8000000 lea     rsp, [rbp+0C8h]
00007ff7`030118bf 5f             pop     rdi
00007ff7`030118c0 5d             pop     rbp
00007ff7`030118c1 c3             ret   

QWORD PTR[ConsoleApplication1!__imp_Sleep] 부분을 통해 call 호출 대상의 간접 참조하는 실제 주소를 다음과 같이 알아낼 수 있습니다.

// 대상 심벌이 가리키는 위치의 주소를 알아내도 되는데,
3: kd> x ConsoleApplication1!__imp_Sleep
00007ff7`03021000 ConsoleApplication1!_imp_Sleep = <no type information>

// 사실 위의 00007ff7`03021000 출력값은 디스어셈블리 창에 보이는
// "call qword ptr [ConsoleApplication1!__imp_Sleep (7ff703021000)]" 값과 같으므로 굳이 x 명령어를 사용하지 않아도 됩니다.

// 그 위치에 담긴 8바이트가 call 대상 주소를 담고 있습니다.
3: kd> dq 00007ff7`03021000 L1
00007ff7`03021000  00007ffd`af56b0e0

보다시피 "00007ffd`af56b0e0" 값이 나오는데요, 이 주소의 코드를 역어셈블하면 이런 결과가 나옵니다.

    KERNEL32!SleepStub:
00007ffd`af56b0e0 48ff25398d0600 jmp     qword ptr [KERNEL32!__imp_Sleep (7ffdaf5d3e20)]
00007ffd`af56b0e7 cc             int     3

이번에는 (call이 아닌) jmp로 이동하지만 마찬가지로 대상 주소는 간접 참조를 하고 있으므로 다음과 같이 알아낼 수 있습니다.

3: kd> dq 7ffdaf5d3e20 L1
00007ffd`af5d3e20  00007ffd`adaa59e0

이어서 00007ffd`adaa59e0 주소를 역어셈블하면,

    KERNELBASE!Sleep:
00007ffd`adaa59e0 33d2               xor     edx, edx
00007ffd`adaa59e2 e909000000         jmp     KERNELBASE!SleepEx (7ffdadaa59f0)
00007ffd`adaa59e7 cc                 int     3

Sleep 함수의 경우 비록 Win32 API로 등록된 것이긴 하지만 구현 자체는 커널 측 Sleep 함수로 1:1 대응이 이뤄지지 않고, 그저 사용자 모드 내의 SleepEx로 단순히 호출만 넘기는 식으로 처리되고 있습니다.

마지막으로, kernelbase.dll에서 제공하는 SleepEx 함수는 사용자 모드의 최종 목적지인 ntdll.dll로 호출을 넘기는데,

    KERNELBASE!SleepEx:
00007ffd`adaa59f0 89542410           mov     dword ptr [rsp+10h], edx
00007ffd`adaa59f4 53                 push    rbx
00007ffd`adaa59f5 56                 push    rsi
00007ffd`adaa59f6 57                 push    rdi
00007ffd`adaa59f7 4881ec80000000     sub     rsp, 80h
...[생략]...
00007ffd`adaa5a87 48ff1512601900     call    qword ptr [KERNELBASE!__imp_NtDelayExecution (7ffdadc3baa0)]
...[생략]...
00007ffd`adaa5ae8 4881c480000000     add     rsp, 80h
00007ffd`adaa5aef 5f                 pop     rdi
00007ffd`adaa5af0 5e                 pop  

dll 간의 호출이기 때문에 이번에도 IAT(Import Adddress Table)을 통해 NtDelayExecution 함수의 주소를 간접 참조하고 있습니다.

3: kd> dq 7ffdadc3baa0 L1
00007ffd`adc3baa0  00007ffd`b018db60

그리고 마침내 마지막 syscall을 하는 ntdll!NtDelayExecution까지 오게 됐습니다.

    ntdll!NtDelayExecution:
00007ffd`b018db60 4c8bd1           mov     r10, rcx  // 첫 번째 매개변수는 r10 레지스터에 백업
00007ffd`b018db63 b834000000       mov     eax, 34h // NtDelayExecution의 커널 측 서비스 함수 번호
00007ffd`b018db68 f604250803fe7f01 test    byte ptr [7FFE0308h], 1  // syscall을 지원하지 않는 CPU를 위해 int 2e 호출 제공
00007ffd`b018db70 7503             jne     ntdll!NtDelayExecution+0x15 (7ffdb018db75)
00007ffd`b018db72 0f05             syscall 
00007ffd`b018db74 c3               ret     
00007ffd`b018db75 cd2e             int     2Eh
00007ffd`b018db77 c3               ret    

위의 첫 코드를 보면, Win32 API 호출 시 첫 번째 매개변수에 해당하는 rcx 값을 r10으로 복사하고 있는데요, 왜냐하면 syscall 호출 시 내부적으로 rcx 레지스터에 사용자 모드로 돌아갈 RIP(위의 예제라면 00007ffd`b018db74)를 보관하는 용도로 사용하기 때문입니다. (그나저나, 왜 AMD는 AMD64 아키텍처에서 syscall 명령어의 내부 연산에서 rcx를 굳이 사용했을까요? 기왕이면 새롭게 생긴 r8 ~ r15 레지스터 중 하나를 했다면 더 좋지 않았을까요? ^^)

이후, "mov eax, 34h"에서 0x34는 NtDelayExecution 함수를 대표하는 서비스 번호에 해당하고, syscall 이후 진입하는 커널 함수 측에서 저 번호를 이용해 커널 측 함수를 호출하는 데 사용됩니다. 이때 재미있는 점은, (예외가 있을 수도 있지만) 대부분의 ntdll 함수와 동일한 이름으로 nt 모듈에도 제공한다는 점입니다.

즉, 사용자 모드의 ntdll!NtDelayExecution 함수는 커널 모드의 nt!NtDelayExecution 함수 호출로 이뤄집니다.

이렇게 설정이 완료됐으면, 이제 커널 모드로 진입해야 하는데요, 이때 "test byte ptr [7FFE0308h], 1"은 syscall을 지원하는지 확인하는 역할을 합니다. 만약 syscall을 지원한다면 그것을 호출하고, 그렇지 않으면 예전처럼 (성능이 낮은) int 2E를 통해 커널로 진입합니다.

참고로, syscall에 대한 지원 여부는 MSR_EFER 레지스터를 통해서도 확인할 수 있는데요,

// MSR_EFER (Extended Feature Enable Register) 0xC0000080

3: kd> rdmsr 0xc0000080 // rdmsr == read MSR (Model Specific Register)
msr[c0000080] = 00000000`00000d01

d01 값을 2진수로 바꿔 각각의 필드에 의미를 해석하면 다음과 같습니다.

d01 == 00001101 00000001

1 SCE System Call Extensions (SYSCALL/SYSRET 명령어 사용 여부)
0000000 (Reserved)
1 LME Long Mode Enable (IA-32e 모드 활성화)
0 (Reserved?)
1 LMA Long Mode Active
1 NXE No Execute Enable
0 SVME Secure Virtual Machine Enable
0 LMSLE Long Mode Segment Limit Enable
0 FFXSR Fast FXSAVE/FXRSTOR
0 TCE Translation Cache Extension
16 ~ 63 (Reserved)

보는 바와 같이 SCE 비트가 1이므로 syscall을 지원한다는 것을 알 수 있습니다. 아마도 "test byte ptr [7FFE0308h], 1" 코드에서 [7FFE0308h] 주소가 참조하는 값은 저 SCE 비트를 보관해 둔 것으로 추정할 수 있습니다.

참고로, 위의 결과는 i9-12900K 인텔 CPU에서 확인한 것이며, 다른 CPU, 가령 AMD 라이젠 7 PRO 4750G 모델에서 확인하면 이런 결과가 나옵니다.

3: kd> rdmsr 0xc0000080
msr[c0000080] = 00000000`00004d01

이번엔 (d01이 아니라) "4d01 == 01001101 00000001" 값이 나왔는데요, 달라진 점은 FFXSR 지원 여부가 1로 바뀌었다는 점입니다.

1 FFXSR Fast FXSAVE/FXRSTOR

이것은 sysinternals의 coreinfo 유틸리티를 이용한 결과로도 알아낼 수 있는데요, 아래는 인텔 CPU에서 실행했을 때와 AMD Ryzen 7 PRO 4750G의 출력 결과에서 FFXSR 지원 여부를 출력한 결과입니다.

c:\temp> coreinfo
...[생략]...
12th Gen Intel(R) Core(TM) i9-12900K
Intel64 Family 6 Model 151 Stepping 2, GenuineIntel
...[생략]...
FFXSR           -       Supports optimized FXSAVE/FSRSTOR instruction
...[생략]...

c:\temp> coreinfo
...[생략]...
AMD Ryzen 7 PRO 4750G with Radeon Graphics
AMD64 Family 23 Model 96 Stepping 1, AuthenticAMD
...[생략]...
FFXSR           *       Supports optimized FXSAVE/FSRSTOR instruction
...[생략]...




ntdll!NtDelayExecution의 마지막 단계인 syscall이 어떤 일을 하는지 알아보겠습니다.

SYSCALL
; https://modoocode.com/en/inst/syscall

SYSCALL — Fast System Call
; https://www.felixcloutier.com/x86/syscall

Spinning in user-mode versus entering kernel - the cost of a SYSCALL in Windows.
 - Entering Kernel From a User-Mode
; https://dennisbabkin.com/blog/?t=critical_section_vs_kernel_objects_in_windows#enter_kernel

위의 두 번째 문서에서 자세하게 pseduo code로 설명하고 있는데요,

IF (CS.L ≠ 1 ) or (IA32_EFER.LMA ≠ 1) or (IA32_EFER.SCE ≠ 1)
(* Not in 64-Bit Mode or SYSCALL/SYSRET not enabled in IA32_EFER *)
    THEN #UD;
FI;
RCX := RIP; (* Will contain address of next instruction *)
RIP := IA32_LSTAR;
R11 := RFLAGS;
RFLAGS := RFLAGS AND NOT(IA32_FMASK);
CS.Selector := IA32_STAR[47:32] AND FFFCH (* Operating system provides CS; RPL forced to 0 *)
(* Set rest of CS to a fixed value *)
CS.Base := 0;
                (* Flat segment *)
CS.Limit := FFFFFH;
                (* With 4-KByte granularity, implies a 4-GByte limit *)
CS.Type := 11;
                (* Execute/read code, accessed *)
CS.S := 1;
CS.DPL := 0;
CS.P := 1;
CS.L := 1;
                (* Entry is to 64-bit mode *)
CS.D := 0;
                (* Required if CS.L = 1 *)
CS.G := 1;
                (* 4-KByte granularity *)
IF ShadowStackEnabled(CPL)
    THEN (* adjust so bits 63:N get the value of bit N–1, where N is the CPU’s maximum linear-address width *)
        IA32_PL3_SSP := LA_adjust(SSP);
            (* With shadow stacks enabled the system call is supported from Ring 3 to Ring 0 *)
            (* OS supporting Ring 0 to Ring 0 system calls or Ring 1/2 to ring 0 system call *)
            (* Must preserve the contents of IA32_PL3_SSP to avoid losing ring 3 state *)
FI;
CPL := 0;
IF ShadowStackEnabled(CPL)
    SSP := 0;
FI;
IF EndbranchEnabled(CPL)
    IA32_S_CET.TRACKER = WAIT_FOR_ENDBRANCH
    IA32_S_CET.SUPPRESS = 0
FI;
SS.Selector := IA32_STAR[47:32] + 8;
                (* SS just above CS *)
(* Set rest of SS to a fixed value *)
SS.Base := 0;
                (* Flat segment *)
SS.Limit := FFFFFH;
                (* With 4-KByte granularity, implies a 4-GByte limit *)
SS.Type := 3;
                (* Read/write data, accessed *)
SS.S := 1;
SS.DPL := 0;
SS.P := 1;
SS.B := 1;
                (* 32-bit stack segment *)
SS.G := 1;
                (* 4-KByte granularity *)

천천히 몇 개만 정리해 보겠습니다. ^^ 우선, 1) IP 레지스터의 값을 바꾸는 것부터 볼까요?

RCX := RIP; (* Will contain address of next instruction *)
RIP := IA32_LSTAR;

즉, syscall을 호출하면 현재 RIP 레지스터의 값을 RCX 레지스터로 복사하는데요, 왜냐하면 나중에 커널 모드의 함수 호출을 끝내고 사용자 모드로 돌아갈 때 실행할 위치를 알아야 하기 때문입니다. (보다시피 저 과정에서 RCX 레지스터를 사용하기 때문에, ntdll!NtDelayExecution 함수에서 미리 r10 레지스터에 백업해 둔 이유가 됩니다.)

그다음 RIP 레지스터에 OS에서 지정한 system call handler의 주소를 덮어쓰는데요, 해당 함수의 위치는 MSR_LSTAR(long mode SYSCALL target) 0xc0000082에 기록돼 있습니다. (당연히 OS가 미리 기록해 두어야 합니다.)

0: kd> rdmsr C0000082
msr[c0000082] = fffff806`49629e00

2) 두 번째로 syscall이 하는 작업은,

R11 := RFLAGS;
RFLAGS := RFLAGS AND NOT(IA32_FMASK);

rflags 레지스터의 값을 r11 레지스터에 복사한 후, IA32FMASK MSR (MSR address C0000084H) 값을 현재의 flags 값에 and 연산을 수행합니다.

0: kd> rdmsr C0000084H
msr[c0000084] = 00000000`00004700  (2진수 01000111 00000000)

위의 경우 0x4700인데요, 이것으로 마스킹 처리되는 flag 값은 TF(Trap Flag), IF(Interrupt Flag), DF(Direction Flag), NT(Nested Task Flag)입니다.

3) 세 번째로, DPL == 3 값을 가지고 있는 code segment selector를, Ring 0의 권한을 요구하는 명령어를 수행하기 위해 DPL == 0에 해당하는 segment selector로 교체합니다.

CS.Selector := IA32_STAR[47:32] AND FFFCH (* Operating system provides CS; RPL forced to 0 *)
(* Set rest of CS to a fixed value *)
CS.Base := 0;
                (* Flat segment *)
CS.Limit := FFFFFH;
                (* With 4-KByte granularity, implies a 4-GByte limit *)
CS.Type := 11;
                (* Execute/read code, accessed *)
CS.S := 1;
CS.DPL := 0;
CS.P := 1;
CS.L := 1;
                (* Entry is to 64-bit mode *)
CS.D := 0;
                (* Required if CS.L = 1 *)
CS.G := 1;

게다가 단순히 selector만 바꾸는 것이 아니고, descriptor가 갖는 나머지 필드들도 새로운 고정값으로 설정하고 있습니다.

여기서 selector의 값을, IA32_STAR 레지스터의 값에서 47 ~ 32 비트를 가져와서 사용하고 있는데요,

// #define MSR_STAR                0xc0000081 /* legacy mode SYSCALL target */

0: kd> rdmsr 0xc0000081
msr[c0000081] = 00230010`00000000

// 2진수: 00000000 00100011 00000000 00010000 00000000 00000000 00000000 00000000
// 47 ~ 32 비트: 00000000 00010000 (0x10)

위의 계산에 따라 나온 값이 0x10이고, 그 값이 커널 모드에서의 cs selector가 되는 것입니다.

// 사용자 모드 디버깅 문맥에서 세그먼트 레지스터 출력
0: kd> rM 8
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!NtDelayExecution+0x12:
0033:00007ffd`b018db72 0f05            syscall

// syscall 이후 세그먼트 레지스터 출력
0: kd> rM 8
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
nt!DbgBreakPointWithStatus:
fffff806`4961f130 cc              int     3

심심하니까, 위의 rM 명령어의 결과로 보이는 다른 세그먼트의 offset 값을 분해해 살펴볼까요? ^^

0x10 == offset 2 // 커널 모드의 cs
0x18 == offset 3 // 커널 모드의 ss
0x2b == offset 5 // ds, es, 사용자 모드의 ss, gs
0x33 == offset 6 // 사용자 모드의 cs
0x53 == offset a // fs

각각의 세그먼트에 해당하는 decsriptor를 GDT에서 찾아보면 이렇게 정리가 됩니다.

3: kd>  dg 0 50
                                                    P Si Gr Pr Lo
Sel        Base              Limit          Type    l ze an es ng Flags
---- ----------------- ----------------- ---------- - -- -- -- -- --------
0000 00000000`00000000 00000000`00000000 <Reserved> 0 Nb By Np Nl 00000000
0008 00000000`00000000 00000000`00000000 <Reserved> 0 Nb By Np Nl 00000000
0010 00000000`00000000 00000000`00000000 Code RE Ac 0 Nb By P  Lo 0000029b // 0x10 (커널 모드의 cs)
0018 00000000`00000000 00000000`00000000 Data RW Ac 0 Bg By P  Nl 00000493 // 커널 모드의 ss
0020 00000000`00000000 00000000`ffffffff Code RE Ac 3 Bg Pg P  Nl 00000cfb
0028 00000000`00000000 00000000`ffffffff Data RW Ac 3 Bg Pg P  Nl 00000cf3 // 0x2b (ds, es, 사용자 모드의 ss, gs)
0030 00000000`00000000 00000000`00000000 Code RE Ac 3 Nb By P  Lo 000002fb // 0x33 (사용자 모드의 cs)
0038 00000000`00000000 00000000`00000000 <Reserved> 0 Nb By Np Nl 00000000
0040 00000000`1a16e000 00000000`00000067 TSS32 Busy 0 Nb By P  Nl 0000008b
0048 00000000`0000ffff 00000000`00009581 <Reserved> 0 Nb By Np Nl 00000000
0050 00000000`00000000 00000000`0000fc00 Data RW Ac 3 Bg By P  Nl 000004f3 // 0x53 (fs)

4) 네 번째로, 스택 세그먼트의 값도 커널 모드의 코드 수행을 위한 전용 스택을 사용하기 위해 DPL == 0인 selector로 변경합니다.

SS.Selector := IA32_STAR[47:32] + 8;
                (* SS just above CS *)
(* Set rest of SS to a fixed value *)
SS.Base := 0;
                (* Flat segment *)
SS.Limit := FFFFFH;
                (* With 4-KByte granularity, implies a 4-GByte limit *)
SS.Type := 3;
                (* Read/write data, accessed *)
SS.S := 1;
SS.DPL := 0;
SS.P := 1;
SS.B := 1;
                (* 32-bit stack segment *)
SS.G := 1;
                (* 4-KByte granularity *)

보는 바와 같이 ss selector의 값은 커널 모드의 cs selector 값에 8을 더하고 있는데요,

0: kd> rM 8
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
nt!DbgBreakPointWithStatus:
fffff806`4961f130 cc              int     3

그래서 Windows에서는 커널 모드의 경우 언제나 저런 식으로 cs = 0x10이면 ss는 0x18이 됩니다.




간단하게 정리하면, syscall은 (MSR_LSTAR에 기록된) KiSystemCall64 함수를 호출하기 위해 전반적인 실행 환경을 Privilege Level 3에서 0으로 바꾸는 특수한 명령어입니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 1/6/2025]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13881정성태2/7/2025408닷넷: 2316. C# - Port I/O를 이용한 PCI Configuration Space 정보 열람파일 다운로드1
13880정성태2/5/2025439오류 유형: 947. sshd - Failed to start OpenSSH server daemon.
13879정성태2/5/2025503오류 유형: 946. Ubuntu - N: Updating from such a repository can't be done securely, and is therefore disabled by default.
13878정성태2/3/2025704오류 유형: 945. Windows - 최대 절전 모드 시 DRIVER_POWER_STATE_FAILURE 발생 (pacer.sys)
13877정성태1/25/20251023닷넷: 2315. C# - PCI 장치 열거 (레지스트리, SetupAPI)파일 다운로드1
13876정성태1/25/20251170닷넷: 2314. C# - ProcessStartInfo 타입의 Arguments와 ArgumentList파일 다운로드1
13875정성태1/24/20251162스크립트: 69. 파이썬 - multiprocessing 패키지의 spawn 모드로 동작하는 uvicorn의 workers
13874정성태1/24/20251132스크립트: 68. 파이썬 - multiprocessing Pool의 기본 프로세스 시작 모드(spawn, fork)
13873정성태1/23/20251059디버깅 기술: 217. WinDbg - PCI 장치 열거
13872정성태1/23/20251045오류 유형: 944. WinDbg - 원격 커널 디버깅이 연결은 되지만 Break (Ctrl + Break) 키를 눌러도 멈추지 않는 현상
13871정성태1/22/20251159Windows: 278. Windows - 윈도우를 다른 모니터 화면으로 이동시키는 단축키 (Window + Shift + 화살표)
13870정성태1/18/20251236개발 환경 구성: 741. WinDbg - 네트워크 커널 디버깅이 가능한 NIC 카드 지원 확대
13869정성태1/18/20251256개발 환경 구성: 740. WinDbg - _NT_SYMBOL_PATH 환경 변수에 설정한 경로로 심벌 파일을 다운로드하지 않는 경우
13868정성태1/17/20251196Windows: 277. Hyper-V - Windows 11 VM의 Enhanced Session 모드로 로그인을 할 수 없는 문제
13867정성태1/17/20251303오류 유형: 943. Hyper-V에 Windows 11 설치 시 "This PC doesn't currently meet Windows 11 system requirements" 오류
13866정성태1/16/20251309개발 환경 구성: 739. Windows 10부터 바뀐 device driver 서명 방법
13865정성태1/15/20251394오류 유형: 942. C# - .NET Framework 4.5.2 이하의 버전에서 HttpWebRequest로 https 호출 시 "System.Net.WebException" 예외 발생
13864정성태1/15/20251345Linux: 114. eBPF를 위해 필요한 SELinux 보안 정책
13863정성태1/14/20251285Linux: 113. Linux - 프로세스를 위한 전용 SELinux 보안 문맥 지정
13862정성태1/13/20251229Linux: 112. Linux - 데몬을 위한 SELinux 보안 정책 설정
13861정성태1/11/20251346Windows: 276. 명령행에서 원격 서비스를 동기/비동기로 시작/중지
13860정성태1/10/20251269디버깅 기술: 216. WinDbg - 2가지 유형의 식 평가 방법(MASM, C++)
13859정성태1/9/20251337디버깅 기술: 215. Windbg - syscall 이후 실행되는 KiSystemCall64 함수 및 SSDT 디버깅
13858정성태1/8/20251410개발 환경 구성: 738. PowerShell - 원격 호출 시 "powershell.exe"가 아닌 "pwsh.exe" 환경으로 명령어를 실행하는 방법
13857정성태1/7/20251820C/C++: 187. Golang - 콘솔 응용 프로그램을 Linux 데몬 서비스를 지원하도록 변경파일 다운로드1
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...