Cmd.exe output Unicode external commands - c

I am trying to get unicode output from running cmd.exe commands. I see the /U switch only works for builtin commands like dir and not others like ipconfig.
If I change the name of my computer to someone like Chinese/Russian/Japanese language, and run ipconfig /all, it will display it in the console just fine. But when I use CreateProcesssW() and redirect my output to a pipe, it doesn't give me unicode back even though it is displaying it properly on the console.
#include <Windows.h>
#include <stdio.h>
int main() {
HANDLE hPipeRead, hPipeWrite;
SECURITY_ATTRIBUTES saAttr = { sizeof(SECURITY_ATTRIBUTES) };
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
if (!CreatePipe(&hPipeRead, &hPipeWrite, &saAttr, 0))
return 0;
STARTUPINFO si = { sizeof(STARTUPINFO) };
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.hStdOutput = hPipeWrite;
si.hStdError = hPipeWrite;
si.wShowWindow = SW_HIDE;
PROCESS_INFORMATION pi = { 0 };
WCHAR works[] = L"C:\\windows\\system32\\cmd.exe /u /c dir C:\\test";
WCHAR dont[] = L"cmd.exe /u /c ipconfig";
BOOL fSuccess = CreateProcessW(NULL, works, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
if (!fSuccess) {
CloseHandle(hPipeWrite);
CloseHandle(hPipeRead);
return 0;
}
BOOL bProcessEnded = FALSE;
for (; !bProcessEnded;) {
bProcessEnded = WaitForSingleObject(pi.hProcess, 50) == WAIT_OBJECT_0;
while (TRUE) {
CHAR *buf = NULL;
DWORD dwRead = 0;
DWORD dwAvail = 0;
if (!PeekNamedPipe(hPipeRead, NULL, 0, NULL, &dwAvail, NULL))
break;
if (!dwAvail)
break;
buf = HeapAlloc(GetProcessHeap(), 0, dwAvail);
if (buf == NULL) {
return 0;
}
if (!ReadFile(hPipeRead, buf, dwAvail, &dwRead, NULL) || !dwRead)
break;
printf("%ls", buf);
}
}
CloseHandle(hPipeWrite);
CloseHandle(hPipeRead);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
Sleep(10000);
return 0;
}
I am trying to figure out how to get unicode back from everything ran in the cmd.exe.

Related

The process ID returned by the CreateProcess function is different from the task manager

#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>
//创建新的控制台
int main(void)
{
BOOL KillProcess();
int ret = 0;
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = TRUE;
ZeroMemory(&pi, sizeof(pi));
WCHAR commandline[] = L"notepad.exe";
//ret = CreateProcess(NULL, commandline, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
//WCHAR commandline1[] = L"notepad.exe";
//WCHAR commandline2[] = L"calc";
ret = CreateProcess(NULL, commandline, NULL, NULL, FALSE, NULL, NULL, NULL, &si, &pi);
//ret = CreateProcess(commandline1, commandline2, NULL, NULL, FALSE, NULL, NULL, NULL, &si, &pi);
if (ret)
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
printf("threadid = %d\n", pi.dwThreadId);
printf("processid = %d\n", pi.dwProcessId);
}
else
{
printf("Start successfully\n");
}
KillProcess();
system("pause");
return 0;
}
BOOL KillProcess()
{
DWORD dwProcessId;
printf("\nPlease input the process ID you want to kill: ");
scanf_s("%u", &dwProcessId);
DWORD dwDesiredAccess = PROCESS_TERMINATE;
BOOL bInheritHandle = FALSE;
HANDLE hProcess = OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId);
if (hProcess == NULL)
return FALSE;
BOOL result = TerminateProcess(hProcess, 0);
CloseHandle(hProcess);
if (result)
{
printf("kill process success\n");
}
else
{
printf("kill process failed\n");
}
return result;
}
After the code is executed, open the calc.exe pi.dwProcessId ,the process ID returned is different from the PID in the task manager,but open it with code notepad.exe normal.I don't know exactly why. I tried many programs, only the PID of Notepad is the same as task manager
I need help from the big guys. Thank you!

strtok_s return incorrect data inside windbg

