Нарушение прав доступа при вызове WinAPI InternetOpenA() из сборки

Я пытаюсь установить HTTP-соединение из сборки с помощью Windows API. Код следующий:

%include "const.asm"
%define u(x) __utf16__(x) 
%define w(x) __utf32__(x) 

; Externs
extern InternetOpenA

extern GetLastError

section .data
    curProc   db 0
    hInternet dq 0

    lpszAgent db "Mozilla/5.0", 0

section .text
global Start
Start:
    sub rsp, 8

    sub rsp, 32 + 1 * 8
    lea rcx, [REL lpszAgent]                            ; lpszAgentW (WIDE STRING)
    mov rdx, 4        ; dwAccessType (DWORD)
    mov r8,  NULL                                       ; lpszProxy (DWORD)
    mov r9,  NULL                                       ; pszProxyBypassW (DWORD)
    mov dword [rsp + 4 * 8], NULL                       ; dwFlags (DWORD)
    call InternetOpenA

    mov [REL hInternet], rax 
    add rsp, 32 + 1 * 8                                 ; hInternet parameter
    cmp rax, NULL
    je error_handler

    ; Stop
    jmp exit


error_handler:
    sub rsp, 32
    call GetLastError                                    ; Load more error info
    add rsp, 32 + 8
    ret


exit:                                                    ; Graceful shutdowns
    xor rax, rax
    add rsp, 8
    ret

Соответствующий Makefile:

INCLUDES := kernel32.lib user32.lib wininet.lib

.PHONY: all compile link clean

all: build

compile:
    @echo "Compiling assembly"
    nasm -f win64 main.asm -o main.o

link: compile
    @echo "Linking assembly"
    polink /ENTRY:Start /SUBSYSTEM:CONSOLE /LIBPATH:C:\lib64 $(INCLUDES) main.o

build: link

clean:
    @echo "Cleaning up"
    del main.o

Когда я запускаю его, возвращается код ошибки -1073741819 или 0xC0000005, как указал @selbie, или также известный как нарушение прав доступа. Я получил segfault как для wininet, так и для winhttp при создании дескриптора. Я прилагаю короткий код C, показывающий, что именно я хотел бы сделать: (Сборка не должна иметь отпечатков. Она находится только в исходном коде C для целей отладки)

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

int main() {
    const char *agent = "Mozilla/5.0";
    HINTERNET handler =  InternetOpenA(agent, INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY, 0, 0, 0);
    if (handler) {
        return 0;
    }
    printf("%d", GetLastError());
    return 1;
}

Для ясности компилирую для 64-битной Windows. Я также статически привязываюсь, как показано на Makefile. Ваше здоровье

Обновлено: Код для некоторой отладки:

[BITS 64]

%include "const.asm"
%define u(x) __utf16__(x) 
%define w(x) __utf32__(x) 

; Externs
extern InternetOpenA

extern GetLastError

extern GetStdHandle
extern WriteFile

section .data
    curProc   db 0
    hInternet dq 0

    lpszAgent db "Mozilla/5.0", 0

    NtlpBuffer:        db    'Hello, World!', 0
    NtnNBytesToWrite:  dq    0eh


section .text
global Start
Start:

    sub rsp, 8
    mov rax, rsp    
    mov rbx, 8 
    xor rdx, rdx
    div rbx
    cmp rdx, 0
    je main

    sub rsp, 32 + 1 * 8
    and rsp, -16
    lea rcx, [REL lpszAgent]                            ; lpszAgentW (WIDE STRING)
    mov rdx, 4                                          ; dwAccessType (DWORD)
    mov r8,  NULL                                       ; lpszProxy (DWORD)
    mov r9,  NULL                                       ; pszProxyBypassW (DWORD)
    mov dword [rsp + 4 * 8], NULL                       ; dwFlags (DWORD)
    
    call InternetOpenA

    mov [REL hInternet], rax 
    add rsp, 32 + 1 * 8                                 ; hInternet parameter
    cmp rax, NULL
    je error_handler

    ; Stop
    jmp exit


error_handler:
    sub rsp, 32
    call GetLastError                                    ; Load more error info
    add rsp, 32 + 8
    ret


exit:                                                    ; Graceful shutdowns
    xor rax, rax
    add rsp, 8
    ret

main:
    sub     rsp, 32
    mov     ecx, -11
    call    GetStdHandle

    mov     rcx, rax
    lea rdx, [rel NtlpBuffer]
    mov     r8, [REL NtnNBytesToWrite]
    mov     r9, NULL
    mov     qword [rsp + 32], 00h
    call    WriteFile
    add     rsp, 32

ExitProgram:
    xor     eax, eax
    add rsp, 8
    ret
-1073741819 это 0xC0000005
selbie 01.08.2024 08:35
add rsp, 32 + 1 * 8 — вы знаете, что функции stdcall, экспортируемые Windows API, восстанавливают указатель стека от имени вызывающего объекта. Вы уверены, что ваш звонок для восстановления RSP необходим?
selbie 01.08.2024 08:36

@selbie RE первый комментарий, спасибо за разъяснения, отредактировано. РЭ 2: Я не знаю об этом факте. Я использовал add rsp, 32 + n * 8 в предыдущих проектах, и все работало нормально. При этом ошибка возникает именно тогда, когда я call InternetOpenA. Также спасибо за быстрый ответ.

Wrench56 01.08.2024 08:39

Это 32-битный или 64-битный код?

selbie 01.08.2024 08:45

@селби 64-бит. Что касается аргумента о восстановлении указателя стека: Learn.microsoft.com/en-us/cpp/build/…. И пролог, и эпилог вручную изменяют указатель стека.

Wrench56 01.08.2024 08:47

