I wrote this code to encrypt a file in python with AES and now I send this file over HTTP to a client in C++ using Windows OS. And I want to decrypt this file on the C++ client, but I haven't found any libraries/usefull code to help me with this manner.
Here is the code for python:
def derive_key_and_iv(password, salt, key_length, iv_length):
d = d_i = b''
while len(d) < key_length + iv_length:
d_i = md5(d_i + str.encode(password) + salt).digest()
d += d_i
return d[:key_length], d[key_length:key_length + iv_length]
def encrypt(in_file, out_file, password, key_length=32):
bs = AES.block_size # 16 bytes
salt = urandom(bs)
key, iv = derive_key_and_iv(password, salt, key_length, bs)
cipher = AES.new(key, AES.MODE_CBC, iv)
out_file.write(salt)
finished = False
while not finished:
chunk = in_file.read(1024 * bs)
if len(chunk) == 0 or len(chunk) % bs != 0: #final block
padding_length = (bs - len(chunk) % bs) or bs
chunk += str.encode(padding_length * chr(padding_length))
finished = True
out_file.write(cipher.encrypt(chunk))
password = 'ABCDE' # some password
with open("some_file.bin", "rb") as in_file, open("enc_some_file.enc", "wb") as out_file:
encrypt(in_file, out_file, password)
Update:
I am updating this question regarding my try with WinCrypt. I am trying to compile from VS CODE using MSYS64 g++ compiler on Windows. Here is the code that I am trying to run. I also have some additional libraries to help me with the client-server part, such as libcurlpp and libcurl.
#include <string>
#include <sstream>
#include <conio.h>
#include <iostream>
#include <fstream>
#include <curlpp/cURLpp.hpp>
#include <curlpp/Easy.hpp>
#include <curlpp/Options.hpp>
#include <wincrypt.h>
#include <windows.h>
#define AES_KEY_SIZE 32
#define IN_CHUNK_SIZE (AES_KEY_SIZE * 10) // a buffer must be a multiple of the key size
#define OUT_CHUNK_SIZE (IN_CHUNK_SIZE * 2) // an output buffer (for encryption) must be twice as big
using namespace std;
string host = ""; #some host
int main(void)
{
curlpp::Easy req;
req.setOpt(new curlpp::options::Url(host));
ofstream myfile;
myfile.open("enc_calc.enc", ios::out | ios::binary);
myfile << req;
myfile.close();
getch();
wchar_t *in_file = (wchar_t *)"enc_calc.enc";
wchar_t *out_file = (wchar_t *)"calc.dll";
wchar_t default_key[] = L"ABCDE";
wchar_t *key_str = default_key;
const size_t len = lstrlenW(key_str);
const size_t key_size = len * sizeof(key_str[0]);
const wchar_t *filename1 = (const wchar_t *)"enc_calc.enc";
const wchar_t *filename2 = (const wchar_t *)"calc.dll";
HANDLE hInpFile = CreateFileW(filename1, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (hInpFile == INVALID_HANDLE_VALUE) {
printf("Cannot open input file!\n");
system("pause");
return (-1);
}
HANDLE hOutFile = CreateFileW(filename2, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hOutFile == INVALID_HANDLE_VALUE) {
printf("Cannot open output file!\n");
system("pause");
return (-1);
}
DWORD dwStatus = 0;
BOOL bResult = FALSE;
wchar_t info[] = L"Microsoft Enhanced RSA and AES Cryptographic Provider";
HCRYPTPROV hProv;
if (!CryptAcquireContextW(&hProv, NULL, info, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) {
dwStatus = GetLastError();
printf("CryptAcquireContext failed: %x\n", dwStatus);
CryptReleaseContext(hProv, 0);
system("pause");
return dwStatus;
}
HCRYPTHASH hHash;
if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash)) {
dwStatus = GetLastError();
printf("CryptCreateHash failed: %x\n", dwStatus);
CryptReleaseContext(hProv, 0);
system("pause");
return dwStatus;
}
if (!CryptHashData(hHash, (BYTE*)key_str, key_size, 0)) {
DWORD err = GetLastError();
printf("CryptHashData Failed : %#x\n", err);
system("pause");
return (-1);
}
printf("[+] CryptHashData Success\n");
HCRYPTKEY hKey;
if (!CryptDeriveKey(hProv, CALG_AES_128, hHash, 0, &hKey)) {
dwStatus = GetLastError();
printf("CryptDeriveKey failed: %x\n", dwStatus);
CryptReleaseContext(hProv, 0);
system("pause");
return dwStatus;
}
printf("[+] CryptDeriveKey Success\n");
const size_t chunk_size = IN_CHUNK_SIZE;
BYTE *chunk = new BYTE[chunk_size];
DWORD out_len = 0;
BOOL isFinal = FALSE;
DWORD readTotalSize = 0;
DWORD inputSize = GetFileSize(hInpFile, NULL);
while (bResult = ReadFile(hInpFile, chunk, IN_CHUNK_SIZE, &out_len, NULL)) {
if (0 == out_len) {
break;
}
readTotalSize += out_len;
if (readTotalSize >= inputSize) {
isFinal = TRUE;
printf("Final chunk set, len: %d = %x\n", out_len, out_len);
}
if (!CryptDecrypt(hKey, NULL, isFinal, 0, chunk, &out_len)) {
printf("[-] CryptDecrypt failed: %x\n", GetLastError());
break;
}
DWORD written = 0;
if (!WriteFile(hOutFile, chunk, out_len, &written, NULL)) {
printf("writing failed!\n");
break;
}
memset(chunk, 0, chunk_size);
}
delete[]chunk; chunk = NULL;
CryptDestroyHash(hHash);
CryptDestroyKey(hKey);
CryptReleaseContext(hProv, 0);
CloseHandle(hInpFile);
CloseHandle(hOutFile);
printf("Finished. Processed %#x bytes.\n", readTotalSize);
return 0;
}
The errors I'm getting are regarding the windows.h library, such as:
"ULONG does not name a type" and other types in windows. and at runtime: "Cannot Open input file", not sure why, even tho the file is downloaded in the current folder and I'm trying to open it as such in the code.
Related
I'm trying to calculate MD5 of a file right after writing it:
std::wstring filePath = L"file.txt";
auto hFile = CreateFile(
filePath.c_str(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
//writing to file with WriteFile(hFile, buffer, DWORD(size), &written, NULL))
Now if I close it and reopen it's OK.
But I'm trying to calculate MD5 without closing it.
To be sure that pointer is set to correct position, I also tried to save a pointer to file's end:
LARGE_INTEGER liOfs = {0};
LARGE_INTEGER liNew = {0};
SetFilePointerEx(hFile, liOfs, &liNew, FILE_CURRENT);
SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
auto md5 = CalculateMd5(hFile); // md5 is correct here
// restore pointer
SetFilePointerEx(hFile, liNew, NULL, FILE_BEGIN);
CloseHandle(hFile);
So, I'm getting exception at CloseHandle(hFile): 0xC0000008: An invalid handle was specified.
And there is MD5 calculating:
std::string GetMD5HashOfFile(HANDLE hFile)
{
if (INVALID_HANDLE_VALUE == hFile) {
return {};
}
HCRYPTPROV hProv = 0;
if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
return {};
}
HCRYPTHASH hHash = 0;
if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) {
CryptReleaseContext(hProv, 0);
return {};
}
static constexpr int BUFSIZE = 1024;
DWORD cbRead = 0;
BYTE rgbFile[BUFSIZE];
BOOL bResult = FALSE;
while (bResult = ReadFile(hFile, rgbFile, BUFSIZE, &cbRead, NULL)) {
if (0 == cbRead) {
break;
}
if (!CryptHashData(hHash, rgbFile, cbRead, 0)) {
CryptReleaseContext(hProv, 0);
CryptDestroyHash(hHash);
return {};
}
}
if (!bResult) {
CryptReleaseContext(hProv, 0);
CryptDestroyHash(hHash);
return {};
}
static constexpr int MD5LEN = 16;
CHAR rgbDigits[] = "0123456789abcdef";
BYTE rgbHash[MD5LEN];
DWORD cbHash = MD5LEN;
if (CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0)) {
std::ostringstream oss;
for (auto c : rgbHash) {
oss.fill('0');
oss.width(2);
oss << std::hex << static_cast<int>(c);
}
CryptDestroyHash(hHash);
CryptReleaseContext(hProv, 0);
return oss.str();
}
else {
CryptDestroyHash(hHash);
CryptReleaseContext(hProv, 0);
return {};
}
CryptDestroyHash(hHash);
CryptReleaseContext(hProv, 0);
return {};
}
Here is the test program:
#include <Windows.h>
#include <wincrypt.h>
#include <iostream>
#include <sstream>
int main()
{
const std::wstring filePath = L"test.txt";
auto r = DeleteFile(filePath.c_str());
if (!r) {
auto e = GetLastError();
if (e != ERROR_FILE_NOT_FOUND) {
std::cout << e << '\n';
return -1;
}
}
auto hFile = CreateFile(filePath.c_str(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
if (hFile == INVALID_HANDLE_VALUE) {
return -1;
}
DWORD written = 0;
const std::wstring buffer = L"Hello, world.";
const auto size = buffer.length() * sizeof(wchar_t);
if (!WriteFile(hFile, buffer.c_str(), size, &written, NULL)) {
CloseHandle(hFile);
return -1;
}
if (size != written) {
CloseHandle(hFile);
return -1;
}
/*CloseHandle(hFile);
hFile = CreateFile(filePath.c_str(),
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
NULL);*/
LARGE_INTEGER liOfs = { 0 };
LARGE_INTEGER liNew = { 0 };
SetFilePointerEx(hFile, liOfs, &liNew, FILE_CURRENT);
auto md5 = GetMD5HashOfFile(hFile);
std::cout << "MD5: " << md5 << '\n';
SetFilePointerEx(hFile, liNew, NULL, FILE_BEGIN);
CloseHandle(hFile);
return 0;
}
It doesn't throw exception. But it somehow calculates incorrect hash: app's MD5 is d41d8cd98f00b204e9800998ecf8427e, and cmd tool shows another - 1207b6ae90980a5b039d57384b8bbd26. If I uncomment lines in the middle hashes are equal, but still no exception. Command to check hash is:
certutil -hashfile test.txt MD5
UPDATE: I'm really sorry. It's a third question where I cann't debug my app properly. Actually, the file was closed twice, hence the exception. I swear, I'll try to do something with myself).
The only question left: is it possible to calculate file hash properly, because without closing the file handle in between gives a wrong hash.
I have 2 programs, one gets all processes information using Windows API and stores them in a vector of structure like this:
struct Info{
int pid,ppid;
char exeName[256];
}
I keep back_pushing structure in vector when i populate a structure with data.
Now, I've tried to use memcpy() to memorize data in mapped-file but I can't read it properly in the second program. Can you help me to figure out how to read data properly?
Code program 1:
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <stdio.h>
#include <Tlhelp32.h>
#include <vector>
struct InfoProces {
DWORD pid;
DWORD ppid;
char exeName[256];
};
int main(){
HANDLE hProcesses;
PROCESSENTRY32 pe32;
hProcesses = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
if (hProcesses == INVALID_HANDLE_VALUE)
{
printf("CreateToolhelp32Snapshot failed. err = %d", GetLastError());
return -1;
}
pe32.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(hProcesses, &pe32)) {
printf("Process32First failed. err = %d", GetLastError());
CloseHandle(hProcesses);
return -1;
}
HANDLE hdata = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 1024 * 1024, "data");
if (hdata == NULL) {
printf("Can't create a file mapping. err = %d", GetLastError());
return -1;
}
unsigned char* pdata = (unsigned char*)MapViewOfFile(hdata, FILE_MAP_WRITE, 0, 0, 0);
if (pdata == NULL) {
printf("cant get pointer to mapping file. err = %d", GetLastError());
return -1;
}
do {
InfoProces pi;
pi.pid = pe32.th32ProcessID;
pi.ppid = pe32.th32ParentProcessID;
strcpy(pi.exeName, pe32.szExeFile);
} while (Process32Next(hProcesses, &pe32));
getchar();
CloseHandle(hProcesses);
return 0;
Code program 2:
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <vector>
#include <stdio.h>
struct InfoProces {
DWORD pid;
DWORD ppid;
char exeName[256];
};
int main()
{
HANDLE hdata = OpenFileMapping(FILE_MAP_ALL_ACCESS, false, "data");
unsigned char* pdata = (unsigned char*)MapViewOfFile(hdata, FILE_MAP_READ, 0, 0, 0);
if (pdata == NULL) {
printf("cant get pointer to mapped file. err = %d", GetLastError());
return -1;
}
std::vector<InfoProces>processes;
memcpy(&processes,pdata,sizeof(data));
for (std::vector<InfoProces>::iterator i = processes.begin(); i < processes.end(); i++)
printf("Process[%d](parinte[%d]): %s\n", i->pid, i->ppid, i->exeName);
return 0;
}
As the comment pointed out, you have got the starting address of the mapped view(pdata), You can only use the pdata for memory sharing, but not any stack address of the process.
Try to copy the memory of InfoProces to the pdata in program 1:
memcpy(pdata, &pi, sizeof(InfoProces));
In addition, in program 2, you did not set the size for the vector, and you should replace &processes with processes.data(). You also need to know the size of the array you added in program 1:
std::vector<InfoProces> processes;
processes.resize(count);
memcpy(processes.data(), pdata, sizeof(InfoProces)*count);
Following code works for me.
Program 1:(Record the count of InfoProces and save it to a DWORD in the beginning of pdata)
...
unsigned char* pdata = (unsigned char*)MapViewOfFile(hdata, FILE_MAP_WRITE, 0, 0, 0);
if (pdata == NULL) {
printf("cant get pointer to mapping file. err = %d", GetLastError());
return -1;
}
unsigned char* begin = pdata;
//offset a size of DWORD;
DWORD count = 0;
pdata += sizeof(DWORD);
do {
InfoProces pi;
pi.pid = pe32.th32ProcessID;
pi.ppid = pe32.th32ParentProcessID;
strcpy(pi.exeName, pe32.szExeFile);
memcpy(pdata, &pi, sizeof(InfoProces));
pdata += sizeof(InfoProces);
count++;
} while (Process32Next(hProcesses, &pe32));
memcpy(begin, &count, sizeof(DWORD));
getchar();
UnmapViewOfFile(begin);
CloseHandle(hdata);
CloseHandle(hProcesses);
...
Program 2:
...
unsigned char* pdata = (unsigned char*)MapViewOfFile(hdata, FILE_MAP_READ, 0, 0, 0);
if (pdata == NULL) {
printf("cant get pointer to mapped file. err = %d", GetLastError());
return -1;
}
unsigned char* begin = pdata;
DWORD count = 0;
memcpy(&count, pdata, sizeof(DWORD));
pdata += sizeof(DWORD);
std::vector<InfoProces> processes;
processes.resize(count);
memcpy(processes.data(), pdata, sizeof(InfoProces)*count);
for (std::vector<InfoProces>::iterator i = processes.begin(); i < processes.end(); i++)
printf("Process[%d](parinte[%d]): %s\n", i->pid, i->ppid, i->exeName);
UnmapViewOfFile(begin);
CloseHandle(hdata);
...
Finally, do not forget to close the handle and unmap address.
I am working on a encryption algo with c++ using WINAPI. My encryption works flawless and my decryption too untill the last chunk to be decrypted with final = TRUE. I got the NTE_BAD_DATA error.
PS : I manualy check the buffer and the decryption works fine untill the last CryptDecrypt.
If someone have an idea, kindly help me :)
Here is my code :
PVOID test(PVOID buffer, DWORD* length, PCHAR key_str2,bool isdecrypt) {
CHAR default_key[] = "3igcZhRdWq96m3GUmTAiv9";
CHAR* key_str = default_key;
size_t len = lstrlenA(key_str);
DWORD dwStatus = 0;
BOOL bResult = FALSE;
wchar_t info[] = L"Microsoft Enhanced RSA and AES Cryptographic Provider";
HCRYPTPROV hProv;
if (!CryptAcquireContextW(&hProv, NULL, info, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) {
dwStatus = GetLastError();
CryptReleaseContext(hProv, 0);
return 0;
}
HCRYPTHASH hHash;
if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash)) {
dwStatus = GetLastError();
CryptReleaseContext(hProv, 0);
return 0;
}
if (!CryptHashData(hHash, (BYTE*)key_str, len, 0)) {
DWORD err = GetLastError();
return 0;
}
HCRYPTKEY hKey;
if (!CryptDeriveKey(hProv, CALG_AES_128, hHash, 0, &hKey)) {
dwStatus = GetLastError();
CryptReleaseContext(hProv, 0);
return 0;
}
const size_t chunk_size = CHUNK_SIZE;
BYTE chunk[chunk_size] = { 0 };
DWORD out_len = 0;
BOOL isFinal = FALSE;
DWORD readTotalSize = 0;
DWORD inputSize = *length;
PVOID newBuff = VirtualAlloc(0, inputSize, MEM_COMMIT, PAGE_READWRITE);
while (true)
{
if (readTotalSize + chunk_size >= inputSize)
{
isFinal = TRUE;
memcpy(chunk, PVOID((DWORD)buffer + readTotalSize), inputSize - readTotalSize);
out_len = inputSize - readTotalSize;
}
else {
memcpy(chunk, PVOID((DWORD)buffer + readTotalSize), chunk_size);
out_len = chunk_size;
}
if (isdecrypt) {
if (!CryptDecrypt(hKey, NULL, isFinal, 0, chunk, &out_len)) {
int a = GetLastError();
break;
}
}
else {
if (!CryptEncrypt(hKey, NULL, isFinal, 0, chunk, &out_len, chunk_size)) {
break;
}
}
if (readTotalSize + chunk_size >= inputSize) {
memcpy(PVOID((DWORD)newBuff + readTotalSize), chunk, inputSize - readTotalSize);
readTotalSize += inputSize - readTotalSize;
}
else {
memcpy(PVOID((DWORD)newBuff + readTotalSize), chunk, chunk_size);
readTotalSize += chunk_size;
}
if (isFinal)
break;
memset(chunk, 0, chunk_size);
}
CryptReleaseContext(hProv, 0);
CryptDestroyKey(hKey);
CryptDestroyHash(hHash);
return newBuff;
}
You are not storing off the encryption the final block correctly.
The amount of data you pass into the encryption is in out_len, and the amount of data that is encrypted is put back into out_len. For a block cypher that doesn't matter for whole blocks, but your last block which is less than a whole block being passed into encryption probably creates a whole block of encrypted data.
This means that your encrypted data is probably a little larger than your plain text in the final block, but you make no effort to handle this so on when you decrypt you have an issue as you've dropped some of the encrypted data on the floor.
Use the value put into out_len by the encryption function to store off your data (as you overwrite your data stream you'll need to make sure there's space though)
Hi guys i am trying to get dll md5 hash but it is returning same value all the time, what i did wrong?
this dll is already loaded when i am trying to getHash
i am getting hash with getHash() method and calculating it with CalcHash
thnks in advanced.
#define BUFSIZE 1024
#define MD5LEN 16
int CalcHash(HANDLE hFile, char *md5sum)
{
BOOL bResult = FALSE;
HCRYPTPROV hProv = 0;
HCRYPTHASH hHash = 0;
BYTE rgbFile[BUFSIZE];
DWORD cbRead = 0;
BYTE rgbHash[MD5LEN];
DWORD cbHash = 0;
CHAR rgbDigits[] = "0123456789abcdef";
char byt[3];
int rc, err;
rc = CryptAcquireContext(&hProv, NULL, MS_STRONG_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
if(!rc)
{
err = GetLastError();
if(err==0x80090016)
{
//first time using crypto API, need to create a new keyset
rc=CryptAcquireContext(&hProv, NULL, MS_STRONG_PROV, PROV_RSA_FULL, CRYPT_NEWKEYSET);
if(!rc)
{
err=GetLastError();
return 0;
}
}
}
CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash);
while(bResult = ReadFile(hFile, rgbFile, BUFSIZE, &cbRead, NULL))
{
if (0 == cbRead)
{
break;
}
CryptHashData(hHash, rgbFile, cbRead, 0);
}
cbHash = MD5LEN;
CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0);
md5sum[0] = 0;
for (DWORD i = 0; i < cbHash; i++)
{
sprintf(byt, "%c%c", rgbDigits[rgbHash[i] >> 4], rgbDigits[rgbHash[i] & 0xf]);
strcat(md5sum, byt);
}
CryptDestroyHash(hHash);
CryptReleaseContext(hProv, 0);
return 1;
}
char *getHash()
{
CalcHash(L"dsetup.dll", md5sum);
Logger(md5sum);
return md5sum;
}
I wrote this piece of code to decrypt a given cipher using a given key and iv. It works fine until CryptDecrypt which returns false and GetLastError() returns 0x80090005 (which should be NTE_BAD_DATA).
#include <Windows.h>
#include <wincrypt.h>
struct AesKey
{
BLOBHEADER Header;
DWORD dwKeyLength;
BYTE cbKey[16];
AesKey()
{
ZeroMemory(this, sizeof(*this));
Header.bType = PLAINTEXTKEYBLOB;
Header.bVersion = CUR_BLOB_VERSION;
Header.reserved = 0;
Header.aiKeyAlg = CALG_AES_128;
dwKeyLength = 16;
}
};
void AesDecrypt(unsigned char *output, unsigned char *input, int inLen, unsigned char *key, unsigned char *iv, int &plainSize)
{
HCRYPTPROV provider;
AesKey rawKey;
HCRYPTKEY cKey;
BOOL hr = CryptAcquireContext(&provider, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, 0);
if (hr == FALSE)
throw "Unable to acquire AES Context";
for (int i = 0; i < 16; i++)
rawKey.cbKey[i] = key[i];
hr = CryptImportKey(provider, (BYTE *) &rawKey, sizeof(AesKey), NULL, 0, &cKey);
if (hr == FALSE)
throw "Unable to import given key";
hr = CryptSetKeyParam(cKey, KP_IV, (BYTE *) iv, 0);
if (hr == FALSE)
throw "Unable to set IV";
DWORD dwMode = CRYPT_MODE_CBC;
hr = CryptSetKeyParam(cKey, KP_MODE, (BYTE*) &dwMode, 0);
if (hr == FALSE)
throw "Unable to set mode";
memcpy(output, input, inLen);
DWORD d = (DWORD) inLen;
hr = CryptDecrypt(cKey, NULL, TRUE, 0, output, &d);
if (hr == FALSE)
{
int err = GetLastError();
throw "Error during Decryption";
}
plainSize = d;
}
If you have any idea, please share :)
Thanks in advance...