(Hello Everyone) I have some problem with strtok_s. I wrote this code(x64).
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
BOOL TestMD5(CONST WCHAR* MD5_DATABASE_FILE)
{
HANDLE hFile = INVALID_HANDLE_VALUE;
DWORD FileSize = 0;
DWORD dwReaded = 0;
PBYTE pData = NULL;
BOOL bRead = FALSE;
PCHAR token_string = NULL;
PCHAR context = NULL;
CONST PCHAR delimeter = "\r\n";
hFile = CreateFileW(
MD5_DATABASE_FILE,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hFile == INVALID_HANDLE_VALUE)
{
wprintf(L"Can't open md5 database file: ");
return FALSE;
}
FileSize = GetFileSize(hFile, NULL);
if (FileSize == 0 || FileSize == INVALID_FILE_SIZE)
{
CloseHandle(hFile);
return FALSE;
}
pData = (PBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (SIZE_T)FileSize);
if (pData == NULL)
{
CloseHandle(hFile);
return FALSE;
}
bRead = ReadFile(hFile, pData, FileSize, &dwReaded, NULL);
if (bRead != TRUE || dwReaded != FileSize)
{
HeapFree(GetProcessHeap(), 0, pData);
CloseHandle(hFile);
return FALSE;
}
token_string = (PCHAR)strtok_s(pData, delimeter, &context);
if (token_string == NULL)
{
HeapFree(GetProcessHeap(), 0, pData);
CloseHandle(hFile);
return FALSE;
}
do {
printf("%s\n", token_string);
} while (token_string = (PCHAR)strtok_s(NULL, delimeter, &context));
HeapFree(GetProcessHeap(), 0, pData);
CloseHandle(hFile);
return TRUE;
}
int main(void)
{
WCHAR* MD5_DATABASE_FILE = L"c:\\md5.txt";
TestMD5(MD5_DATABASE_FILE);
}
When I run exe this gives me a incorrect data. Content of md5.txt (DC288E0B39EA16B4E9455F82FF265A67:1213:TestDBG + (\r\n)
output:
D:\repos\TestWindbg\x64\Debug>TestWindbg.exe
DC288E0B39EA16B4E9455F82FF265A67:1213:TestDBG
áááááááááááááááá
I open exe in windbg and I saw while(token_string) is not NULL after first time. But is must?
WinDbg image : "https://i.ibb.co/60nHk5S/Untitled.png"
What is problem? Thanks for reading
Jeffrey Shao - MSFT :Thank you for reply but this is not solution(but I changed my code PBYTE TO PCHAR). The problem is that strtok_s is a string function for this reason you must add NULL byte after buff. Like HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,(SIZE_T)FileSize + 1) #1 for NULL character . HeapAlloc alloc buff size:FileSize and +1 For Null...
Thanks for blabb and Daniel Sęk:
I just change some types of pData and token_string.
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
BOOL TestMD5(CONST WCHAR* MD5_DATABASE_FILE)
{
HANDLE hFile = INVALID_HANDLE_VALUE;
DWORD FileSize = 0;
DWORD dwReaded = 0;
char* pData = NULL;
BOOL bRead = FALSE;
char* token_string = NULL;
PCHAR context = NULL;
CONST PCHAR delimeter = "\r\n";
hFile = CreateFileW(
MD5_DATABASE_FILE,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hFile == INVALID_HANDLE_VALUE)
{
wprintf(L"Can't open md5 database file: ");
return FALSE;
}
FileSize = GetFileSize(hFile, NULL);
if (FileSize == 0 || FileSize == INVALID_FILE_SIZE)
{
CloseHandle(hFile);
return FALSE;
}
pData = (char*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,(SIZE_T)FileSize + 1);
if (pData == NULL)
{
CloseHandle(hFile);
return FALSE;
}
bRead = ReadFile(hFile, pData, FileSize, &dwReaded, NULL);
if (bRead != TRUE || dwReaded != FileSize)
{
HeapFree(GetProcessHeap(), 0, pData);
CloseHandle(hFile);
return FALSE;
}
token_string = strtok_s(pData, delimeter, &context);
if (token_string == NULL)
{
HeapFree(GetProcessHeap(), 0, pData);
CloseHandle(hFile);
return FALSE;
}
do {
printf("%s\n", token_string);
} while (token_string = strtok_s(NULL, delimeter, &context));
HeapFree(GetProcessHeap(), 0, pData);
CloseHandle(hFile);
return TRUE;
}
int main(void)
{
WCHAR* MD5_DATABASE_FILE = L"c:\\md5.txt";
TestMD5(MD5_DATABASE_FILE);
}
Output:
DC288E0B39EA16B4E9455F82FF265A67:1213:TestDBG + (\r\n)

Obtaining handle to key with NtCreateKey/NtOpenKey

