ring0 APC DLL injection crash target process on win7 - crash

I am trying to implement a ring0 dll injector driver and implement by APC injection. the code work perfectly on win XP. by on win7, it keeps crashing the target process.
here is the code:
NTSTATUS InjectDll( IN ULONG ulProcessId, HANDLE hEvent, IN TCHAR * pszDllPath )
{
NTSTATUS ntStatus;
HANDLE hProcess = NULL;
OBJECT_ATTRIBUTES ObjectAttributes;
CLIENT_ID ClientId;
PKEVENT pevtWait;
PKPROCESS pPcb = NULL;
PVOID pArg = NULL;
ULONG ulSize = 0;
KAPC_STATE ApcState;
PKAPC pApc;
PVOID pTcb;
PVOID pfnLoadLibrary;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
LARGE_INTEGER nTimeout;
nTimeout.QuadPart = (LONGLONG) -300000000;
do
{
InitializeObjectAttributes(
&ObjectAttributes,
NULL,
0,
NULL,
NULL)
ClientId.UniqueProcess = (HANDLE)ulProcessId;
ClientId.UniqueThread = 0;
ntStatus = ZwOpenProcess(
&hProcess,
PROCESS_ALL_ACCESS,
&ObjectAttributes,
&ClientId
);
if ( NT_ERROR(ntStatus) )
{
DbgPrint("Open process %d failed - error 0x%X\r\n", ulProcessId, ntStatus);
break;
}
ulSize = MAX_FILENAME_LEN * sizeof(WCHAR);
ntStatus = ZwAllocateVirtualMemory(
hProcess,
&pArg,
0,
&ulSize,
MEM_RESERVE|MEM_COMMIT,
PAGE_READWRITE);
if ( NT_ERROR(ntStatus) )
{
DbgPrint("ZwAllocateVirtualMemory failed - error 0x%X\r\n", ntStatus);
// thus, make it leak in target process space!
break;
}
ntStatus = ObReferenceObjectByHandle(
hProcess,
PROCESS_ALL_ACCESS,
0,//should be PsProcessType,
PreviousMode,
(PVOID*)&pPcb,
NULL);
if ( NT_ERROR(ntStatus) )
{
DbgPrint("ObReferenceObjectByHandle 0x%X failed - error 0x%X\r\n", hProcess, ntStatus);
break;
}
//enter target process space
KeStackAttachProcess(pPcb,&ApcState);
pfnLoadLibrary = GetLoadLibraryAddress(pPcb);
if (!pfnLoadLibrary)
{
DbgPrint("Failed to get address of LoadLibrary\r\n");
// leave target process space
KeUnstackDetachProcess(&ApcState);
break;
}
else
{
DbgPrint("Get address of LoadLibrary : 0x%X\r\n", pfnLoadLibrary);
}
RtlCopyMemory( pArg, pszDllPath, MAX_FILENAME_LEN * sizeof(TCHAR));
// leave target process space
KeUnstackDetachProcess(&ApcState);
// get target thread
pTcb = GetThreadByProcess(pPcb);
if (!pTcb)
{
DbgPrint("Get thread failed!\r\n");
ntStatus = STATUS_UNSUCCESSFUL;
break;
}
// start inject
pApc = (PKAPC)ExAllocatePoolWithTag(NonPagedPool,sizeof(KAPC),POOL_TAG);
if (!pApc)
{
DbgPrint("Failed to allocate memory for the APC structure\r\n");
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
break;
}
KeInitializeApc(
pApc,
(PETHREAD)pTcb,
OriginalApcEnvironment,
&ApcKernelRoutine,
NULL,
(PKNORMAL_ROUTINE)pfnLoadLibrary,
UserMode,
pArg);
if (!KeInsertQueueApc(pApc,NULL,NULL,0))
{
DbgPrint("Failed to insert APC\r\n");
ntStatus = STATUS_UNSUCCESSFUL;
ExFreePool(pApc);
break;
}
} while (0);
if ( pPcb )
{
ObDereferenceObject(pPcb);
}
if ( hProcess)
{
ZwClose(hProcess);
}
DbgPrint("InjectDll end - status = 0x%X\r\n", ntStatus);
return ntStatus;
}
injectDll is called by DeviceIoControl serivce routine.
DRIVER_DISPATCH InjectorDeviceControl;
NTSTATUS NTAPI InjectorDeviceControl(
IN PDEVICE_OBJECT pDeviceObject,
PIRP pIrp)
{
PIO_STACK_LOCATION pStack;
PINJECT_DLL_PARAM pParam;
NTSTATUS ntStatus;
DbgPrint("call InjectorDeviceControl...\r\n");
/* Get the stack location and parameters */
pStack = IoGetCurrentIrpStackLocation(pIrp);
pParam = (PINJECT_DLL_PARAM)pIrp->AssociatedIrp.SystemBuffer;
if (pStack->Parameters.DeviceIoControl.IoControlCode != IOCTL_INJECTDLL)
{
/* Unsupported command */
ntStatus = STATUS_NOT_IMPLEMENTED;
}
else
{
/* Validate the input buffer length */
if (pStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(INJECT_DLL_PARAM))
{
/* Invalid buffer */
ntStatus = STATUS_INVALID_PARAMETER;
}
else
{
/* Inject dll */
ntStatus = InjectDll(pParam->ulProcessId, pParam->hEvent, pParam->DllName);
}
}
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return ntStatus;
}
here is the crash stacktrace of target process.
FAULTING_IP:
ntdll!RtlActivateActivationContextUnsafeFast+9c
7770f59a 8933 mov dword ptr [ebx],esi
EXCEPTION_RECORD: ffffffff -- (.exr 0xffffffffffffffff)
ExceptionAddress: 7770f59a (ntdll!RtlActivateActivationContextUnsafeFast+0x0000009c)
ExceptionCode: c0000005 (Access violation)
ExceptionFlags: 00000000
NumberParameters: 2
Parameter[0]: 00000001
Parameter[1]: 00000000
Attempt to write to address 00000000
DEFAULT_BUCKET_ID: NULL_POINTER_WRITE
PROCESS_NAME: QQProtect.exe
ERROR_CODE: (NTSTATUS) 0xc0000005 - 0x%08lx
EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - 0x%08lx
EXCEPTION_PARAMETER1: 00000001
EXCEPTION_PARAMETER2: 00000000
WRITE_ADDRESS: 00000000
FOLLOWUP_IP:
ntdll!RtlActivateActivationContextUnsafeFast+9c
7770f59a 8933 mov dword ptr [ebx],esi
NTGLOBALFLAG: 0
FAULTING_THREAD: 00000300
PRIMARY_PROBLEM_CLASS: NULL_POINTER_WRITE
BUGCHECK_STR: APPLICATION_FAULT_NULL_POINTER_WRITE
LAST_CONTROL_TRANSFER: from 77740baa to 7770f59a
STACK_TEXT:
021df8a0 77740baa 75655a2c 00000000 02790000 ntdll!RtlActivateActivationContextUnsafeFast+0x9c
021df91c 77740461 002b5cd0 021dfab8 756559bc ntdll!LdrpProcessStaticImports+0x1b8
021dfa8c 7774232c 021dfaec 021dfab8 00000000 ntdll!LdrpLoadDll+0x314
021dfac0 759088ee 0026f074 021dfb00 021dfaec ntdll!LdrLoadDll+0x92
021dfaf8 75dc3c12 00000000 00000000 00000001 KERNELBASE!LoadLibraryExW+0x15a
021dfb0c 77726f7d 02790000 00000000 00000000 kernel32!LoadLibraryW+0x11
021dff88 75dc3c45 00000000 021dffd4 777437f5 ntdll!KiUserApcDispatcher+0x25
021dff94 777437f5 00295fe8 75655ce4 00000000 kernel32!BaseThreadInitThunk+0xe
021dffd4 777437c8 7770fd0f 00295fe8 00000000 ntdll!__RtlUserThreadStart+0x70
021dffec 00000000 7770fd0f 00295fe8 00000000 ntdll!_RtlUserThreadStart+0x1b
STACK_COMMAND: ~1s; .ecxr ; kb
SYMBOL_STACK_INDEX: 0
SYMBOL_NAME: ntdll!RtlActivateActivationContextUnsafeFast+9c
FOLLOWUP_NAME: MachineOwner
MODULE_NAME: ntdll
IMAGE_NAME: ntdll.dll
DEBUG_FLR_IMAGE_TIMESTAMP: 4ce7b96e
FAILURE_BUCKET_ID: NULL_POINTER_WRITE_c0000005_ntdll.dll!RtlActivateActivationContextUnsafeFast
BUCKET_ID: APPLICATION_FAULT_NULL_POINTER_WRITE_ntdll!RtlActivateActivationContextUnsafeFast+9c
WATSON_STAGEONE_URL: http://watson.microsoft.com/StageOne/QQProtect_exe/3_0_1_3629/50a06369/ntdll_dll/6_1_7601_17514/4ce7b96e/c0000005/0002f59a.htm?Retriage=1
Followup: MachineOwner
any idea?

