ดังที่ได้ตอบไว้ข้างต้นคำตอบที่ถูกต้องคือการรวบรวมทุกอย่างด้วย VS2015 แต่สำหรับความสนใจสิ่งต่อไปนี้คือการวิเคราะห์ปัญหาของฉัน
สัญลักษณ์นี้ไม่ได้ถูกกำหนดไว้ในไลบรารีแบบคงที่ที่ Microsoft จัดเตรียมไว้ให้โดยเป็นส่วนหนึ่งของ VS2015 ซึ่งค่อนข้างแปลกเนื่องจากมีสัญลักษณ์อื่น ๆ ทั้งหมด ในการค้นหาสาเหตุเราต้องดูที่การประกาศฟังก์ชันนั้นและที่สำคัญกว่านั้นคือวิธีการใช้งาน
นี่คือตัวอย่างจากส่วนหัว Visual Studio 2008:
_CRTIMP FILE * __cdecl __iob_func(void);
#define stdin (&__iob_func()[0])
#define stdout (&__iob_func()[1])
#define stderr (&__iob_func()[2])
ดังนั้นเราจึงเห็นได้ว่าหน้าที่ของฟังก์ชันคือการส่งคืนจุดเริ่มต้นของอาร์เรย์ของออบเจ็กต์ FILE (ไม่ใช่แฮนเดิล "FILE *" คือแฮนเดิล FILE คือโครงสร้างข้อมูลแบบทึบที่เก็บข้อมูลสถานะที่สำคัญ) ผู้ใช้ฟังก์ชั่นนี้คือสามมาโคร stdin, stdout และ stderr ซึ่งใช้สำหรับการเรียกสไตล์ fscanf, fprintf ต่างๆ
ตอนนี้เรามาดูกันว่า Visual Studio 2015 กำหนดสิ่งเดียวกันอย่างไร:
_ACRTIMP_ALT FILE* __cdecl __acrt_iob_func(unsigned);
#define stdin (__acrt_iob_func(0))
#define stdout (__acrt_iob_func(1))
#define stderr (__acrt_iob_func(2))
ดังนั้นแนวทางจึงเปลี่ยนไปสำหรับฟังก์ชั่นการแทนที่ในขณะนี้ส่งคืนหมายเลขอ้างอิงของไฟล์แทนที่จะเป็นที่อยู่ของอาร์เรย์ของวัตถุไฟล์และมาโครได้เปลี่ยนไปเป็นการเรียกใช้ฟังก์ชันที่ส่งผ่านในหมายเลขระบุ
เหตุใดจึงไม่สามารถจัดหา API ที่เข้ากันได้? มีกฎสำคัญสองข้อที่ Microsoft ไม่สามารถฝ่าฝืนในแง่ของการใช้งานดั้งเดิมผ่าน __iob_func:
- ต้องมีอาร์เรย์ของโครงสร้าง FILE สามชุดซึ่งสามารถจัดทำดัชนีในลักษณะเดียวกันกับก่อนหน้านี้
- เค้าโครงโครงสร้างของ FILE ไม่สามารถเปลี่ยนแปลงได้
การเปลี่ยนแปลงใด ๆ ในข้อใดข้อหนึ่งข้างต้นจะหมายถึงโค้ดที่คอมไพล์ที่มีอยู่ซึ่งเชื่อมโยงซึ่งจะผิดพลาดอย่างร้ายแรงหากมีการเรียก API นั้น
มาดูกันว่า FILE ถูกกำหนดไว้อย่างไร
อันดับแรกนิยามไฟล์ VS2008:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
และตอนนี้นิยามไฟล์ VS2015:
typedef struct _iobuf
{
void* _Placeholder;
} FILE;
ดังนั้นจึงมีปมของมันคือโครงสร้างมีการเปลี่ยนแปลงรูปร่าง โค้ดที่คอมไพล์ที่มีอยู่ซึ่งอ้างถึง __iob_func ขึ้นอยู่กับข้อเท็จจริงที่ว่าข้อมูลที่ส่งคืนเป็นทั้งอาร์เรย์ที่สามารถจัดทำดัชนีได้และในอาร์เรย์นั้นองค์ประกอบจะอยู่ห่างกันเท่ากัน
วิธีแก้ปัญหาที่เป็นไปได้ที่กล่าวถึงในคำตอบข้างต้นตามบรรทัดเหล่านี้จะใช้ไม่ได้ (หากถูกเรียก) ด้วยเหตุผลบางประการ:
FILE _iob[] = {*stdin, *stdout, *stderr};
extern "C" FILE * __cdecl __iob_func(void)
{
return _iob;
}
อาร์เรย์ FILE _iob จะคอมไพล์ด้วย VS2015 ดังนั้นจึงถูกจัดวางเป็นบล็อกของโครงสร้างที่มีโมฆะ * สมมติว่าการจัดตำแหน่ง 32 บิตองค์ประกอบเหล่านี้จะห่างกัน 4 ไบต์ ดังนั้น _iob [0] จึงอยู่ที่ออฟเซ็ต 0, _iob [1] อยู่ที่ออฟเซ็ต 4 และ _iob [2] อยู่ที่ออฟเซ็ต 8 รหัสการเรียกจะคาดว่า FILE จะยาวกว่ามากโดยจัดให้อยู่ที่ 32 ไบต์ในระบบของฉัน มันจะใช้ที่อยู่ของอาร์เรย์ที่ส่งคืนและเพิ่ม 0 ไบต์เพื่อไปยังองค์ประกอบศูนย์ (อันนั้นใช้ได้) แต่สำหรับ _iob [1] จะอนุมานได้ว่าต้องเพิ่ม 32 ไบต์และสำหรับ _iob [2] มันจะอนุมาน จำเป็นต้องเพิ่ม 64 ไบต์ (เนื่องจากเป็นลักษณะที่ปรากฏในส่วนหัว VS2008) และแน่นอนรหัสถอดสำหรับ VS2008 แสดงให้เห็นถึงสิ่งนี้
ปัญหารองของโซลูชันข้างต้นคือการคัดลอกเนื้อหาของโครงสร้าง FILE (* stdin) ไม่ใช่ที่จับ FILE * ดังนั้นรหัส VS2008 ใด ๆ จะดูโครงสร้างพื้นฐานที่แตกต่างจาก VS2015 สิ่งนี้อาจใช้ได้ผลหากโครงสร้างมีเพียงตัวชี้ แต่นั่นเป็นความเสี่ยงใหญ่ ไม่ว่าในกรณีใดปัญหาแรกจะทำให้เกิดปัญหานี้ไม่เกี่ยวข้อง
การแฮ็กเพียงอย่างเดียวที่ฉันสามารถฝันถึงคือสิ่งที่ __iob_func เดินไปตามสแต็กการโทรเพื่อดูว่าไฟล์ใดที่จัดการได้จริงที่พวกเขากำลังมองหา (ขึ้นอยู่กับออฟเซ็ตที่เพิ่มไปยังที่อยู่ที่ส่งคืน) และส่งกลับค่าที่คำนวณได้เช่นนั้น ให้คำตอบที่ถูกต้อง นี่เป็นสิ่งที่บ้าคลั่งพอ ๆ กับที่ฟัง แต่ต้นแบบสำหรับ x86 เท่านั้น (ไม่ใช่ x64) แสดงอยู่ด้านล่างเพื่อความบันเทิงของคุณ การทดลองของฉันใช้ได้ผลดี แต่ระยะของคุณอาจแตกต่างกันไป - ไม่แนะนำให้ใช้ในการผลิต!
#include <windows.h>
#include <stdio.h>
#include <dbghelp.h>
/* #define LOG */
#if defined(_M_IX86)
#define GET_CURRENT_CONTEXT(c, contextFlags) \
do { \
c.ContextFlags = contextFlags; \
__asm call x \
__asm x: pop eax \
__asm mov c.Eip, eax \
__asm mov c.Ebp, ebp \
__asm mov c.Esp, esp \
} while(0);
#else
/* This should work for 64-bit apps, but doesn't */
#define GET_CURRENT_CONTEXT(c, contextFlags) \
do { \
c.ContextFlags = contextFlags; \
RtlCaptureContext(&c); \
} while(0);
#endif
FILE * __cdecl __iob_func(void)
{
CONTEXT c = { 0 };
STACKFRAME64 s = { 0 };
DWORD imageType;
HANDLE hThread = GetCurrentThread();
HANDLE hProcess = GetCurrentProcess();
GET_CURRENT_CONTEXT(c, CONTEXT_FULL);
#ifdef _M_IX86
imageType = IMAGE_FILE_MACHINE_I386;
s.AddrPC.Offset = c.Eip;
s.AddrPC.Mode = AddrModeFlat;
s.AddrFrame.Offset = c.Ebp;
s.AddrFrame.Mode = AddrModeFlat;
s.AddrStack.Offset = c.Esp;
s.AddrStack.Mode = AddrModeFlat;
#elif _M_X64
imageType = IMAGE_FILE_MACHINE_AMD64;
s.AddrPC.Offset = c.Rip;
s.AddrPC.Mode = AddrModeFlat;
s.AddrFrame.Offset = c.Rsp;
s.AddrFrame.Mode = AddrModeFlat;
s.AddrStack.Offset = c.Rsp;
s.AddrStack.Mode = AddrModeFlat;
#elif _M_IA64
imageType = IMAGE_FILE_MACHINE_IA64;
s.AddrPC.Offset = c.StIIP;
s.AddrPC.Mode = AddrModeFlat;
s.AddrFrame.Offset = c.IntSp;
s.AddrFrame.Mode = AddrModeFlat;
s.AddrBStore.Offset = c.RsBSP;
s.AddrBStore.Mode = AddrModeFlat;
s.AddrStack.Offset = c.IntSp;
s.AddrStack.Mode = AddrModeFlat;
#else
#error "Platform not supported!"
#endif
if (!StackWalk64(imageType, hProcess, hThread, &s, &c, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL))
{
#ifdef LOG
printf("Error: 0x%08X (Address: %p)\n", GetLastError(), (LPVOID)s.AddrPC.Offset);
#endif
return NULL;
}
if (s.AddrReturn.Offset == 0)
{
return NULL;
}
{
unsigned char const * assembly = (unsigned char const *)(s.AddrReturn.Offset);
#ifdef LOG
printf("Code bytes proceeding call to __iob_func: %p: %02X,%02X,%02X\n", assembly, *assembly, *(assembly + 1), *(assembly + 2));
#endif
if (*assembly == 0x83 && *(assembly + 1) == 0xC0 && (*(assembly + 2) == 0x20 || *(assembly + 2) == 0x40))
{
if (*(assembly + 2) == 32)
{
return (FILE*)((unsigned char *)stdout - 32);
}
if (*(assembly + 2) == 64)
{
return (FILE*)((unsigned char *)stderr - 64);
}
}
else
{
return stdin;
}
}
return NULL;
}