PURPOSE
I'm trying to make a function which will create a given sub key in the HKCU registry hive, or open the sub key if it already exists, then return TRUE.
NOTES
Let RegSidPath represent a fully qualified HKCU registry path with an user SID appended to it such as \\Registry\\User\\S-1-5-20-xxxxxx-xxxxxx-xxxxxxxx-1050
Let KeyToCreate represent a specific registry path such as \\Software\\MyCompany\\MySoftware\\MySubKey
CODE
I have the following function:
BOOL CreateHKCUKey(PWCHAR RegSidPath, PWCHAR KeyToCreate) {
UNICODE_STRING uString;
RtlInitUnicodeString(&uString, RegSidPath);
OBJECT_ATTRIBUTES ObjAttributes;
InitializeObjectAttributes(&ObjAttributes, &uString, OBJ_CASE_INSENSITIVE, 0, 0);
HANDLE BaseKeyHandle = NULL;
NTSTATUS Status = NtOpenKey(&BaseKeyHandle, KEY_CREATE_SUB_KEY, &ObjAttributes);
if (NT_SUCCESS(Status) && Status != STATUS_OBJECT_NAME_NOT_FOUND) {
UNICODE_STRING KeyString = { 0 };
do {
PWCHAR NextSubKey = StrStrW((KeyString.Length == 0 ? KeyToCreate : KeyString.Buffer) + 1, L"\\");
DWORD CurrentKeyLength = lstrlenW(KeyToCreate) - lstrlenW(NextSubKey);
PWCHAR CurrentSubKey = PWCHAR(GlobalAlloc(GPTR, CurrentKeyLength + sizeof(WCHAR)));
if (CurrentSubKey != ERROR) {
memcpy(CurrentSubKey, KeyToCreate, CurrentKeyLength * sizeof(WCHAR));
CurrentSubKey[CurrentKeyLength] = UNICODE_NULL;
RtlInitUnicodeString(&KeyString, CurrentSubKey);
OBJECT_ATTRIBUTES KeyAttributes;
InitializeObjectAttributes(&KeyAttributes, &KeyString, OBJ_CASE_INSENSITIVE, &BaseKeyHandle, 0);
HANDLE CurrentHiveEntry = NULL;
Status = NtOpenKey(&CurrentHiveEntry, KEY_CREATE_SUB_KEY, &KeyAttributes);
if (RtlNtStatusToDosError(Status) == ERROR_BAD_PATHNAME) {
InitializeObjectAttributes(&KeyAttributes, &KeyString, OBJ_CASE_INSENSITIVE, &CurrentHiveEntry, 0);
DWORD DefaultDisposition;
Status = NtCreateKey(&CurrentHiveEntry, KEY_CREATE_SUB_KEY, &KeyAttributes, 0, NULL, REG_OPTION_NON_VOLATILE, &DefaultDisposition);
if (NT_SUCCESS(Status)) {
if (StrCmpNW(KeyString.Buffer + uString.Length, KeyString.Buffer, lstrlenW(KeyToCreate) == 0))
return TRUE;
else continue;
} else break;
} else break;
BaseKeyHandle = CurrentHiveEntry;
}
} while (TRUE);
}
NtClose(BaseKeyHandle);
return FALSE;
}
PROBLEM
Whenever the code gets to this part of the function
Status = NtOpenKey(&CurrentHiveEntry, KEY_CREATE_SUB_KEY, &KeyAttributes);
if (RtlNtStatusToDosError(Status) == ERROR_BAD_PATHNAME) {
The return value is always ERROR_BAD_PATHNAME (161) even if the current sub key already exists.
QUESTION
What is the reason, and what am I doing wrong? Is there anything that I've done which is not correct, and how can I fix it?
NTSTATUS CreateKey(PHANDLE KeyHandle, ACCESS_MASK DesiredAccess, PWCHAR RegSidPath, PWCHAR KeyToCreate, PULONG Disposition)
{
UNICODE_STRING ObjectName;
RtlInitUnicodeString(&ObjectName, RegSidPath);
OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, &ObjectName ,OBJ_CASE_INSENSITIVE };
NTSTATUS status = ZwOpenKey(&oa.RootDirectory, KEY_CREATE_SUB_KEY, &oa);
if (0 <= status)
{
ObjectName.Buffer = KeyToCreate;
do
{
ACCESS_MASK Access;
if (KeyToCreate = wcschr(++ObjectName.Buffer, '\\'))
{
ObjectName.Length = (USHORT)RtlPointerToOffset(ObjectName.Buffer, KeyToCreate);
Access = KEY_CREATE_SUB_KEY;
}
else
{
ObjectName.Length = (USHORT)wcslen(ObjectName.Buffer) * sizeof(WCHAR);
Access = DesiredAccess;
}
ObjectName.MaximumLength = ObjectName.Length;
status = ZwCreateKey(KeyHandle, Access, &oa, 0, 0, 0, Disposition);
NtClose(oa.RootDirectory);
oa.RootDirectory = *KeyHandle;
} while (0 <= status && (ObjectName.Buffer = KeyToCreate));
}
return status;
}
and use as
HANDLE hKey;
NTSTATUS status = CreateKey(&hKey, KEY_ALL_ACCESS,
L"\\REGISTRY\\USER\\S-***",
L"\\Software\\MyCompany\\MySoftware\\MySubKey", 0);
Unless you are writing a driver, use RegCreateKeyEx() instead. It handles all the logic of creating intermediate keys for you if they don't already exist. 1 function call, no looping needed.
HKEY hKey;
DWORD dwDisposition;
LONG lRet = RegCreateKeyExW(HKEY_USERS, L"S-1-5-20-xxxxxx-xxxxxx-xxxxxxxx-1050\\Software\\MyCompany\\MySoftware\\MySubKey", 0, NULL, REG_OPTION_NON_VOLATILE, samDesired, NULL, &hKey, &dwDisposition);
if (lRet == 0)
{
...
RegCloseKey(hKey);
}
However, to access the HKEY_CURRENT_USER hive of a specific user, the preferred solution is to use RegOpenCurrentUser() or LoadUserProfile() instead of accessing HKEY_USERS directly:
// impersonate the desired user first, then...
HKEY hRootKey;
LONG lRet = RegOpenCurrentUser(samDesired, &hRootKey);
if (lRet == 0)
{
HKEY hKey;
DWORD dwDisposition;
lRet = RegCreateKeyExW(hRootKey, L"Software\\MyCompany\\MySoftware\\MySubKey", 0, NULL, REG_OPTION_NON_VOLATILE, samDesired, NULL, &hKey, &dwDisposition);
if (lRet == 0)
{
...
RegCloseKey(hKey);
}
RegCloseKey(hRootKey);
}
// stop impersonating...
// obtain token to desired user first, then...
PROFILEINFO profile = {0};
profile.dwSize = sizeof(profile);
profile.dwFlags = PI_NOUI;
if (LoadUserProfile(hToken, &profile))
{
HKEY hKey;
DWORD dwDisposition;
LONG lRet = RegCreateKeyExW((HKEY)profile.hProfile, L"Software\\MyCompany\\MySoftware\\MySubKey", 0, NULL, REG_OPTION_NON_VOLATILE, samDesired, NULL, &hKey, &dwDisposition);
if (lRet == 0)
{
...
RegCloseKey(hKey);
}
UnloadUserProfile(hToken, profile.hProfile);
}
// release token ...