I've being working on some dll-injetion by apc and got this exact same problem.
After performing some reverse-engeenering, I found why it occured.
This is caused when the current thread's TEB does not have a ActivationContextStackPointer member:
ntdll!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : (null)
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : (null)
+0x02c ThreadLocalStoragePointer : (null)
+0x030 ProcessEnvironmentBlock : 0x7ffd8000 _PEB
+0x034 LastErrorValue : 0
+0x038 CountOfOwnedCriticalSections : 0
+0x03c CsrClientThread : (null)
+0x040 Win32ThreadInfo : (null)
+0x044 User32Reserved : [26] 0
+0x0ac UserReserved : [5] 0
+0x0c0 WOW32Reserved : (null)
+0x0c4 CurrentLocale : 0x804
+0x0c8 FpSoftwareStatusRegister : 0
+0x0cc SystemReserved1 : [54] (null)
+0x1a4 ExceptionCode : 0n0
+0x1a8 ActivationContextStackPointer : (null) <---it's null
The RtlActivateActivationContextUnsafeFast function tries to insert a node in the ActivationContext list, but the ActivationContextStackPointer is null, so an ACCESS_VIOLATION is raised.
mov dword ptr [ebx],esi //ebx = teb->ActivationContextStackPointer which is 000000000 esi = node
The ActivationContext is kinda related to the manifest of DLL.
You can get more information about this in the Microsoft help.
When loading a dll with a manifest file, the RtlActivateActivationContextUnsafeFast is called.
In case of that, I've solved the problem by disabling the generation of my dll's manifest file:
linker - > (/MANIFEST:NO)
or you can try to enforce the system to allocate a ActivationContextStack for target thread by making the target thread to call a ActivateActCtx function (I'm not sure if it is possible?).
Hope this helps you.

I stumbled upon the same problem, with a slightly different crash on Windows 10 (invalid address access, but not NULL pointer dereference). I didn't like 王云路's solution since I wanted to be able to load any DLL. What worked for me is calling NtQueueApcThread directly instead of using QueueUserAPC. In this case, RtlDispatchAPC is bypassed and the activation functions are not called.
Reference:
https://repnz.github.io/posts/apc/user-apc/#queueuserapc-kernelbase-dll-layer

Related

Why my interrupt is not connected? qemu_irq_is_connected( ) returns false

I'm trying to make interrupt work for a device in qemu. The machnie name is ab21q, a modified version of arm64 virt machine, and the device name is ab21q_axpu.
Here are some relevant codes. I referenced pl011.c. (I switched temporarily back to qemu-5.1.0 for this test.)
==== hw/arm/ab21q.c
machab21q_init(MachineState *machine)
{
.... skip ....
create_ab21q_axpu_device(vms, sysmem); // ab21q-axpu test
....
}
static void create_ab21q_axpu_device(const Ab21qMachineState *vms, MemoryRegion *mem)
{
char *nodename;
hwaddr base = vms->memmap[AB21Q_AXPU].base;
hwaddr size = vms->memmap[AB21Q_AXPU].size;
int irq = vms->irqmap[AB21Q_AXPU];
const char compat[] = "ab21q-axpu";
DeviceState *dev = qdev_new(TYPE_AB21Q_AXPU);
SysBusDevice *s = SYS_BUS_DEVICE(dev);
//sysbus_create_simple("ab21q-axpu", base, qdev_get_gpio_in(vms->gic, irq));
sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
memory_region_add_subregion(mem, base,
sysbus_mmio_get_region(s, 0));
sysbus_connect_irq(s, 0, qdev_get_gpio_in(vms->gic, irq));
nodename = g_strdup_printf("/ab21q_axpu#%" PRIx64, base);
qemu_fdt_add_subnode(vms->fdt, nodename);
qemu_fdt_setprop(vms->fdt, nodename, "compatible", compat, sizeof(compat));
qemu_fdt_setprop_sized_cells(vms->fdt, nodename, "reg", 2, base, 2, size);
qemu_fdt_setprop_cells(vms->fdt, nodename, "interrupts",
GIC_FDT_IRQ_TYPE_SPI, irq,
GIC_FDT_IRQ_FLAGS_LEVEL_HI);
qemu_fdt_setprop_cell(vms->fdt, nodename, "interrupt-parent", vms->gic_phandle);
g_free(nodename);
}
==== hw/misc/ab21q_axpu.c
static void ab21q_axpu_init(Object *obj)
{
Ab21qAxpuState *s = AB21Q_AXPU(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
int i;
memory_region_init_io(&s->iomem, OBJECT(s), &ab21q_axpu_ops, s,
TYPE_AB21Q_AXPU, 0x200000*64);
sysbus_init_mmio(sbd, &s->iomem);
sysbus_init_irq(sbd, &s->irq);
s->init = 0;
s->int_flag = 0;
s->status = 0;
s->id = CHIP_ID;
}
static void ab21q_axpu_realize(DeviceState *d, Error **errp)
{
Ab21qAxpuState *s = AB21Q_AXPU(d);
SysBusDevice *sbd = SYS_BUS_DEVICE(d);
if (qemu_irq_is_connected(s->irq)) {printf("axpu irq connected!\n");}
else { printf("axpu irq not connected!\n");}
}
static void ab21q_axpu_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = ab21q_axpu_realize;
}
static void ab21q_axpu_set_irq(Ab21qAxpuState *s, int irq)
{
s->status = irq;
qemu_set_irq(s->irq, 1);
}
static void ab21q_axpu_write(void *opaque, hwaddr offset, uint64_t value,
unsigned size)
{
Ab21qAxpuState *s = (Ab21qAxpuState *)opaque;
.... skip ...
switch (offset) {
case TRIGGER_RUN:
....
if (((uint64_t *)(s->ioctl_arg + *host_virt_offset_p))[0] == 0x1604) {
s->int_flag = 1;
ab21q_axpu_set_irq(s, INT_AXPU_RUN_FINISHED);
}
The machine and the device works. (actually the device is a shared library that qemu links to) except the interrupt doesn't work even though qemu does set_irq. I checked with qemu_irq_is_connected in realize function. The pl011 case says pl011 irq connected!, but in my device I see axpu irq not connected!. So it's not related to device driver but qemu model itself.
Could anyone find what is missing in the above code? Should I add something to acpi table(in ab21q-build-acpi.c)?
I tried adding in hw/arm/ab21q-build-acpi.c these lines. In function build_dsdt,
acpi_dsdt_add_axpu(scope, &memmap[AB21Q_AXPU],
(irqmap[AB21Q_AXPU] + ARM_SPI_BASE));
The acpi_dsdt_add_axpu function being
static void acpi_dsdt_add_axpu(Aml *scope, const MemMapEntry *uart_memmap,
uint32_t irq)
{
Aml *dev = aml_device("AXPU");
aml_append(dev, aml_name_decl("_HID", aml_string("AXPU0011")));
aml_append(dev, aml_name_decl("_UID", aml_int(0)));
Aml *crs = aml_resource_template();
aml_append(crs, aml_memory32_fixed(uart_memmap->base,
uart_memmap->size, AML_READ_WRITE));
aml_append(crs,
aml_interrupt(AML_CONSUMER, AML_LEVEL, AML_ACTIVE_HIGH,
AML_EXCLUSIVE, &irq, 1));
aml_append(dev, aml_name_decl("_CRS", crs));
aml_append(scope, dev);
}
Inside the virtual machine (ubuntu 20.04), I did acpidump and converted it to .dsl files. The dsdt.dsl contains this entry.
Device (AXPU)
{
Name (_HID, "AXPU0011") // _HID: Hardware ID
Name (_UID, Zero) // _UID: Unique ID
Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings
{
Memory32Fixed (ReadWrite,
0x09100000, // Address Base
0x00080000, // Address Length
)
Interrupt (ResourceConsumer, Level, ActiveHigh, Exclusive, ,, )
{
0x000000D0,
}
})
}
I'm not sure what I should fix in acpi table.
Any comment or advice will be deeply appreciated.
Thank you!
Chan Kim
So I added some prints. This is the result.
create_uart called!
pl011_init called!
pl011_realize called!
pl011 irq not connected!
now calling sysbus_connect_irq for pl011..
now passed sysbus_connect_irq for pl011..
pl011 irq connected!
create_ab21q_axpu_device called!
ab21q_axpu_init called!
ab21q_axpu_realize called!
axpu irq not connected!
now calling sysbus_connect_irq for ab21q_axpu..
now passed sysbus_connect_irq for ab21q_axpu..
ab21q_axpu irq connected!
So irq-connection-wise speaking, pl011 and ab21q_axpu is the same!
For pl011, the irq was not connected either before the sysbus_connect_irq,
but in the code above, I used qemu_irq_is_connected(s->irq)) by mistake and it gave me true (a false true). I fixed it to qemu_irq_is_connected(s->irq[0])) because it had 6 irq outputs and it returned false before the sysbus_connect_irq. After the sysbus_connect_irq, both ab21q_axpu and pl011's irq shown to have been connected.
And of course I used qemu_irq_is_connected(AB21Q_AXPU(dev)->irq) or
qemu_irq_is_connected(PL011(dev)->irq[0]) to check the irq connection inside the xxx_create functions.
ADD : I later found the request_irq functino returned -EINVAL. so interrupt was not registerred correctly in the driver.