@selbie: Windows x64 — это caller-pops, поэтому вы можете выделить теневое пространство (плюс пространство для передачи аргументов, если необходимо) один раз для функции, которая выполняет несколько вызовов, поэтому RSP никогда не выходит за пределы пролога/эпилога (кроме самого вызова) , что упрощает очистку стека метаданных.

Peter Cordes 01.08.2024 09:01

Если Start вызывается как обычная функция, ваш пролог смещает стек - общее изменение в RSP от двух инструкций sub будет четным кратным 8. Но поскольку call отправил адрес возврата, вам нужно нечетное кратное 8, чтобы получить из RSP % 16 == 8 при входе в RSP % 16 == 0 перед звонком. Код в библиотечной функции, которая использует movaps в памяти стека, выдаст ошибку, если вы сделаете это неправильно, как в Linux, который имеет такое же требование к выравниванию (glibc scanf Ошибки сегментации при вызове из функции, которая не выравнивает RSP)

Peter Cordes 01.08.2024 09:05

@Peter Cordes Спасибо за быстрый ответ! Я прочитал ваш ответ и снова попытался sub rsp, 8 получить нечетное число, кратное 8. Это все равно привело к сегфолту. Возможно, я неправильно истолковываю ваше решение по другому вопросу.

Wrench56 01.08.2024 09:16

Хорошо, возможно, это не ваша ошибка. Используйте отладчик, чтобы выяснить, какая инструкция дает сбой.

Peter Cordes 01.08.2024 09:31

Изменения указателя стека в вашей функции кажутся слишком сложными. Не нужны разные настройки в разных ветках, просто выделите достаточно в прологе и уберите в обоих эпилогах. (add rsp, 32 + 8*1)

Peter Cordes 01.08.2024 09:34

@Питер Кордес, ты уверен в этом? Без него я не могу вызвать, например. печать. Я проверил это, удалив эпилог после печати, но это дает мне ошибку. Когда я возвращаю эпилог, он работает.

Wrench56 01.08.2024 09:55

@Peter Cordes Я добавил в вопрос немного отладочного кода. Этот код выполняется без проблем, печатает Hello World! и возвращает ноль. Это также проверяет rsp, глядя на остаток. Я изо всех сил пытаюсь настроить отладчик: gdb, похоже, не распознает заголовки nasm и polink/link отладки.

Wrench56 01.08.2024 10:00

В GDB используйте layout asm для отладки на основе дизассемблирования машинного кода. Вам не нужны метаданные отладки. (А затем layout next чтобы увидеть разделенное представление с дизассемблированием и регистрами. Советы по ассемблеру GDB смотрите внизу stackoverflow.com/tags/x86/info)

Peter Cordes 01.08.2024 10:22
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
13
57
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

После нескольких часов попыток и неудач мой коллега-программист @sbdswr из Discord помог мне отладить его — как ни странно, без всякого отладчика. Проблема действительно заключалась в моей обработке указателя стека, как уже указывал @Peter Cordes. Вот полная версия исправленного кода. Спасибо за помощь!

BITS 64

%include "const.asm"
%define u(x) __utf16__(x) 
%define w(x) __utf32__(x) 

; Externs
extern InternetOpenA

extern GetLastError

extern GetStdHandle
extern WriteFile

extern ExitProcess

section .data
    curProc   db 0
    hInternet dq 0

    lpszAgent db "Mozilla/5.0", 0

    NtlpBuffer:        db    'Hello, World!', 0
    NtnNBytesToWrite:  dq    0eh


section .text
global Start
Start:
    push    rbp
    mov     rbp, rsp
    sub     rsp, 48
    
    lea     rcx, [REL lpszAgent]                            ; lpszAgentW (WIDE STRING)
    mov     rdx, 4                                          ; dwAccessType (DWORD)
    mov     r8,  NULL                                       ; lpszProxy (DWORD)
    mov     r9,  NULL                                       ; pszProxyBypassW (DWORD)
    mov     dword [rsp + 4 * 8], NULL                       ; dwFlags (DWORD)
    call    InternetOpenA
    
    mov     [REL hInternet], rax                            ; hInternet parameter
    cmp     rax, NULL
    je      error_handler

    jmp     exit


error_handler:
    call    GetLastError                                    ; Load more error info
    jmp     exit


exit:
    call    print
    xor     rcx, rcx
    call    ExitProcess
    
print:
    sub     rsp, 40
    mov     ecx, -11
    call    GetStdHandle

    mov     rcx, rax
    lea     rdx, [rel NtlpBuffer]
    mov     r8, [REL NtnNBytesToWrite]
    mov     r9, NULL
    mov     qword [rsp + 32], 00h
    call    WriteFile
    add     rsp, 40
    ret

Пожалуйста, примите ваш ответ как ответ, если это решит проблему.

Ax1le 05.08.2024 04:39

Другие вопросы по теме

Похожие вопросы

Проблемы с чтением и печатью файлов в сборке с использованием функций библиотеки C в 64-разрядной версии Windows
Почему компоновщик GCC помещает 8 байтов пространства между этими двумя разделами?
NASM x64: часть с плавающей запятой неправильно напечатана как 0,000000
Связь порядка байтов с преобразованием размера сборки в C
Невозможно зарегистрировать несколько выходных данных с помощью WriteConsoleA в программе сборки Windows 10 (64-разрядной версии)
Использование printf из c в Nasm приводит к добавлению случайного оператора конечной строки в строку
Можно ли определить функцию C++, в которой один параметр передается через регистр EAX?
Почему операнд 1 в байте modr/m меняется в зависимости от режима декодирования
Производительность SIMD в два раза медленнее без дополнительной копии
Почему загрузка EAX из массива байтов не соответствует ожидаемому мной литералу?