성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[양승조] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
[공진영] 안녕하세요 좋은글 감사합니다. 현재 제가 wpf로 관제 모...
[정성태] The Windows Registry Adventure #1: ...
[정성태] systemd for Developers I ; https:/...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C 언어의 setjmp/longjmp 기능을 Thread Context를 이용해 유사하게 구현하는 방법</h1> <p> 이번 글은 그냥 재미로 봐주시면 되겠습니다. (사실 현업에서 쓸 일은 거의 없습니다. ^^)<br /> <br /> C 언어를 공부하신 분은 특이한 (무조건적인) 점프 문이 함수로 구현된 것을 아실 텐데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C library function - longjmp() ; <a target='tab' href='https://www.tutorialspoint.com/c_standard_library/c_function_longjmp.htm'>https://www.tutorialspoint.com/c_standard_library/c_function_longjmp.htm</a> </pre> <br /> 위의 글에 실린 예제를,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // <a target='tab' href='https://www.tutorialspoint.com/c_standard_library/c_function_longjmp.htm'>https://www.tutorialspoint.com/c_standard_library/c_function_longjmp.htm</a> #include <stdio.h> #include <stdlib.h> #include <setjmp.h> int main () { int val; jmp_buf env_buffer; /* save calling environment for longjmp */ <span style='color: blue; font-weight: bold'>val = setjmp( env_buffer );</span> if( val != 0 ) { printf("Returned from a longjmp() with value = %s\n", val); exit(0); } printf("Jump function call\n"); <span style='color: blue; font-weight: bold'>jmpfunction( env_buffer );</span> return(0); } void jmpfunction(jmp_buf env_buf) { <span style='color: blue; font-weight: bold'>longjmp(env_buf, "tutorialspoint.com");</span> } /* 실행 출력 결과 Jump function call Returned from a longjmp() with value = tutorialspoint.com */ </pre> <br /> 실행해 보면, setjmp를 호출함으로써 그 시점의 환경을 저장한 다음, longjmp를 호출함으로써 setjmp를 호출했던 시점으로 말 그대로 jump가 됩니다. 이에 대한 구현 방식은 간단하게 Visual Studio의 디버깅 중 Disassembly 창을 이용해 덤프해 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > --- d:\agent\_work\36\s\src\vctools\crt\vcruntime\src\eh\amd64\setjmp.asm ------ 00007FFD9727F0F0 48 89 11 mov qword ptr [rcx],rdx 00007FFD9727F0F3 48 89 59 08 mov qword ptr [rcx+8],rbx 00007FFD9727F0F7 48 89 69 18 mov qword ptr [rcx+18h],rbp 00007FFD9727F0FB 48 89 71 20 mov qword ptr [rcx+20h],rsi 00007FFD9727F0FF 48 89 79 28 mov qword ptr [rcx+28h],rdi 00007FFD9727F103 4C 89 61 30 mov qword ptr [rcx+30h],r12 00007FFD9727F107 4C 89 69 38 mov qword ptr [rcx+38h],r13 00007FFD9727F10B 4C 89 71 40 mov qword ptr [rcx+40h],r14 00007FFD9727F10F 4C 89 79 48 mov qword ptr [rcx+48h],r15 00007FFD9727F113 4C 8D 44 24 08 lea r8,[rsp+8] 00007FFD9727F118 4C 89 41 10 mov qword ptr [rcx+10h],r8 00007FFD9727F11C 4C 8B 04 24 mov r8,qword ptr [rsp] 00007FFD9727F120 4C 89 41 50 mov qword ptr [rcx+50h],r8 00007FFD9727F124 0F AE 59 58 stmxcsr dword ptr [rcx+58h] 00007FFD9727F128 D9 79 5C fnstcw word ptr [rcx+5Ch] 00007FFD9727F12B 66 0F 7F 71 60 movdqa xmmword ptr [rcx+60h],xmm6 00007FFD9727F130 66 0F 7F 79 70 movdqa xmmword ptr [rcx+70h],xmm7 00007FFD9727F135 66 44 0F 7F 81 80 00 00 00 movdqa xmmword ptr [rcx+80h],xmm8 00007FFD9727F13E 66 44 0F 7F 89 90 00 00 00 movdqa xmmword ptr [rcx+90h],xmm9 00007FFD9727F147 66 44 0F 7F 91 A0 00 00 00 movdqa xmmword ptr [rcx+0A0h],xmm10 00007FFD9727F150 66 44 0F 7F 99 B0 00 00 00 movdqa xmmword ptr [rcx+0B0h],xmm11 00007FFD9727F159 66 44 0F 7F A1 C0 00 00 00 movdqa xmmword ptr [rcx+0C0h],xmm12 00007FFD9727F162 66 44 0F 7F A9 D0 00 00 00 movdqa xmmword ptr [rcx+0D0h],xmm13 00007FFD9727F16B 66 44 0F 7F B1 E0 00 00 00 movdqa xmmword ptr [rcx+0E0h],xmm14 00007FFD9727F174 66 44 0F 7F B9 F0 00 00 00 movdqa xmmword ptr [rcx+0F0h],xmm15 00007FFD9727F17D 33 C0 xor eax,eax 00007FFD9727F17F C3 ret </pre> <br /> 대체로 ("CRT(C Runtime)" Library를 비롯한) Visual C++ 관련 소스 코드는 "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.29.30037\crt\src" 디렉터리에 공개돼 있지만 아쉽게도 setjmp.asm 파일은 없습니다. 하지만, 어차피 어셈블리 코드라서 위와 같이 역어셈블 코드만 봐도 대충 호출 문맥을 <a target='tab' href='https://docs.microsoft.com/en-us/cpp/c-runtime-library/standard-types'>jmp_buf</a> 타입의 구조체에 저장한다는 것을 짐작게 합니다.<br /> <br /> 그리고 당연히 longjmp는 jmp_buf 구조체에 저장된 문맥을 복원하고, 마찬가지로 setjmp가 호출된 당시의 EIP/RIP 주소로 jmp 하리라는 것을 Disassembly 창에서 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > --- d:\agent\_work\36\s\src\vctools\crt\vcruntime\src\eh\amd64\longjmp.asm ----- 00007FFD972AEFF0 48 83 EC 48 sub rsp,48h 00007FFD972AEFF4 48 85 D2 test rdx,rdx 00007FFD972AEFF7 75 03 jne LJ10 (07FFD972AEFFCh) 00007FFD972AEFF9 48 FF C2 inc rdx 00007FFD972AEFFC 4D 33 D2 xor r10,r10 00007FFD972AEFFF F3 49 0F 1E CA rdsspq r10 00007FFD972AF004 4D 85 D2 test r10,r10 00007FFD972AF007 0F 85 91 00 00 00 jne LJ10+0A2h (07FFD972AF09Eh) 00007FFD972AF00D 4C 39 11 cmp qword ptr [rcx],r10 00007FFD972AF010 0F 85 88 00 00 00 jne LJ10+0A2h (07FFD972AF09Eh) 00007FFD972AF016 48 8B C2 mov rax,rdx 00007FFD972AF019 48 8B 59 08 mov rbx,qword ptr [rcx+8] 00007FFD972AF01D 48 8B 71 20 mov rsi,qword ptr [rcx+20h] 00007FFD972AF021 48 8B 79 28 mov rdi,qword ptr [rcx+28h] 00007FFD972AF025 4C 8B 61 30 mov r12,qword ptr [rcx+30h] 00007FFD972AF029 4C 8B 69 38 mov r13,qword ptr [rcx+38h] 00007FFD972AF02D 4C 8B 71 40 mov r14,qword ptr [rcx+40h] 00007FFD972AF031 4C 8B 79 48 mov r15,qword ptr [rcx+48h] 00007FFD972AF035 0F AE 51 58 ldmxcsr dword ptr [rcx+58h] 00007FFD972AF039 DB E2 fnclex 00007FFD972AF03B D9 69 5C fldcw word ptr [rcx+5Ch] 00007FFD972AF03E 66 0F 6F 71 60 movdqa xmm6,xmmword ptr [rcx+60h] 00007FFD972AF043 66 0F 6F 79 70 movdqa xmm7,xmmword ptr [rcx+70h] 00007FFD972AF048 66 44 0F 6F 81 80 00 00 00 movdqa xmm8,xmmword ptr [rcx+80h] 00007FFD972AF051 66 44 0F 6F 89 90 00 00 00 movdqa xmm9,xmmword ptr [rcx+90h] 00007FFD972AF05A 66 44 0F 6F 91 A0 00 00 00 movdqa xmm10,xmmword ptr [rcx+0A0h] 00007FFD972AF063 66 44 0F 6F 99 B0 00 00 00 movdqa xmm11,xmmword ptr [rcx+0B0h] 00007FFD972AF06C 66 44 0F 6F A1 C0 00 00 00 movdqa xmm12,xmmword ptr [rcx+0C0h] 00007FFD972AF075 66 44 0F 6F A9 D0 00 00 00 movdqa xmm13,xmmword ptr [rcx+0D0h] 00007FFD972AF07E 66 44 0F 6F B1 E0 00 00 00 movdqa xmm14,xmmword ptr [rcx+0E0h] 00007FFD972AF087 66 44 0F 6F B9 F0 00 00 00 movdqa xmm15,xmmword ptr [rcx+0F0h] 00007FFD972AF090 48 8B 51 50 mov rdx,qword ptr [rcx+50h] 00007FFD972AF094 48 8B 69 18 mov rbp,qword ptr [rcx+18h] 00007FFD972AF098 48 8B 61 10 mov rsp,qword ptr [rcx+10h] 00007FFD972AF09C FF E2 jmp rdx </pre> <br /> <hr style='width: 50%' /><br /> <br /> 자, 그럼 이것을 GetThreadContext와 SetThreadContext로 흉내 내 볼까요? ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > GetThreadContext function (processthreadsapi.h) ; <a target='tab' href='https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadcontext'>https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadcontext</a> SetThreadContext function (processthreadsapi.h) ; <a target='tab' href='https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadcontext'>https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadcontext</a> </pre> <br /> 문제를 명확하게 분리하기 위한 목적으로 Thread를 하나 생성하고, nullptr 참조로 인한 crash가 발생하는 예제를 일부러 작성해 보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #include <windows.h> #include <stdio.h> DWORD WINAPI threadFunc1(LPVOID lpThreadParameter) { <span style='color: blue; font-weight: bold'>char *ptr = nullptr;</span> printf("Thread1: I'll destroy this process after one second.\n"); Sleep(1000); <span style='color: blue; font-weight: bold'>*ptr = '\0'; // null 참조 비정상 종료</span> return 0; } int main() { ::CreateThread(nullptr, 0, threadFunc1, nullptr, 0, nullptr); printf("%s\n", "Press any key to exit.."); getchar(); return 0; } </pre> <br /> 위의 프로그램을 실행하면, 1초 후에 비정상 종료합니다. 이 상태에서, 다음과 같이 GetThreadContext/SetThreadContext를 넣어 볼까요? ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > DWORD WINAPI threadFunc1(LPVOID lpThreadParameter) { CONTEXT ctxThread1 = { 0 }; ctxThread1.ContextFlags = CONTEXT_ALL; // 현재 스레드 실행 문맥을 저장하고, <span style='color: blue; font-weight: bold'>::GetThreadContext(GetCurrentThread(), &ctxThread1);</span> char *ptr = nullptr; printf("Thread1: I'll destroy this process after one second.\n"); Sleep(1000); // 이전에 저장해 둔 실행 문맥을 복원 <span style='color: blue; font-weight: bold'>SetThreadContext(GetCurrentThread(), &ctxThread1);</span> *ptr = '\0'; // null 참조 비정상 종료 return 0; } </pre> <br /> 그런데, 실제로 저렇게 실행하면 SetThreadContext 함수 호출은 (무시라도 하는 것처럼) 마치 아무 일도 없었다는 듯 이후의 "*ptr = '\0'" 코드를 실행합니다. 오호~~~ 이유가 뭘까요? ^^<br /> <br /> 사실, 분명히 SetThreadContext는 GetThreadContext를 수행했던 시기의 EIP/RIP로 정확하게 이동을 합니다. 문제는, GetThreadContext의 호출로 저장된 EIP/RIP의 위치입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 00007FFDCBFEEC38 0F 1F 84 00 00 00 00 00 nop dword ptr [rax+rax] 00007FFDCBFEEC40 4C 8B D1 mov r10,rcx 00007FFDCBFEEC43 B8 F2 00 00 00 mov eax,0F2h 00007FFDCBFEEC48 F6 04 25 08 03 FE 7F 01 test byte ptr [7FFE0308h],1 00007FFDCBFEEC50 75 03 jne NtGetContextThread+15h (07FFDCBFEEC55h) 00007FFDCBFEEC52 0F 05 syscall <span style='color: blue; font-weight: bold'>00007FFDCBFEEC54 C3 ret</span> </pre> <br /> 위의 코드에서 NtGetContextThread는 syscall로 User 모드에서 Kernel 모드로의 이전이 이뤄지고 결국 GetThreadContext의 EIP/RIP 값은 바로 그다음에 해당하는 (00007FFDCBFEEC54 주소의) ret 위치를 담게 됩니다.<br /> <br /> 이후 SetThreadContext가 호출되면 00007FFDCBFEEC54 위치로 실행이 이동하게 되고, 곧이어 ret을 호출하기 때문에 결국 SetThreadContext의 호출이 ret 처리된 것이나 다름없게 되는 효과를 냅니다. 따라서, 우리가 의도한 목적을 달성하려면 EIP/RIP의 위치 보정이 필요합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>DWORD64 g_jmpBase;</span> void SetJumpPoint() { <span style='color: blue; font-weight: bold'>g_jmpBase = *(DWORD64*)_AddressOfReturnAddress();</span> } DWORD WINAPI threadFunc1(LPVOID lpThreadParameter) { CONTEXT ctxThread1 = { 0 }; ctxThread1.ContextFlags = CONTEXT_ALL; ::GetThreadContext(GetCurrentThread(), &ctxThread1); <span style='color: blue; font-weight: bold'>SetJumpPoint(); ctxThread1.Rip = g_jmpBase;</span> char *ptr = nullptr; printf("Thread1: I'll destroy this process after one second.\n"); Sleep(1000); printf("Context.Rip: <span style='text-decoration: line-through'><a target='tab' href='https://www.youtube.com/watch?v=LrHTR22pIhw'>(Dormammu)</a></span> Thread1, I've Come To Bargain.\n"); SetThreadContext(GetCurrentThread(), &ctxThread1); *ptr = '\0'; return 0; } </pre> <br /> 이렇게 하고 나서야, 이제 비로소 우리가 의도했던 대로 SetThreadContext로 인한 이전 코드로의 실행 점프가 됩니다. (즉, *ptr = '0' 코드는 수행되지 않고 SetJumpPoint 호출 위치로 무한 반복을 합니다.)<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1810&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 참고로, g_jmpBase를 반환 처리하면 안 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > DWORD64 GetJumpPoint() { return *(DWORD64*)_AddressOfReturnAddress(); } DWORD WINAPI threadFunc1(LPVOID lpThreadParameter) { CONTEXT ctxThread1 = { 0 }; ctxThread1.ContextFlags = CONTEXT_ALL; ::GetThreadContext(GetCurrentThread(), &ctxThread1); <span style='color: blue; font-weight: bold'>ctxThread1.Rip = GetJumpPoint();</span> char *ptr = nullptr; printf("Thread1: I'll destroy this process after one second.\n"); Sleep(1000); printf("Context.Rip: <span style='text-decoration: line-through'><a target='tab' href='https://www.youtube.com/watch?v=LrHTR22pIhw'>(Dormammu)</a></span> Thread1, I've Come To Bargain.\n"); SetThreadContext(GetCurrentThread(), &ctxThread1); *ptr = '\0'; return 0; } </pre> <br /> 위와 같이 하면, 처음 한 번의 SetThreadContext 호출 시에는 정상적으로 동작하지만 두 번째 호출 시에는 비정상 종료가 됩니다. 이때 만약 Visual Studio IDE 내에서 디버깅 중이라면 아래와 같은 유형의 예외 메시지를 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Exception thrown at <span style='color: blue; font-weight: bold'>0x0000000000000000</span> in ConsoleApplication2.exe: 0xC0000005: Access violation executing location 0x0000000000000000. </pre> <br /> 즉, SetThreadContext에 지정된 ctxThread1.Rip의 주솟값이 0x0000000000000000이었기 때문에 그리로 점프해 (당연히) 비정상 종료를 한 것인데요.<br /> <br /> 원인은, GetJumpPoint가 반환한 위치는 아래에서 "00007FF60DDE1888"로 rax 값을 변수 ctxThread1.Rip에 대입하는 위치이기 때문입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 27: ctxThread1.Rip = GetJumpPoint(); 00007FF60DDE1883 E8 65 FB FF FF call GetJumpPoint (07FF60DDE13EDh) <span style='color: blue; font-weight: bold'>00007FF60DDE1888 48 89 85 08 01 00 00 mov qword ptr [rbp+108h],rax </span> </pre> <br /> 따라서 SetThreadContext 호출 시점의 rax가 ctxThread1.Rip에 담기게 되는데, 그때의 rax에 들어 있던 값이 0이어서 저런 현상이 발생하는 것입니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1059
(왼쪽의 숫자를 입력해야 합니다.)