Skip to content

Instantly share code, notes, and snippets.

@rrika
Created September 1, 2023 21:06
Show Gist options
  • Save rrika/685a8cd016f14a4c337f6de2a7f7c3e9 to your computer and use it in GitHub Desktop.
Save rrika/685a8cd016f14a4c337f6de2a7f7c3e9 to your computer and use it in GitHub Desktop.
reading the wine registry
// 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