x64调用约定下,不再使用stack frame pointer(如ebp) ,并且unwind info,seh table都是编译期生成。
对于动态生成的代码,如执行shellcode等,异常表中没有RUMTIME_FUNCTION,编译器无法正常展开调用栈。
因此需要手动构造动态代码的RUNTIME_FUNCTION信息,并调用系统函数RtlAddFunctionTable,向系统注册动态代码的栈展开或异常处理机制。
网上讲解x64调用约定的文章很多,但是如何构造异常表的资料很少,自己找了一些别人的代码,做了改进。
参考资料:
RUNTIME_FUNCTION结构体详解 https://blog.csdn.net/tutucoo/article/details/83828700
UNWIND_CODE结构体详解 https://docs.microsoft.com/en-us/previous-versions/ck9asaa9(v=vs.140)
目前只有了最基础的unwind 和 单条 seh exceptionhandler。源代码如下:
1 #include <iostream> 2 #include <vector> 3 4 #include <stdio.h> 5 #include <stdlib.h> 6 #include <stdint.h> 7 8 #include <windows.h> 9 10 typedef uint8_t UBYTE; 11 typedef uint16_t USHORT; 12 13 typedef void(__cdecl* CallTestFunc)(void); 14 15 typedef enum _UNWIND_OP_CODES { 16 UWOP_PUSH_NONVOL = 0, 17 UWOP_ALLOC_LARGE, // 1 18 UWOP_ALLOC_SMALL, // 2 19 UWOP_SET_FPREG, // 3 20 UWOP_SAVE_NONVOL, // 4 21 UWOP_SAVE_NONVOL_FAR, // 5 22 UWOP_SPARE_CODE1, // 6 23 UWOP_SPARE_CODE2, // 7 24 UWOP_SAVE_XMM128, // 8 25 UWOP_SAVE_XMM128_FAR, // 9 26 UWOP_PUSH_MACHFRAME // 10 27 } UNWIND_OP_CODES, *PUNWIND_OP_CODES; 28 29 typedef union _UNWIND_CODE { 30 struct { 31 UBYTE CodeOffset; 32 UBYTE UnwindOp : 4; 33 UBYTE OpInfo : 4; 34 }; 35 USHORT FrameOffset; 36 } UNWIND_CODE, *PUNWIND_CODE; 37 38 typedef struct _UNWIND_INFO { 39 UBYTE Version : 3; 40 UBYTE Flags : 5; 41 UBYTE SizeOfProlog; 42 UBYTE CountOfCodes; 43 UBYTE FrameRegister : 4; 44 UBYTE FrameOffset : 4; 45 UNWIND_CODE UnwindCode[1]; 46 /* UNWIND_CODE MoreUnwindCode[((CountOfCodes + 1) & ~1) - 1]; 47 * OPTIONAL ULONG ExceptionHandler; 48 * OPTIONAL ULONG ExceptionData[]; */ 49 } UNWIND_INFO, *PUNWIND_INFO; 50 51 typedef struct { 52 uint8_t code[0x1000]; 53 RUNTIME_FUNCTION function_table[1]; 54 UNWIND_INFO unwind_info[1]; 55 } DYNSECTION; 56 57 58 // FILTER HANDLER FINALLY_HANDLER 59 #define UNW_FLAG_NHANDLER 0x0 // NO NO 60 #define UNW_FLAG_EHANDLER 0x1 // YES YES 61 #define UNW_FLAG_UHANDLER 0x2 // YES 62 #define UNW_FLAG_CHAININFO 0x4 // multi UNWIND_INFO 63 64 struct STACK_FRAME_INFO { 65 PVOID returnAddress; 66 PVOID functionAddress; 67 }; 68 69 struct CALL_STACK_INFO { 70 PVOID stackBottom; 71 PVOID stackTop; 72 DWORD dwFrameCount; 73 STACK_FRAME_INFO* pFrameList; 74 }; 75 76 typedef ULONG(WINAPI *RTLWALKFRAMECHAIN)(OUT PVOID *Callers, IN ULONG Count, IN ULONG Flags); 77 78 void GetCallStackInfo(CALL_STACK_INFO& callStack, DWORD dwMaxFrame) 79 { 80 std::vector<STACK_FRAME_INFO> vecFrames; 81 #ifdef _M_IX86 82 STACK_FRAME_INFO StackFrame; 83 std::vector<PVOID> vecRetAddr; 84 ULONG StackCount = 0; 85 RTLWALKFRAMECHAIN pRtlWalkFrameChain = 86 (RTLWALKFRAMECHAIN)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlWalkFrameChain"); 87 88 vecRetAddr.resize(50); 89 StackCount = pRtlWalkFrameChain(&vecRetAddr[0], vecRetAddr.size(), 0); 90 91 for (ULONG i = 0;i < StackCount;i++) 92 { 93 ZeroMemory(&StackFrame, sizeof(StackFrame)); 94 95 if (vecRetAddr[i] == nullptr) 96 { 97 break; 98 } 99 100 StackFrame.returnAddress = vecRetAddr[i]; 101 102 vecFrames.push_back(StackFrame); 103 } 104 105 #elif (_M_X64) 106 CONTEXT Context; 107 KNONVOLATILE_CONTEXT_POINTERS NvContext; 108 UNWIND_HISTORY_TABLE UnwindHistoryTable; 109 PRUNTIME_FUNCTION RuntimeFunction; 110 PVOID HandlerData; 111 ULONG64 EstablisherFrame; 112 ULONG64 ImageBase; 113 STACK_FRAME_INFO StackFrame; 114 115 RtlCaptureContext(&Context); 116 117 ZeroMemory(&UnwindHistoryTable, sizeof(UNWIND_HISTORY_TABLE)); 118 119 for (ULONG Frame = 0; Frame <= dwMaxFrame; Frame++) 120 { 121 RuntimeFunction = RtlLookupFunctionEntry( 122 Context.Rip, 123 &ImageBase, 124 &UnwindHistoryTable 125 ); 126 127 ZeroMemory(&NvContext, sizeof(KNONVOLATILE_CONTEXT_POINTERS)); 128 129 if (!RuntimeFunction) 130 { 131 Context.Rip = (ULONG64)(*(PULONG64)Context.Rsp); 132 Context.Rsp += 8; 133 } 134 else 135 { 136 RtlVirtualUnwind( 137 UNW_FLAG_NHANDLER, 138 ImageBase, 139 Context.Rip, 140 RuntimeFunction, 141 &Context, 142 &HandlerData, 143 &EstablisherFrame, 144 &NvContext); 145 } 146 147 if (!Context.Rip) 148 { 149 break; 150 } 151 152 ZeroMemory(&StackFrame, sizeof(StackFrame)); 153 154 StackFrame.returnAddress = (PVOID)Context.Rip; 155 StackFrame.functionAddress = (PVOID)(ImageBase + RuntimeFunction->BeginAddress); 156 157 vecFrames.push_back(StackFrame); 158 } 159 #endif 160 161 callStack.pFrameList = (STACK_FRAME_INFO*)malloc(vecFrames.size() * sizeof(STACK_FRAME_INFO)); 162 if (!callStack.pFrameList) 163 { 164 callStack.dwFrameCount = 0; 165 return; 166 } 167 168 callStack.dwFrameCount = vecFrames.size(); 169 for (DWORD dwFrame = 0; dwFrame < vecFrames.size(); dwFrame++) 170 { 171 CopyMemory(&callStack.pFrameList[dwFrame], &vecFrames[dwFrame], sizeof(STACK_FRAME_INFO)); 172 } 173 } 174 175 void Func1() 176 { 177 CALL_STACK_INFO callStack; 178 ZeroMemory(&callStack, sizeof(callStack)); 179 GetCallStackInfo(callStack, 100); 180 181 for (DWORD dwFrame = 0; dwFrame < callStack.dwFrameCount; dwFrame++) 182 { 183 printf( 184 "FRAME %02x: FuncAddrss=%p ReturnAddress=%p \n", 185 dwFrame, 186 callStack.pFrameList[dwFrame].functionAddress, 187 callStack.pFrameList[dwFrame].returnAddress); 188 } 189 190 } 191 192 void DumpCallStack() 193 { 194 Func1(); 195 } 196 197 DWORD TestForSehAddFunctionTable(); 198 DWORD TestForUnwindFunctionTable(); 199 200 int main() 201 { 202 TestForUnwindFunctionTable(); 203 204 ::system("pause"); 205 } 206 207 #ifdef _M_X64 208 209 DWORD TestForUnwindFunctionTable() 210 { 211 DYNSECTION *pDynData = (DYNSECTION*)VirtualAlloc(NULL, 0x2000, MEM_COMMIT, PAGE_EXECUTE_READWRITE); 212 uint8_t *code = pDynData->code; 213 ULONG_PTR p = 0; 214 code[p++] = 0x56; //push rsi 215 code[p++] = 0x57; //push rdi 216 code[p++] = 0x48; 217 code[p++] = 0x81; 218 code[p++] = 0xec; 219 code[p++] = 0x88; 220 code[p++] = 0x07; 221 code[p++] = 0x00; 222 code[p++] = 0x00; //sub rsp,788h 223 ULONG_PTR prologSize = p; 224 code[p++] = 0x48; 225 code[p++] = 0xb8; //mov rax, xxxxxxxh 226 ULONG_PTR returnAddress = p; 227 for (int i = 0; i < sizeof(ULONG_PTR); i++) 228 { 229 code[p++] = 0x00; 230 } 231 code[p++] = 0x50; //push rax 232 code[p++] = 0x48; 233 code[p++] = 0xb8; //mov rax, xxxxxxxh 234 ULONG_PTR targetAddress = p; 235 for (int i = 0; i < sizeof(ULONG_PTR); i++) 236 { 237 code[p++] = 0x00; 238 } 239 code[p++] = 0x50; //push rax 240 code[p++] = 0xc3; //ret 241 ULONG_PTR offsetRip = p; 242 code[p++] = 0x48; 243 code[p++] = 0x81; 244 code[p++] = 0xc4; 245 code[p++] = 0x88; 246 code[p++] = 0x07; 247 code[p++] = 0x00; 248 code[p++] = 0x00; //add rsp,788h 249 code[p++] = 0x5f; //pop rdi 250 code[p++] = 0x5e; //pop rsi 251 code[p++] = 0xc3; //ret 252 ULONG_PTR trampoline = p; 253 254 UNWIND_INFO *pUnwindInfo = pDynData->unwind_info; 255 pUnwindInfo[0].Version = 1; 256 pUnwindInfo[0].Flags = UNW_FLAG_NHANDLER; 257 pUnwindInfo[0].SizeOfProlog = (UBYTE)prologSize; 258 pUnwindInfo[0].CountOfCodes = 3; 259 pUnwindInfo[0].FrameRegister = 0; 260 pUnwindInfo[0].FrameOffset = 0; 261 262 DWORD64 dyn_base = (DWORD64)pDynData; 263 264 UNWIND_CODE *pUnwindCode = pUnwindInfo->UnwindCode; 265 266 //pUnwindCode[2].CodeOffset = 10; 267 //pUnwindCode[2].UnwindOp = UWOP_ALLOC_LARGE; 268 //pUnwindCode[2].OpInfo = 0; 269 // 270 //pUnwindCode[1].CodeOffset = 2; 271 //pUnwindCode[1].UnwindOp = UWOP_PUSH_NONVOL; 272 //pUnwindCode[1].OpInfo = 7; 273 // 274 //pUnwindCode[0].CodeOffset = 1; 275 //pUnwindCode[0].UnwindOp = UWOP_PUSH_NONVOL; 276 //pUnwindCode[0].OpInfo = 6; 277 // push xxx must before sub rsp, xxx 278 pUnwindCode[0].FrameOffset = 0x6001; 279 pUnwindCode[1].FrameOffset = 0x7002; 280 pUnwindCode[2].FrameOffset = 0x010a; 281 282 283 284 RUNTIME_FUNCTION *function_table = pDynData->function_table; 285 function_table[0].BeginAddress = (DWORD64)&code[0] - dyn_base; // set RVA of dynamic code start 286 function_table[0].EndAddress = (DWORD64)&code[trampoline] - dyn_base; // RVA of dynamic code end 287 function_table[0].UnwindInfoAddress = (DWORD64)pUnwindCode - dyn_base; // RVA of unwind info 288 289 *(DWORD64*)&code[returnAddress] = (DWORD64)code + offsetRip; 290 *(DWORD64*)&code[targetAddress] = (DWORD64)DumpCallStack; // VA of target 291 292 if (!RtlAddFunctionTable(function_table, 1, dyn_base)) { 293 printf("RtlAddFunctionTable() failed, exit.\n"); 294 exit(EXIT_FAILURE); 295 } 296 297 void(*call)() = (void(*)())code; 298 (*call)(); 299 300 return 0; 301 } 302 303 static EXCEPTION_DISPOSITION handler( 304 PEXCEPTION_RECORD ExceptionRecord, 305 ULONG64 EstablisherFrame, 306 PCONTEXT ContextRecord, 307 PDISPATCHER_CONTEXT DispatcherContext 308 ) 309 { 310 printf("handler!\n"); 311 ContextRecord->Rip += 3; 312 return ExceptionContinueExecution; 313 } 314 315 DWORD TestForSehAddFunctionTable() 316 { 317 int ret; 318 RUNTIME_FUNCTION *q; 319 DYNSECTION *dynsection = (DYNSECTION*)VirtualAlloc(NULL, 0x2000, MEM_COMMIT, PAGE_EXECUTE_READWRITE); 320 321 uint8_t *code = dynsection->code; 322 size_t p = 0; 323 code[p++] = 0xb8; // mov rax, 42 324 code[p++] = 0x2a; 325 code[p++] = 0x00; 326 code[p++] = 0x00; 327 code[p++] = 0x00; 328 code[p++] = 0xc6; // mov byte [rax], 0 -- raises exception! 329 code[p++] = 0x00; 330 code[p++] = 0x00; 331 code[p++] = 0xc3; // ret 332 333 size_t trampoline = p; 334 code[p++] = 0x48; // mov rax, 335 code[p++] = 0xb8; 336 size_t patch_handler_address = p; 337 code[p++] = 0x00; // address to handler patched here 338 code[p++] = 0x00; 339 code[p++] = 0x00; 340 code[p++] = 0x00; 341 code[p++] = 0x00; 342 code[p++] = 0x00; 343 code[p++] = 0x00; 344 code[p++] = 0x00; 345 code[p++] = 0xff; // jmp rax 346 code[p++] = 0xe0; 347 348 DWORD64 dyn_base = 0; 349 q = RtlLookupFunctionEntry((DWORD64)code, &dyn_base, NULL); 350 printf("lookup 'code' %p %llx\n", q, dyn_base); // no function table entry 351 352 DWORD64 image_base = 0; 353 q = RtlLookupFunctionEntry((DWORD64)main, &image_base, NULL); 354 printf("lookup 'main' %p %llx\n", q, image_base); // there is a function table entry 355 356 dyn_base = (DWORD64)dynsection; 357 UNWIND_INFO *unwind_info = dynsection->unwind_info; 358 unwind_info[0].Version = 1; 359 unwind_info[0].Flags = UNW_FLAG_EHANDLER; 360 unwind_info[0].SizeOfProlog = 0; 361 unwind_info[0].CountOfCodes = 0; 362 unwind_info[0].FrameRegister = 0; 363 unwind_info[0].FrameOffset = 0; 364 *(DWORD *)&unwind_info[0].UnwindCode = (DWORD64)&code[trampoline] - dyn_base; 365 366 RUNTIME_FUNCTION *function_table = dynsection->function_table; 367 function_table[0].BeginAddress = (DWORD64)&code[0] - dyn_base; // set RVA of dynamic code start 368 function_table[0].EndAddress = (DWORD64)&code[trampoline] - dyn_base; // RVA of dynamic code end 369 function_table[0].UnwindInfoAddress = (DWORD64)unwind_info - dyn_base; // RVA of unwind info 370 371 *(DWORD64 *)&code[patch_handler_address] = (DWORD64)handler; // VA of handler 372 373 printf("main VA %016llx\n", (DWORD64)main); 374 printf("code VA %016llx\n", (DWORD64)code); 375 printf("function table VA %016llx\n", (DWORD64)function_table); 376 printf("unwind info VA %016llx\n", (DWORD64)unwind_info); 377 printf("handler VA %016llx\n", (DWORD64)handler); 378 printf("RUNTIME_FUNCTION begin RVA %08x, end RVA %08x, unwind RVA %08x\n", 379 function_table[0].BeginAddress, function_table[0].EndAddress, 380 function_table[0].UnwindInfoAddress); 381 printf("UNWIND_INFO handler RVA %08x\n", *(DWORD *)&unwind_info[0].UnwindCode); 382 383 if (!RtlAddFunctionTable(function_table, 1, dyn_base)) { 384 printf("RtlAddFunctionTable() failed, exit.\n"); 385 exit(EXIT_FAILURE); 386 } 387 388 q = RtlLookupFunctionEntry((DWORD64)code, &dyn_base, NULL); 389 printf("lookup 'code' %p %llx\n", q, dyn_base); // should return address of function table entry 390 391 uint64_t(*call)() = (uint64_t(*)()) code; 392 uint64_t result = (*call)(); 393 printf("result = %llx\n", result); 394 395 if (!RtlDeleteFunctionTable(function_table)) { 396 printf("RtlDeleteFunctionTable() failed, exit.\n"); 397 exit(EXIT_FAILURE); 398 } 399 400 return EXIT_SUCCESS; 401 } 402 #endif