Created
September 1, 2023 21:06
-
-
Save rrika/685a8cd016f14a4c337f6de2a7f7c3e9 to your computer and use it in GitHub Desktop.
reading the wine registry
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// compile with gcc -fshort-wchar | |
#define _GNU_SOURCE // for pipe2 | |
#include <errno.h> | |
#include <fcntl.h> | |
#include <stdint.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <sys/socket.h> | |
#include <sys/stat.h> | |
#include <sys/syscall.h> | |
#include <sys/uio.h> | |
#include <sys/un.h> | |
#include <unistd.h> | |
#define SERVER_PROTOCOL_VERSION 779 | |
#define SERVER_START_REQ(version, type) \ | |
do { \ | |
struct __server_request_info __req; \ | |
struct type##_request * const req = &__req.u.req.type##_request; \ | |
const struct type##_reply * const reply = &__req.u.reply.type##_reply; \ | |
memset( &__req.u.req, 0, sizeof(__req.u.req) ); \ | |
__req.u.req.request_header.req = REQ_##version##_##type; \ | |
__req.data_count = 0; \ | |
(void)reply; \ | |
do | |
#define SERVER_END_REQ \ | |
while(0); \ | |
} while(0) | |
// from server/server_protocol.h | |
// generated from server/protocol.def | |
// by tools/make_requests | |
typedef unsigned int obj_handle_t; | |
typedef unsigned int process_id_t; | |
typedef unsigned int thread_id_t; | |
typedef unsigned int data_size_t; | |
typedef int64_t timeout_t; | |
struct object_attributes | |
{ | |
obj_handle_t rootdir; | |
unsigned int attributes; | |
data_size_t sd_len; | |
data_size_t name_len; | |
}; | |
struct request_header { | |
int req; | |
data_size_t request_size; | |
data_size_t reply_size; | |
}; | |
struct reply_header { | |
unsigned int error; | |
data_size_t reply_size; | |
}; | |
#define REQ_779_init_first_thread 5 | |
#define REQ_779_close_handle 21 | |
#define REQ_779_create_key 83 | |
#define REQ_779_open_key 84 | |
#define REQ_779_get_key_value 89 | |
struct init_first_thread_request { | |
struct request_header __header; | |
int unix_pid; | |
int unix_tid; | |
int debug_level; | |
int reply_fd; | |
int wait_fd; | |
}; | |
struct init_first_thread_reply { | |
struct reply_header __header; | |
process_id_t pid; | |
thread_id_t tid; | |
timeout_t server_start; | |
unsigned int session_id; | |
data_size_t info_size; | |
/* VARARG(machines,ushorts); */ | |
}; | |
struct close_handle_request { | |
struct request_header __header; | |
obj_handle_t handle; | |
}; | |
struct close_handle_reply { | |
struct reply_header __header; | |
}; | |
struct create_key_request { | |
struct request_header __header; | |
unsigned int access; | |
unsigned int options; | |
/* VARARG(objattr,object_attributes); */ | |
/* VARARG(class,unicode_str); */ | |
char __pad_20[4]; | |
}; | |
struct create_key_reply { | |
struct reply_header __header; | |
obj_handle_t hkey; | |
char __pad_12[4]; | |
}; | |
struct open_key_request { | |
struct request_header __header; | |
obj_handle_t parent; | |
unsigned int access; | |
unsigned int attributes; | |
/* VARARG(name,unicode_str); */ | |
}; | |
struct open_key_reply { | |
struct reply_header __header; | |
obj_handle_t hkey; | |
char __pad_12[4]; | |
}; | |
struct get_key_value_request { | |
struct request_header __header; | |
obj_handle_t hkey; | |
/* VARARG(name,unicode_str); */ | |
}; | |
struct get_key_value_reply { | |
struct reply_header __header; | |
int type; | |
data_size_t total; | |
/* VARARG(data,bytes); */ | |
}; | |
// end protocol | |
struct __server_iovec { | |
const void *ptr; | |
data_size_t size; | |
}; | |
#define __SERVER_MAX_DATA 5 | |
struct request_max_size{ | |
int pad[16]; | |
}; | |
union generic_request { | |
struct request_max_size max_size; | |
struct request_header request_header; | |
struct init_first_thread_request init_first_thread_request; | |
struct close_handle_request close_handle_request; | |
struct create_key_request create_key_request; | |
struct open_key_request open_key_request; | |
struct get_key_value_request get_key_value_request; | |
}; | |
union generic_reply { | |
struct request_max_size max_size; | |
struct reply_header reply_header; | |
struct init_first_thread_reply init_first_thread_reply; | |
struct close_handle_reply close_handle_reply; | |
struct create_key_reply create_key_reply; | |
struct open_key_reply open_key_reply; | |
struct get_key_value_reply get_key_value_reply; | |
}; | |
struct __server_request_info { | |
union { | |
union generic_request req; /* request structure */ | |
union generic_reply reply; /* reply structure */ | |
} u; | |
unsigned int data_count; /* count of request data pointers */ | |
void *reply_data; /* reply data pointer */ | |
struct __server_iovec data[__SERVER_MAX_DATA]; /* request variable size data */ | |
}; | |
int fd_socket; | |
int request_fd; | |
int reply_fd; | |
int wait_fd[2]; | |
int server_pid; | |
int CurrentThreadId = 0; | |
static unsigned int send_request( const struct __server_request_info *req ) | |
{ | |
printf("send_request request_size = %d\n", req->u.req.request_header.request_size); | |
unsigned int i; | |
int ret; | |
if (!req->u.req.request_header.request_size) | |
{ | |
if ((ret = write(request_fd, &req->u.req, | |
sizeof(req->u.req) )) == sizeof(req->u.req)) return 0 /*STATUS_SUCCESS*/; | |
} | |
else | |
{ | |
struct iovec vec[__SERVER_MAX_DATA+1]; | |
vec[0].iov_base = (void *)&req->u.req; | |
vec[0].iov_len = sizeof(req->u.req); | |
for (i = 0; i < req->data_count; i++) | |
{ | |
vec[i+1].iov_base = (void *)req->data[i].ptr; | |
vec[i+1].iov_len = req->data[i].size; | |
} | |
if ((ret = writev(request_fd, vec, i+1 )) == | |
req->u.req.request_header.request_size + sizeof(req->u.req)) return 0 /*STATUS_SUCCESS*/; | |
} | |
if (ret >= 0) { perror("partial write"); exit(1); } | |
if (errno == EPIPE) exit(1); | |
if (errno == EFAULT) return 0xC0000005 /*STATUS_ACCESS_VIOLATION*/; | |
perror("write"); exit(1); | |
} | |
static void read_reply_data( void *buffer, size_t size ) | |
{ | |
int ret; | |
for (;;) | |
{ | |
if ((ret = read(reply_fd, buffer, size )) > 0) | |
{ | |
if (!(size -= ret)) return; | |
buffer = (char *)buffer + ret; | |
continue; | |
} | |
if (!ret) break; | |
if (errno == EINTR) continue; | |
if (errno == EPIPE) break; | |
perror("read"); | |
exit(1); | |
} | |
/* the server closed the connection; time to die... */ | |
exit(1); | |
} | |
/* get the size of the variable part of the returned reply */ | |
static inline data_size_t wine_server_reply_size( const void *reply ) | |
{ | |
return ((const struct reply_header *)reply)->reply_size; | |
} | |
/* add some data to be sent along with the request */ | |
static inline void wine_server_add_data( void *req_ptr, const void *ptr, data_size_t size ) | |
{ | |
struct __server_request_info * const req = req_ptr; | |
if (size) | |
{ | |
req->data[req->data_count].ptr = ptr; | |
req->data[req->data_count++].size = size; | |
req->u.req.request_header.request_size += size; | |
} | |
} | |
/* set the pointer and max size for the reply var data */ | |
static inline void wine_server_set_reply( void *req_ptr, void *ptr, data_size_t max_size ) | |
{ | |
struct __server_request_info * const req = req_ptr; | |
req->reply_data = ptr; | |
req->u.req.request_header.reply_size = max_size; | |
} | |
static inline unsigned int wait_reply( struct __server_request_info *req ) | |
{ | |
printf("wait_reply\n"); | |
read_reply_data( &req->u.reply, sizeof(req->u.reply) ); | |
if (req->u.reply.reply_header.reply_size) | |
read_reply_data( req->reply_data, req->u.reply.reply_header.reply_size ); | |
return req->u.reply.reply_header.error; | |
} | |
unsigned int wine_server_call( void *req_ptr ) { | |
struct __server_request_info * const req = req_ptr; | |
unsigned int ret; | |
if ((ret = send_request( req ))) return ret; | |
return wait_reply( req ); | |
} | |
struct send_fd | |
{ | |
thread_id_t tid; | |
int fd; | |
}; | |
void wine_server_send_fd( int fd ) { | |
struct send_fd data; | |
struct msghdr msghdr; | |
struct iovec vec; | |
int ret; | |
#ifdef HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS | |
msghdr.msg_accrights = (void *)&fd; | |
msghdr.msg_accrightslen = sizeof(fd); | |
#else /* HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS */ | |
char cmsg_buffer[256]; | |
struct cmsghdr *cmsg; | |
msghdr.msg_control = cmsg_buffer; | |
msghdr.msg_controllen = sizeof(cmsg_buffer); | |
msghdr.msg_flags = 0; | |
cmsg = CMSG_FIRSTHDR( &msghdr ); | |
cmsg->cmsg_len = CMSG_LEN( sizeof(fd) ); | |
cmsg->cmsg_level = SOL_SOCKET; | |
cmsg->cmsg_type = SCM_RIGHTS; | |
*(int *)CMSG_DATA(cmsg) = fd; | |
msghdr.msg_controllen = cmsg->cmsg_len; | |
#endif /* HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS */ | |
msghdr.msg_name = NULL; | |
msghdr.msg_namelen = 0; | |
msghdr.msg_iov = &vec; | |
msghdr.msg_iovlen = 1; | |
vec.iov_base = (void *)&data; | |
vec.iov_len = sizeof(data); | |
data.tid = CurrentThreadId; | |
data.fd = fd; | |
for (;;) | |
{ | |
if ((ret = sendmsg( fd_socket, &msghdr, 0 )) == sizeof(data)) return; | |
if (ret >= 0) { perror( "partial write" ); exit(1); } | |
if (errno == EINTR) continue; | |
if (errno == EPIPE) exit(1); | |
perror( "sendmsg" ); exit(1); | |
} | |
} | |
static int receive_fd( obj_handle_t *handle ) | |
{ | |
struct iovec vec; | |
struct msghdr msghdr; | |
int ret, fd = -1; | |
#ifdef HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS | |
msghdr.msg_accrights = (void *)&fd; | |
msghdr.msg_accrightslen = sizeof(fd); | |
#else /* HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS */ | |
char cmsg_buffer[256]; | |
msghdr.msg_control = cmsg_buffer; | |
msghdr.msg_controllen = sizeof(cmsg_buffer); | |
msghdr.msg_flags = 0; | |
#endif /* HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS */ | |
msghdr.msg_name = NULL; | |
msghdr.msg_namelen = 0; | |
msghdr.msg_iov = &vec; | |
msghdr.msg_iovlen = 1; | |
vec.iov_base = (void *)handle; | |
vec.iov_len = sizeof(*handle); | |
for (;;) | |
{ | |
if ((ret = recvmsg( fd_socket, &msghdr, MSG_CMSG_CLOEXEC )) > 0) | |
{ | |
#ifndef HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS | |
struct cmsghdr *cmsg; | |
for (cmsg = CMSG_FIRSTHDR( &msghdr ); cmsg; cmsg = CMSG_NXTHDR( &msghdr, cmsg )) | |
{ | |
if (cmsg->cmsg_level != SOL_SOCKET) continue; | |
if (cmsg->cmsg_type == SCM_RIGHTS) fd = *(int *)CMSG_DATA(cmsg); | |
#ifdef SCM_CREDENTIALS | |
else if (cmsg->cmsg_type == SCM_CREDENTIALS) | |
{ | |
struct ucred *ucred = (struct ucred *)CMSG_DATA(cmsg); | |
server_pid = ucred->pid; | |
} | |
#endif | |
} | |
#endif /* HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS */ | |
if (fd != -1) fcntl( fd, F_SETFD, FD_CLOEXEC ); /* in case MSG_CMSG_CLOEXEC is not supported */ | |
return fd; | |
} | |
if (!ret) break; | |
if (errno == EINTR) continue; | |
if (errno == EPIPE) break; | |
perror("recvmsg"); | |
exit(1); | |
} | |
exit(1); | |
} | |
static int init_thread_pipe(void){ | |
int reply_pipe[2]; | |
if (pipe2( reply_pipe, O_CLOEXEC ) == -1) { perror( "pipe" ); exit(1); } | |
if (pipe2( wait_fd, O_CLOEXEC ) == -1) { perror( "pipe" ); exit(1); } | |
printf("wine_server_send_fd\n"); | |
wine_server_send_fd( reply_pipe[1] ); | |
printf("wine_server_send_fd\n"); | |
wine_server_send_fd( wait_fd[1] ); | |
printf("done\n"); | |
reply_fd = reply_pipe[0]; | |
return reply_pipe[1]; | |
} | |
// ------- ------- ------- ------- ------- ------- ------- -------- -------- // | |
typedef void *HANDLE, *HKEY; | |
// using -fshort-wchar means that functions like wcslen won't work anymore | |
size_t strlen16(wchar_t const *x) { | |
size_t len = 0; | |
while (*x++) len++; | |
return len; | |
} | |
void NtClose(HANDLE handle) { | |
unsigned ret; | |
SERVER_START_REQ(779, close_handle) | |
{ | |
req->handle = (unsigned)(uintptr_t)handle; | |
ret = wine_server_call( req ); | |
} | |
SERVER_END_REQ; | |
} | |
HKEY NtCreateKey_ish(wchar_t const *name, uint32_t access, uint32_t options) { | |
size_t name_len = 2 * strlen16(name); | |
data_size_t len = sizeof(struct object_attributes) + ((name_len+3)&~3); | |
struct object_attributes *objattr = alloca(len); | |
objattr->rootdir = 0; | |
objattr->attributes = 0xC0 /*OBJ_OPENIF | OBJ_CASE_INSENSITIVE*/; | |
objattr->sd_len = 0; // no security descriptor | |
objattr->name_len = name_len; | |
char *beyond = (char*)(objattr+1); | |
memcpy(beyond, name, objattr->name_len); | |
unsigned ret; | |
HKEY hkey; | |
SERVER_START_REQ(779, create_key) | |
{ | |
req->access = access; | |
req->options = options; | |
wine_server_add_data( req, objattr, len ); | |
// no class | |
ret = wine_server_call( req ); | |
hkey = (void*)(uintptr_t)reply->hkey; | |
} | |
SERVER_END_REQ; | |
printf("NtCreateKey hkey=%lx\n", (unsigned long)(uintptr_t)hkey); | |
return hkey; | |
} | |
HKEY NtOpenKeyEx_ish(HANDLE rootDirectory, wchar_t const *name, uint32_t access) { | |
size_t name_len = 2 * strlen16(name); | |
unsigned ret; | |
HKEY hkey; | |
SERVER_START_REQ( 779, open_key ) | |
{ | |
req->parent = (uint32_t)(uintptr_t)rootDirectory; | |
req->access = access; | |
req->attributes = 0x40; /*OBJ_CASE_INSENSITIVE*/ | |
wine_server_add_data( req, name, name_len ); | |
ret = wine_server_call( req ); | |
hkey = (void*)(uintptr_t)reply->hkey; | |
} | |
SERVER_END_REQ; | |
printf("NtOpenKey hkey=%lx\n", (unsigned long)(uintptr_t)hkey); | |
return hkey; | |
} | |
typedef struct _KEY_VALUE_PARTIAL_INFORMATION { | |
unsigned long TitleIndex; | |
unsigned long Type; | |
unsigned long DataLength; | |
} KEY_VALUE_PARTIAL_INFORMATION; | |
long NtQueryValueKey_Partial( | |
HANDLE handle, const wchar_t *name, KEY_VALUE_PARTIAL_INFORMATION *info, uint32_t length, uint32_t *result_len) | |
{ | |
size_t name_len = 2 * strlen16(name); | |
unsigned int ret; | |
unsigned char *data_ptr; | |
unsigned int fixed_size; | |
fixed_size = sizeof(KEY_VALUE_PARTIAL_INFORMATION); | |
data_ptr = (unsigned char*)(info+1); | |
SERVER_START_REQ(779, get_key_value) | |
{ | |
req->hkey = (unsigned)(uintptr_t)handle; | |
wine_server_add_data( req, name, name_len ); | |
if (length > fixed_size && data_ptr) wine_server_set_reply( req, data_ptr, length - fixed_size ); | |
if (!(ret = wine_server_call( req ))) | |
{ | |
*result_len = fixed_size + reply->total; | |
if (length < sizeof(KEY_VALUE_PARTIAL_INFORMATION)) ret = 0xC0000023 /*STATUS_BUFFER_TOO_SMALL*/; | |
else if (length < *result_len) ret = 0x80000005 /*STATUS_BUFFER_OVERFLOW*/; | |
} | |
} | |
SERVER_END_REQ; | |
return 0; | |
} | |
void *HKEY_LOCAL_MACHINE = (void*)L"\\Registry\\Machine"; // need -fshort-wchar to make this utf-16 | |
HKEY HKEY_LOCAL_MACHINE_cached = 0; | |
HKEY getSpecialKey(HKEY hkey) { | |
if (hkey == HKEY_LOCAL_MACHINE) { | |
if (HKEY_LOCAL_MACHINE_cached == 0) | |
HKEY_LOCAL_MACHINE_cached = NtCreateKey_ish( | |
(wchar_t*)hkey, 0x02000000 /* MAXIMUM_ALLOWED */, 0); | |
hkey = HKEY_LOCAL_MACHINE_cached; | |
} | |
return hkey; | |
} | |
void RegCloseKey(HKEY hKey) { return NtClose(hKey); } | |
long RegOpenKeyExW_ish(HKEY hkey, const wchar_t *name, uint32_t access, HKEY *retkey) { | |
hkey = getSpecialKey(hkey); | |
*retkey = NtOpenKeyEx_ish(hkey, name, access); | |
return *retkey == 0; | |
} | |
long RegQueryValueExW_ish(HKEY hkey, const wchar_t *name, uint32_t *type, void *data, uint32_t *size) { | |
hkey = getSpecialKey(hkey); | |
KEY_VALUE_PARTIAL_INFORMATION *info = alloca(sizeof(KEY_VALUE_PARTIAL_INFORMATION) + *size); | |
long status = NtQueryValueKey_Partial(hkey, name, info, sizeof(KEY_VALUE_PARTIAL_INFORMATION) + *size, size); | |
if (status) return status; | |
size_t limit = info->DataLength < *size ? info->DataLength : *size; | |
printf("got %d, taking %d\n", info->DataLength, limit); | |
memcpy(data, info+1, limit); | |
if (type) *type = info->Type; | |
*size = info->DataLength; | |
return 0; | |
} | |
long RegGetValueW_ish( | |
HKEY hKey, const wchar_t *pszSubKey, const wchar_t *pszValue, | |
uint32_t *pdwType, void *pvData, uint32_t *pcbData) | |
{ | |
if (pszSubKey) { | |
long ret = RegOpenKeyExW_ish(hKey, pszSubKey, /*KEY_QUERY_VALUE|KEY_WOW64_64KEY*/ 0x101, &hKey); | |
if (ret) return ret; | |
} | |
long ret = RegQueryValueExW_ish(hKey, pszValue, pdwType, pvData, pcbData); | |
if (pszSubKey) | |
RegCloseKey(hKey); | |
return ret; | |
} | |
// ------- ------- ------- ------- ------- ------- ------- -------- -------- // | |
int main(int argc, char const *argv[]) { | |
struct stat st; | |
char winePath[512]; | |
snprintf(winePath, 512, "%s/.wine", getenv("HOME")); | |
if (stat(winePath, &st) == -1) { perror("couldn't stat ~/.wine"); return 1; } | |
if (st.st_uid != getuid()) { perror("not owned by current user ~/.wine"); return 1; } | |
char socketPath[108]; | |
snprintf(socketPath, 108, "/tmp/.wine-%d/server-%lx-%lx/socket", getuid(), (unsigned long)st.st_dev, (unsigned long)st.st_ino); | |
printf("found %s\n", socketPath); | |
struct sockaddr_un addr; | |
addr.sun_family = AF_UNIX; | |
strcpy(addr.sun_path, socketPath); | |
size_t slen = sizeof(addr) - sizeof(addr.sun_path) + strlen(addr.sun_path) + 1; | |
if ((fd_socket = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { perror("socket failed"); return 1; } | |
int enable = 1; | |
setsockopt(fd_socket, SOL_SOCKET, SO_PASSCRED, &enable, sizeof(enable)); | |
if (connect(fd_socket, (struct sockaddr *)&addr, slen) == -1) { perror("connect failed"); return 1; } | |
obj_handle_t version; | |
request_fd = receive_fd(&version); | |
if (version != SERVER_PROTOCOL_VERSION) { fprintf(stderr, "version mismatch server %d != client %d\n", version, SERVER_PROTOCOL_VERSION); return 1; } | |
enable = 0; | |
setsockopt(fd_socket, SOL_SOCKET, SO_PASSCRED, &enable, sizeof(enable)); | |
{ | |
int reply_pipe = init_thread_pipe(); | |
int ret; | |
uint32_t pid, tid; | |
unsigned session_id; | |
size_t info_size; | |
timeout_t server_start_time; | |
unsigned int supported_machines_count = 0; | |
uint16_t supported_machines[8] = { 0 }; | |
SERVER_START_REQ(779, init_first_thread) | |
{ | |
req->unix_pid = getpid(); | |
req->unix_tid = syscall( __NR_gettid ); // get_unix_tid(); | |
req->reply_fd = reply_pipe; | |
req->wait_fd = wait_fd[1]; | |
req->debug_level = 1; // (TRACE_ON(server) != 0); | |
wine_server_set_reply( req, supported_machines, sizeof(supported_machines) ); | |
ret = wine_server_call( req ); | |
pid = reply->pid; | |
tid = reply->tid; | |
session_id = reply->session_id; | |
info_size = reply->info_size; | |
server_start_time = reply->server_start; | |
supported_machines_count = wine_server_reply_size( reply ) / sizeof(*supported_machines); | |
} | |
SERVER_END_REQ; | |
close( reply_pipe ); | |
} | |
printf("connected\n"); | |
wchar_t wbuffer[300]; | |
uint32_t wbufferSize = sizeof(wbuffer); | |
long r = RegGetValueW_ish(HKEY_LOCAL_MACHINE, | |
L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Steam App 238010", | |
L"InstallLocation", | |
NULL, | |
wbuffer, | |
&wbufferSize); | |
// assume ascii | |
char buffer[300]; | |
for (uint32_t i=0; i<300; i++) | |
buffer[i] = wbuffer[i]; // just truncate them | |
printf("DXHR install location: %s\n", buffer); | |
close(fd_socket); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment