so I created a C++ DLL file and I'd like to use it in python that's not my problem. My problem is the return type of the DLL functions, I have 2 function, both of them return DATA_BLOB and I didn't find a way that I could use DATA_BLOB in python, my question is how can I use DATA_BLOB in python.
The return type of a DLL function is generally the result of a function execution. If you need to return data, put it in the parameters, and use the pointer.
DLL Sample:
BOOL DLLCall1(PDATA_BLOB DataOut) //return DATA_BLOB pointer
{
DATA_BLOB DataIn;
BYTE* pbDataInput = (BYTE*)"Hello world of data protection.";
DWORD cbDataInput = strlen((char*)pbDataInput) + 1;
//--------------------------------------------------------------------
// Initialize the DataIn structure.
DataIn.pbData = pbDataInput;
DataIn.cbData = cbDataInput;
CryptProtectData(
&DataIn,
L"This is the description string.", // A description string
// to be included with the
// encrypted data.
NULL, // Optional entropy not used.
NULL, // Reserved.
NULL, // Pass NULL for the
// prompt structure.
0,
DataOut);
return 1;
}
BOOL DLLCall2(DATA_BLOB DataOut)
{
LPWSTR pDescrOut = NULL;
DATA_BLOB DataVerify;
CRYPTPROTECT_PROMPTSTRUCT PromptStruct;
ZeroMemory(&PromptStruct, sizeof(PromptStruct));
PromptStruct.cbSize = sizeof(PromptStruct);
PromptStruct.dwPromptFlags = CRYPTPROTECT_PROMPT_ON_PROTECT;
PromptStruct.szPrompt = L"This is a user prompt.";
if (CryptUnprotectData(
&DataOut,
&pDescrOut,
NULL, // Optional entropy
NULL, // Reserved
&PromptStruct, // Optional PromptStruct
0,
&DataVerify))
{
printf("The decrypted data is: %s\n", DataVerify.pbData);
printf("The description of the data was: %S\n", pDescrOut);
return 1;
}
return 0;
}
python:
from ctypes import *
from ctypes.wintypes import DWORD
import ctypes
class DATA_BLOB(Structure):
_fields_ = [
("cbData", DWORD),
("pbData", POINTER(c_char)),
]
lib = cdll.LoadLibrary("C:\\Test.dll")
blobOut = DATA_BLOB()
lib.DLLCall1(byref(blobOut))
lib.DLLCall2(blobOut)
Related
Context
I have a Windows desktop program which can be used by different types of user roles.
Each user role has its own set of restricted views and actions, which depend on the outcome of other user actions.
The program communicates to an API and authentication is done with Windows authentication.
In order to test the program end to end, both manually and within a pipeline step, multiple instances of the same program need to run at the same time under different users.
Containers are not an option for the time being.
I am resorting to writing a Python script which uses ctypes.windll.advapi32.CreateProcessWithLogonW to run the program as a specified user.
Issue
When calling CreateProcessWithLogonW with dwLogonFlags equal to LOGON_WITH_PROFILE a folder C:\Users\TEMP is created.
All the files created during the program session are stored under that folder.
The folder is destroyed when the program terminates.
When calling CreateProcessWithLogonW with dwLogonFlags equal to LOGON_NETCREDENTIALS_ONLY the folder is not created.
All the files created during the program session are stored under C:\Users<username of logged in user calling CreateProcessWithLogonW>
Authentication is done only after the program has started and the first API call is made, in line with the documentation for this flag.
Neither option is ideal because different users need to store their own data during their sessions.
Question
How can I make sure a folder C:\Users<username> is created for each user ?
How can I make sure a folder C:\Users is created for each
user ?
You can use the CreateDirectory function and specify the SECURITY_ATTRIBUTES structure to create a folder.
lpSecurityDescriptor: A pointer to a SECURITY_DESCRIPTOR structure
that controls access to the object. If the value of this member is
NULL, the object is assigned the default security descriptor
associated with the access token of the calling process. This is not
the same as granting access to everyone by assigning a NULL
discretionary access control list (DACL). By default, the default DACL
in the access token of a process allows access only to the user
represented by the access token.
You can assign permissions to different users, such as restricting access or writing.
C++ Sample:
#include <windows.h>
#include <stdio.h>
#include <aclapi.h>
#include <tchar.h>
#include <mq.h.>
#pragma comment(lib, "advapi32.lib")
HRESULT GetSid(
LPCWSTR wszAccName,
PSID* ppSid
)
{
// Validate the input parameters.
if (wszAccName == NULL || ppSid == NULL)
{
return MQ_ERROR_INVALID_PARAMETER;
}
// Create buffers that may be large enough.
// If a buffer is too small, the count parameter will be set to the size needed.
const DWORD INITIAL_SIZE = 32;
DWORD cbSid = 0;
DWORD dwSidBufferSize = INITIAL_SIZE;
DWORD cchDomainName = 0;
DWORD dwDomainBufferSize = INITIAL_SIZE;
WCHAR* wszDomainName = NULL;
SID_NAME_USE eSidType;
DWORD dwErrorCode = 0;
HRESULT hr = MQ_OK;
// Create buffers for the SID and the domain name.
*ppSid = (PSID) new BYTE[dwSidBufferSize];
if (*ppSid == NULL)
{
return MQ_ERROR_INSUFFICIENT_RESOURCES;
}
memset(*ppSid, 0, dwSidBufferSize);
wszDomainName = new WCHAR[dwDomainBufferSize];
if (wszDomainName == NULL)
{
return MQ_ERROR_INSUFFICIENT_RESOURCES;
}
memset(wszDomainName, 0, dwDomainBufferSize * sizeof(WCHAR));
// Obtain the SID for the account name passed.
for (; ; )
{
// Set the count variables to the buffer sizes and retrieve the SID.
cbSid = dwSidBufferSize;
cchDomainName = dwDomainBufferSize;
if (LookupAccountNameW(
NULL, // Computer name. NULL for the local computer
wszAccName,
*ppSid, // Pointer to the SID buffer. Use NULL to get the size needed,
&cbSid, // Size of the SID buffer needed.
wszDomainName, // wszDomainName,
&cchDomainName,
&eSidType
))
{
if (IsValidSid(*ppSid) == FALSE)
{
wprintf(L"The SID for %s is invalid.\n", wszAccName);
dwErrorCode = MQ_ERROR;
}
break;
}
dwErrorCode = GetLastError();
// Check if one of the buffers was too small.
if (dwErrorCode == ERROR_INSUFFICIENT_BUFFER)
{
if (cbSid > dwSidBufferSize)
{
// Reallocate memory for the SID buffer.
wprintf(L"The SID buffer was too small. It will be reallocated.\n");
FreeSid(*ppSid);
*ppSid = (PSID) new BYTE[cbSid];
if (*ppSid == NULL)
{
return MQ_ERROR_INSUFFICIENT_RESOURCES;
}
memset(*ppSid, 0, cbSid);
dwSidBufferSize = cbSid;
}
if (cchDomainName > dwDomainBufferSize)
{
// Reallocate memory for the domain name buffer.
wprintf(L"The domain name buffer was too small. It will be reallocated.\n");
delete[] wszDomainName;
wszDomainName = new WCHAR[cchDomainName];
if (wszDomainName == NULL)
{
return MQ_ERROR_INSUFFICIENT_RESOURCES;
}
memset(wszDomainName, 0, cchDomainName * sizeof(WCHAR));
dwDomainBufferSize = cchDomainName;
}
}
else
{
wprintf(L"LookupAccountNameW failed. GetLastError returned: %d\n", dwErrorCode);
hr = HRESULT_FROM_WIN32(dwErrorCode);
break;
}
}
delete[] wszDomainName;
return hr;
}
void main()
{
PSID sid;
GetSid(L"strive", &sid);
DWORD dwRes, dwDisposition;
PACL pACL = NULL;
PSECURITY_DESCRIPTOR pSD = NULL;
EXPLICIT_ACCESS ea;
SECURITY_ATTRIBUTES sa;
BOOL l = 0;
// Initialize an EXPLICIT_ACCESS structure for an ACE.
// The ACE will allow Everyone read access to the key.
ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));
ea.grfAccessPermissions = GENERIC_ALL;
ea.grfAccessMode = SET_ACCESS;
ea.grfInheritance = NO_INHERITANCE;
ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea.Trustee.TrusteeType = TRUSTEE_IS_USER;
ea.Trustee.ptstrName = (LPTSTR)sid;
// Create a new ACL that contains the new ACEs.
dwRes = SetEntriesInAcl(1, &ea, NULL, &pACL);
if (ERROR_SUCCESS != dwRes)
{
_tprintf(_T("SetEntriesInAcl Error %u\n"), GetLastError());
goto Cleanup;
}
// Initialize a security descriptor.
pSD = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR,
SECURITY_DESCRIPTOR_MIN_LENGTH);
if (NULL == pSD)
{
_tprintf(_T("LocalAlloc Error %u\n"), GetLastError());
goto Cleanup;
}
if (!InitializeSecurityDescriptor(pSD,
SECURITY_DESCRIPTOR_REVISION))
{
_tprintf(_T("InitializeSecurityDescriptor Error %u\n"),
GetLastError());
goto Cleanup;
}
// Add the ACL to the security descriptor.
if (!SetSecurityDescriptorDacl(pSD,
TRUE, // bDaclPresent flag
pACL,
FALSE)) // not a default DACL
{
_tprintf(_T("SetSecurityDescriptorDacl Error %u\n"),
GetLastError());
goto Cleanup;
}
// Initialize a security attributes structure.
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = pSD;
sa.bInheritHandle = FALSE;
// Use the security attributes to set the security descriptor
// when you create a key.
l = CreateDirectory(L"C:\\Users\\strive", &sa);
Cleanup:
if (pACL)
LocalFree(pACL);
if (pSD)
LocalFree(pSD);
return;
}
The following (MCVE if compiled as a cdylib called libffitest, requires libc as a dependency) demonstrates the problem:
use libc::{c_char, c_void, size_t};
use std::ffi::CString;
use std::mem;
use std::slice;
#[repr(C)]
#[derive(Clone)]
pub struct Array {
pub data: *const c_void,
pub len: size_t,
}
#[no_mangle]
pub unsafe extern "C" fn bar() -> Array {
let v = vec![
CString::new("Hi There").unwrap().into_raw(),
CString::new("Hi There").unwrap().into_raw(),
];
v.into()
}
#[no_mangle]
pub extern "C" fn drop_word_array(arr: Array) {
if arr.data.is_null() {
return;
}
// Convert incoming data to Vec so we own it
let mut f: Vec<c_char> = arr.into();
// Deallocate the underlying c_char data by reconstituting it as a CString
let _: Vec<CString> = unsafe { f.iter_mut().map(|slice| CString::from_raw(slice)).collect() };
}
// Transmute to array for FFI
impl From<Vec<*mut c_char>> for Array {
fn from(sl: Vec<*mut c_char>) -> Self {
let array = Array {
data: sl.as_ptr() as *const c_void,
len: sl.len() as size_t,
};
mem::forget(sl);
array
}
}
// Reconstitute from FFI
impl From<Array> for Vec<c_char> {
fn from(arr: Array) -> Self {
unsafe { slice::from_raw_parts_mut(arr.data as *mut c_char, arr.len).to_vec() }
}
}
I thought that by reconstituting the incoming Array as a slice, taking ownership of it as a Vec, then reconstituting the elements as CString, I was freeing any allocated memory, but I'm clearly doing something wrong. Executing this Python script tells me that it's trying to free a pointer that was not allocated:
python(85068,0x10ea015c0) malloc: *** error for object 0x7ffdaa512ca1: pointer being freed was not allocated
import sys
import ctypes
from ctypes import c_void_p, Structure, c_size_t, cast, POINTER, c_char_p
class _FFIArray(Structure):
"""
Convert sequence of structs to C-compatible void array
"""
_fields_ = [("data", c_void_p),
("len", c_size_t)]
def _arr_to_wordlist(res, _func, _args):
ls = cast(res.data, POINTER(c_char_p * res.len))[0][:]
print(ls)
_drop_wordarray(res)
prefix = {"win32": ""}.get(sys.platform, "lib")
extension = {"darwin": ".dylib", "win32": ".dll"}.get(sys.platform, ".so")
lib = ctypes.cdll.LoadLibrary(prefix + "ffitest" + extension)
lib.bar.argtypes = ()
lib.bar.restype = _FFIArray
lib.bar.errcheck = _arr_to_wordlist
_drop_wordarray = lib.drop_word_array
if __name__ == "__main__":
lib.bar()
Well, that was a fun one to go through.
Your biggest problem is the following conversion:
impl From<Array> for Vec<c_char> {
fn from(arr: Array) -> Self {
unsafe { slice::from_raw_parts_mut(arr.data as *mut c_char, arr.len).to_vec() }
}
}
You start with what comes out of the FFI boundary as an array of strings (i.e. *mut *mut c_char). For some reason, you decide that all of a sudden, it is a Vec<c_char> and not a Vec<*const c_char> as you would expect for the CString conversion. That's UB #1 - and the cause of your use-after-free.
The unnecessarily convoluted conversions made things even muddier due to the constant juggling between types. If your FFI boundary is Vec<CString>, why do you split the return into two separate calls? That's literally calling for disaster, as it happened.
Consider the following:
impl From<Array> for Vec<CString> {
fn from(arr: Array) -> Self {
unsafe {
slice::from_raw_parts(
arr.data as *mut *mut c_char,
arr.len
)
.into_iter().map(|r| CString::from_raw(*r))
.collect()
}
}
}
This gives you a one-step FFI boundary conversion (without the necessity for the second unsafe block in your method), clean types and no leaks.
I have a struct very similar to this:
struct data_point
{
data_point() = delete;
data_point(const int& data) :
m_data(data)
{}
int m_data;
};
I also have this type declared as such.
typedef std::vector<data_point> data_list;
The binding for this struct is defined:
PYBIND11_MODULE(data_lib, ref)
{
py::class_<data_point> dp(ref, "data_point");
dp.def(py::init<const int&>());
dp.def_readwrite("m_data", &data_point::m_data);
}
How do I define a binding for the typedef list type?
Its not clear to me how to do this in the pybind documentation.
For this specific issue, pybind will automatically interpret an std::vector type as a python list when you include "pybind11/stl.h" . Thus, a binding for this type is unnecessary.
Ex:
#include "pybind11.h"
#include "pybind11/stl.h"
struct data_point
{
data_point() = delete;
data_point(const int& data) :
m_data(data)
{}
int m_data;
};
std::vector<data_point> make_vec(){
return {data_point(20), data_point(18)};
}
PYBIND11_MODULE(data_lib, ref)
{
py::class_<data_point> dp(ref, "data_point");
dp.def(py::init<const int&>());
dp.def_readwrite("m_data", &data_point::m_data);
ref.def("make_vec", &make_vec, "A function that returns a vector of data_points");
}
In python, when you import the data_lib library you will be able to use functions that return lists of data_point.
import data_lib
p = data_lib.make_vec()
print len(p)
output: 2
Suppose I want to get the reference to some global / internal c++ object, one method is to declare function with boost::python::return_value_policy<reference_existing_object>().
Both GetGlobalObjectA and GetGlobalObjectB return the reference to the original c++ object without create a new copy;
But how to make GetGlobalObjectByID return a ref to the existing c++ object?
struct A { uint32_t value; };
struct B { uint64_t value; };
A globalA;
B globalB;
boost::python::object GetGlobalObjectByID(int id)
{
// boost::python::object will return a new copy of C++ object, not the global one.
if (id == 1)
return boost::python::object(&globalA);
else if (id == 2)
return boost::python::object(&globalB);
else
return boost::python::object(nullptr);
}
A& GetGlobalObjectA() { return globalA; }
B& GetGlobalObjectB() { return globalB; }
BOOST_PYTHON_MODULE(myModule)
{
using namespace boost::python;
class_<A>("A");
class_<B>("B");
def("GetGlobalObjectByID", GetGlobalObjectByID);
def("GetGlobalObjectA", GetGlobalObjectA, return_value_policy<reference_existing_object>());
def("GetGlobalObjectB", GetGlobalObjectB, return_value_policy<reference_existing_object>());
}
Based on this answer, I found it is possible to use reference_existing_object::apply as a converter.
template <typename T>
inline object MagicMethod(T* ptr)
{
typename reference_existing_object::apply::type converter;
handle handle(converter(ptr));
return object(handle);
}
And here is the modified version.
boost::python::object GetGlobalObjectByID(int id)
{
if (id == 1)
return MagicMethod(&globalA);
else if (id == 2)
return MagicMethod(&globalB);
else
return boost:python::object();
}
I am learning python and trying to convert the above code snippet found online to python.
As my understanding, the below code is generating the session key based upon SHA1 hash of the password "Microsoft" but I am not sure how I can derive AES 256 key based upon the hash of the password in python. And when I use AES.new(), what IV should be in this case? 16 Random bytes?
string encrypt ( const char *s )
{
DWORD dwSize = strlen ( s );
DWORD dwSize2 = strlen ( s );
HCRYPTHASH hHash = NULL;
HCRYPTKEY hKey = NULL;
HCRYPTPROV hProv = NULL;
char *buffer;
char *pwd = "Microsoft";
int pwdLen = strlen ( pwd );
// CryptAcquireContext function is used to acquire a handle to a particular key container within a particular cryptographic service provider (CSP)
if ( ! CryptAcquireContext ( &hProv, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, 0 ) )
{
printf ( "Unable to acquire encryption context\n" );
return NULL;
}
// CryptCreateHash function initiates the hashing of a stream of data. It creates and returns to the calling application a handle to a cryptographic service provider (CSP) hash object
if ( ! CryptCreateHash ( hProv, CALG_SHA1, 0, 0, &hHash ) )
{
CryptReleaseContext ( hProv, 0 );
printf ( "Unable to create hash\n" );
return NULL;
}
// CryptHashData function adds data to a specified hash object
if ( ! CryptHashData ( hHash, (const byte *)pwd, pwdLen, 0 ) )
{
CryptDestroyHash ( hHash );
CryptReleaseContext ( hProv, 0 );
printf ( "Unable to add key\n" );
return NULL;
}
// CryptDeriveKey function generates cryptographic session keys derived from a base data value
if ( ! CryptDeriveKey ( hProv, CALG_AES_256, hHash, 0, &hKey ) )
{
CryptDestroyHash ( hHash );
CryptReleaseContext ( hProv, 0 );
printf ( "Unable to derive key\n" );
return NULL;
}
// CryptEncrypt function encrypts data; have API return us the required buffer size
CryptEncrypt ( hKey, 0, true, 0, 0, &dwSize, strlen ( s ) );
}