How to access memory mapped file created by parent process in C (Windows)

I created the mapped file and read from view in the parent process. However, I couldn't make the child process access the memory-mapped file. Can you please examine the code below and help me figure it out?
Here is my child process code:
#include <Windows.h>
#include <stdio.h>
#define SIZE 1024 *40
int main(){
HANDLE hLogMap;
char* pView, *start;
if((hLogMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "a.txt")) == NULL)
{
fprintf(stderr,"Unable to open memory mapping: %d\n", GetLastError());
}
if((pView = (char *) MapViewOfFile(hLogMap, FILE_MAP_ALL_ACCESS, 0, 0, SIZE)) == NULL)
{
fprintf(stderr,"Unable to create map view: %d\n", GetLastError());
}
start=pView;
while(pView < start + SIZE){
fprintf(stderr,*(pView++));
pView++;
}
system("pause");
return 1;
}
Here is the parent code:
#include <Windows.h>
#include <stdio.h>
#define BUFF_SIZE 1024 *40
#define FILE_NAME "a.txt"
void exitPrompt(){
system("pause");
exit(0);
}
void main(){
STARTUPINFO si;
PROCESS_INFORMATION pi;
SECURITY_ATTRIBUTES sa;
char* lpCommandLine="child.exe";
HANDLE hFile, hMMap, handle;
char * pFile, * start, *rFile;
SecureZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
SecureZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
if (!CreateProcess(NULL, lpCommandLine, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi))
{
printf("unable to create new process!");
system("pause");
exit(0);
}
else
{
printf("parent is now working!\n");
handle = pi.hProcess;
}
if((hFile = CreateFile( FILE_NAME,
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_WRITE|FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL))
== INVALID_HANDLE_VALUE){
fprintf(stderr,"Unable to open file %s: %d\n",FILE_NAME,GetLastError());
exitPrompt();
}
if((hMMap = CreateFileMapping( hFile,
&sa,
PAGE_READWRITE,
0,
BUFF_SIZE,
NULL))
== NULL){
fprintf(stderr,"Unable to create memory mapping: %d\n",GetLastError());
exitPrompt();
}
if( ( pFile = (char *) MapViewOfFile( hMMap,
FILE_MAP_WRITE,
0,
0,
BUFF_SIZE))
== NULL)
{
fprintf(stderr,"Unable to create map view: %d\n",GetLastError());
exitPrompt();
}
start = pFile;
while(pFile < start + BUFF_SIZE){
*(pFile++) = 'f';
*(pFile++) = 'i';
*(pFile++) = 'g';
*(pFile++) = 'e';
*(pFile++) = 'n';
*(pFile++) = 'g';
*(pFile++) = '_';
*(pFile++) = 10; //in ascii 10 is new line
}
if( ( rFile = (char *) MapViewOfFile( hMMap,
FILE_MAP_READ,
0,
0,
BUFF_SIZE))
== NULL)
{
fprintf(stderr,"Unable to create map view: %d\n",GetLastError());
exitPrompt();
}
rFile=start;
/*while(rFile < start + BUFF_SIZE){
printf("%c",*(rFile));
rFile++;
}*/
WaitForSingleObject(handle,INFINITE);
CloseHandle(handle);
CloseHandle(pi.hThread);
CloseHandle(hMMap);
CloseHandle(hFile);
exitPrompt();
}
The OpenFileMapping expected the mapping name not the file in itself.
if((hLogMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "mapping")) == NULL)
...
When you create the mapping provide the mapping name in the CreateFileMapping.
if((hMMap = CreateFileMapping( hFile,
&sa,
PAGE_READWRITE,
0,
BUFF_SIZE,
"mapping"))
== NULL){
fprintf(stderr,"Unable to create memory mapping: %d\n",GetLastError());
Possibly share the mapping name between your child and parent and use a random name.

Windows 7 DLL Injection

I am trying to inject a dll into an existing process. I am trying to use the CreateRemoteThread LoadLibrary way. I understand how it works, but I cannot figure out why CreateRemoteThread is returning null (failing)... I am on Windows 7 so this may be the reason, but I don't know for sure if it is.. Perhaps I need to set privaleges? My code is below:
#define DLL_NAME "message.dll"
void main()
{
InjectDLL(1288, DLL_NAME);
}
BOOL InjectDLL(DWORD dwProcessId, LPCSTR lpszDLLPath)
{
HANDLE hProcess, hThread;
LPVOID lpBaseAddr, lpFuncAddr;
DWORD dwMemSize, dwExitCode;
BOOL bSuccess = FALSE;
HMODULE hUserDLL;
if((hProcess = OpenProcess(PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION
|PROCESS_VM_WRITE|PROCESS_VM_READ, FALSE, dwProcessId)))
{
dwMemSize = lstrlen(lpszDLLPath) + 1;
if(lpBaseAddr = VirtualAllocEx(hProcess, NULL, dwMemSize, MEM_COMMIT, PAGE_READWRITE))
{
if(WriteProcessMemory(hProcess, lpBaseAddr, lpszDLLPath, dwMemSize, NULL))
{
if(hUserDLL = LoadLibrary(TEXT("kernel32.dll")))
{
if(lpFuncAddr = GetProcAddress(hUserDLL, TEXT("LoadLibraryA")))
{
if(hThread = CreateRemoteThread(hProcess, NULL, 0, lpFuncAddr, lpBaseAddr, 0, NULL))
{
WaitForSingleObject(hThread, INFINITE);
if(GetExitCodeThread(hThread, &dwExitCode)) {
bSuccess = (dwExitCode != 0) ? TRUE : FALSE;
}
CloseHandle(hThread);
}
}
FreeLibrary(hUserDLL);
}
}
VirtualFreeEx(hProcess, lpBaseAddr, 0, MEM_RELEASE);
}
CloseHandle(hProcess);
}
return bSuccess;
}
yes you need privileges before you open the precess, here's the code:
int GimmePrivileges(){
HANDLE Token;
TOKEN_PRIVILEGES tp;
if(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &Token)
{
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid); tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(Token, 0, &tp, sizeof(tp), NULL, NULL);
}
}
An other thing... this code is confusing!!! you need to synthesize!

Resources