Puzzling why variable fails to update, causing the dreaded HardFault?

I'm migrating to STM32, debugging STM32F103C8T6 with Keil MDK5, running at 72 MHz. Total RAM is less than 200 bytes only, C99 mode enabled. I have a function as shown below, which I do not understand why the variable new_data_Length fails to update most of the time, remains as ZERO, causing the HardFault exception caught by HardFault_Handler(). Being a newbie in STM32 and Keil MDK, maybe there's something I'm missing. Any ideas?
uint8_t size_A = 22; // globally declared
uint8_t data_Length = someValue; // globally declared
someFunction(dData, data_Length, size_A);
.
.
in other file...
void someFunction(uint8_t* rData, uint8_t dLength, uint8_t sizeA)
{
uint8_t new_data_Length = dLength + sizeA + 1;
unit8_t newData[new_data_Length]; // <<--malloc fails because new_Data_Length is 0
// unit8_t* newData;
// newData = malloc(new_data_Length); // <<--malloc fails here too...
// if (newData == 0)
// {
.
.
.
}

How do I get the current interrupt state (enabled, disabled or current level) on a MC9S12ZVM processor

I'm working on a project using a MC9S12ZVM family processor and need to be able to get, save and restore the current interrupt enabled state. This is needed to access variables from the main line code that may be modified by the interrupt handler that are larger than word in size and therefore not atomic.
pseudo code: (variable is 32bits and -= isn't atomic anyhow)
state_save = current_interrupt_state();
DisableInterrupt();
variable -= x;
RestoreInterrupts(state_save);
Edit: I found something that works, but has the issue of modifying the stack.
asm(PSH CCW);
asm(SEI);
Variable++;
asm(PUL CCW);
This is ok as long as I don't need to do anything other than a simple variable++, but I don't like exiting a block with the stack modified.
It seems you are referring to the global interrupt mask. If so, then this is one way to disable it and then restore it to previous state:
static const uint8_t CCR_I_MASK = 0x10;
static uint8_t ccr;
void disable_interrupts (void)
{
__asm PSHA;
__asm TPA; // transfer CCR to A
__asm STA ccr; // store CCR in RAM variable
__asm PULA;
__asm SEI;
}
void restore_interrupts (void)
{
if((ccr & CCR_I_MASK) == 0)
{
__asm CLI; // i was not set, clear it
}
else
{
; // i was set, do nothing
}
}
__asm is specific to the Codewarrior compiler, with or without "strict ANSI" option set.
Ok, I've found an answer to my problem, with thanks to those who commented.
static volatile uint16_t v = 0u;
void testfunction(void);
void testfunction(void)
{
static uint16_t L_CCR;
asm( PSH D2 );
asm( TFR CCW, D2);
asm( ST D2, L_CCR );
asm( PUL D2 );
asm( SEI );
v++;
asm( PSH D2 );
asm( LD D2, L_CCR );
asm( TFR D2, CCW);
asm( PUL D2 );
}

Storing blocks on C arrays and running them later

Ok, I can store blocks on NSArrays using something like this:
NSArray *myArray = #[[myBlock1 copy], [myBlock2 copy]];
and run that code later by doing, for example:
myBlockType runBlock = myArray[0];
runBlock(); // run baby, run
What about the C equivalent to this? Is that possible?
This works:
typedef void (^MyBlockType)(void);
MyBlockType blocks[100];
for(int i = 0; i < 100; i++) {
MyBlockType block= ^{printf("Run!");};
blocks[i] = block;
}
MyBlockType thisBlock = blocks[0];
thisBlock();
If the Operating System allows you to do that, you can assign the address of an array to a function pointer and call the code in the array using the name of that pointer.
typedef void (*fx_t)(void);
int main(void) {
fx_t udf;
unsigned char code[1000] = "<<machine code instructions>>";
udf = code; /* UB: assignment of object pointer to non-object pointer */
/* illegal assignment according to the Standard */
udf();
return 0;
}
As per requests in comments, here is a complete example that works on my computer (FreeBSD 9.2 on amd64)
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
typedef int (*fx_t)(void);
int main(void) {
int a = -1;
fx_t udf;
unsigned char code[1000] = "\xb8\x2a\x00\x00\x00" // mov $0x2a,%eax
"\xc3"; // retq
void *codeaddr = code;
size_t length = 4096; /* size of a page */
if (mprotect(codeaddr, length, PROT_READ | PROT_WRITE | PROT_EXEC)) {
perror("mprotect");
exit(EXIT_FAILURE);
}
udf = (void*)code;
a = udf(); // call the code in the `code` string
printf("a is now %d.\n", a); // expect 42, changed by the code in `code`
code[1] = 24; // w00t! self changing code
a = udf();
printf("a is now %d.\n", a); // expect 24
return 0;
}
To get the bytes for my code, I compiled a very simple int fortytwo(void) { return 42; } to object file and then objdump -d it and tried the bytes that seemed relevant (I don't know assembly language)
0000000000000000 <fortytwo>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: b8 2a 00 00 00 mov $0x2a,%eax
9: c9 leaveq
a: c3 retq
b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)

WinXP: sendto() failed with 10014 (WSAEFAULT) if destination address is const-qualified, IPv4-specific

It seems, I found a bug in Windows...
Ok, let not be such pathetic one. I'm trying to do generic sendto() operation for UDP and occasionaly found that WinXP (32 bit, SP3, checked on real and virtual machines) returns "-1" bytes sent with WSAGetLastError() as error 10014 (aka WSAEFAULT). Occurs only on IPv4 addresses (same code with IPv6 destination works perfectly). Major condition to reproduce is usage of "const struct sockaddr_in" declared at global scope. Here is the plain C code for VS2010 (also I've tried with Eclipse+MinGW, got same results):
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <winsock2.h>
#include <stdint.h>
#pragma comment(lib, "Ws2_32.lib")
#define INADDR_UPNP_V4 0xEFFFFFFA
#define htons(x) ((((uint16_t)(x) & 0xFF00) >> 8) | (((uint16_t)(x) & 0x00FF) << 8))
#define htonl(x) ((((uint32_t)(x) & 0xFF000000) >> 24) | (((uint32_t)(x) & 0x00FF0000) >> 8) | (((uint32_t)(x) & 0x0000FF00) << 8) | (((uint32_t)(x) & 0x000000FF) << 24))
// Magic "const" qualifier, causes run-time error
const struct sockaddr_in addr_global = {
AF_INET,
htons(1900),
{
htonl(INADDR_UPNP_V4)
},
{0},
};
int main(int argc, char** argv)
{
#define CR_LF "\r\n"
// these two lines to un-buffer console window output at Win32, see URL below for details
// http://wiki.eclipse.org/CDT/User/FAQ#Eclipse_console_does_not_show_output_on_Windows
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
printf("Started\n");
const struct sockaddr_in addr_local = {
AF_INET,
htons(1900),
{
htonl(INADDR_UPNP_V4)
},
{0},
};
const char *MSEARCH_REQUEST_V4 = "M-SEARCH * HTTP/1.1"CR_LF
"Host:239.255.255.250:1900"CR_LF
"MAN:\"ssdp:discover\""CR_LF
"ST:ssdp:all"CR_LF
"MX:3"CR_LF
CR_LF;
const int MSEARCH_LEN = strlen(MSEARCH_REQUEST_V4);
WSADATA wsaData;
int res = WSAStartup(MAKEWORD(2, 2), &wsaData);
int af = AF_INET;
int sock_id = socket(af, SOCK_DGRAM, IPPROTO_UDP);
if (-1 == sock_id) {
printf("%s: socket() failed with error %i/%i\n", __FUNCTION__,
errno, WSAGetLastError());
return 1;
}
int data_sent = 0;
printf("1st sendto()\n");
data_sent = sendto(sock_id, MSEARCH_REQUEST_V4,
MSEARCH_LEN, 0,
(const struct sockaddr * const)&addr_local,
sizeof(struct sockaddr_in));
if (data_sent < 0) {
printf("%s: sendto(local) failed with error %i/%i\n", __FUNCTION__,
errno, WSAGetLastError());
}
printf("2nd sendto(), will fail on WinXP SP3 (32 bit)\n");
data_sent = sendto(sock_id, MSEARCH_REQUEST_V4,
MSEARCH_LEN, 0,
(const struct sockaddr * const)&addr_global,
sizeof(struct sockaddr_in));
if (data_sent < 0) {
printf("%s: sendto(global) failed with error %i/%i\n", __FUNCTION__,
errno, WSAGetLastError());
}
closesocket(sock_id);
res = WSACleanup();
printf("Finished\n");
return 0;
}
So, if you run this code at Win7, for example, it will be absolutely OK. But WinXP fails on addr_global usage if it equipped with "const" qualifier (see "Magic" comment above). Also, "Output" window says:
First-chance exception at 0x71a912f4 in SendtoBugXP.exe: 0xC0000005:
Access violation writing location 0x00415744.
With help of "Autos" window, it's easy to figure out that 0x00415744 location is address of addr_global.sin_zero field. It seems, WinXP to write zeros there and violates memory access flags. Or this is just silly me, trying to go wrong door?
Appreciate your comments a lot. Thanks in advance.
Yeah you found a bug. sendto() has that argument declared const, but wrote to it anyway. Good luck getting it fixed though. Hint: it might be in your antivirus or firewall.
To summarize results from other forums: yes, this is Windows bug, existing up to WinXP in "desktop" and Win2003 at "server" segments.
WinSock code does attempt to force-fill "sin_zero" field with zeros. And "const" global scope causes memory access violation. Stack trace is about like that:
Thread [1] 0 (Suspended : Signal : SIGSEGV:Segmentation fault)
WSHTCPIP!WSHGetSockaddrType() at 0x71a912f4
0x71a52f9f
WSAConnect() at 0x71ab2fd7
main() at tests_main.c:77 0x401584
The same behavior observed on bind() by other people.