Introduction
Welcome to Intro to C on Windows. This book walks through topics in order. Your actual homework lives in one GitHub Classroom repository (see below)—you start from a base template with boilerplate and grow it chapter by chapter.
Important
GitHub Classroom
- Open the assignment: Accept the GitHub Classroom assignment.
- Sign in to GitHub if prompted, then accept the assignment so a personal repository is created for you.
- That repo is created from a base template project: it includes boilerplate (solution, project files, stubs, etc.) that you will keep using for the entire series.
- Watch for the invitation / acknowledgement email from GitHub (or your course) and complete any steps it asks for.
- Clone your Classroom repository to your machine (HTTPS or SSH, as your instructor prefers) and open that solution in Visual Studio for all labs.
Note
One project, growing over time
As you move through the chapters, add to the same template—new files, functions, and behavior stack on what you already wrote. You do not start a fresh project each week unless your instructor says otherwise. Chapter titles (e.g. “Pointers”, “The PEB and loader”) describe what you are learning next and what to implement inside your template next.
Tip
Submit work after each chapter
When you finish the code for a chapter (or whatever unit your instructor defines), commit your changes with a clear message (e.g.
Complete Ch. 4 – lists and structs) and push tomain(or the branch your instructor uses). That push is what signals “ready for review.” Push regularly so feedback can keep up with your progress—not only at the very end of the course.
Warning
The “Feedback” pull request
The Feedback pull request in your repo is for the instructor to leave comments and suggestions. Do not merge or close it. Keep implementing in your template on
main(or the branch your instructor specifies), then commit and push; new commits onmainare what get reviewed. The Feedback PR is a conversation surface, not a feature branch you complete or merge.
How this book works
- Theory and guidance are written in each chapter: what the Windows APIs and C features are doing and why it matters.
- Code in the book is what you study and reproduce: samples are complete enough to type in or adapt; they are not a second codebase you must hunt for elsewhere.
- Your Classroom template holds the real project: you add
.c/.hfiles, fill inmain, split modules as directed, and wire everything to build. - At the end of each chapter, a short “Implement” section reminds you to build, test, commit, and push—the book does not assume you have a separate “solution repo” to copy from.
What you need
Note
VSCode in your browser
An attempt has been made to enable coding from your browser or even installing the GitHub Classroom VSCode extension. However, there is no guarantee that setup will work perfectly 100% of the time. The recommendation is to use VS2022 Community locally on a Windows host or Windows VM.
- Windows and Visual Studio 2022 Community (or another build setup that can compile Windows C projects with the Windows SDK).
- Many later labs use x64 only: several topics use
__readgsqwordto read the TEB/PEB and will fail to compile in 32-bit (x86) configurations by design. - Basic familiarity with a terminal, Git, and cloning/pushing to GitHub.
How this book is organized
Chapters proceed from numeric types and printf through pointers, structs, the module list, PE basics, threads and file search, and WinINet HTTP. Later optional material covers intrinsics and low-level patterns. The order matches how the course builds concepts—not a folder tree on disk.
Where things live
| Location | Purpose |
|---|---|
| Your cloned Classroom repo | The base template you accepted from GitHub Classroom—this is where you edit, commit, and push all course work. |
This book (IntroToCBook) | Primary reference for explanations and code samples. |
IntroToCBook/ (published site or repo) | The rendered mdBook you read while coding. |
Conventions
- Long samples may be abbreviated with
/* ... */in the book; you still implement the full behavior in your template unless the chapter says otherwise. - Windows types such as
DWORD,ULONG,PCHAR, andUNICODE_STRINGcome fromWindows.hand related headers. ERROR_SUCCESSis a common “success” return for Win32-stylemainexamples.
Next chapter: Windows types—read the theory and samples, then implement them in your template, commit, and push when that stage is done.
Windows types, main, and printf
This chapter covers a minimal Windows C program, Win32 typedefs (INT, UINT, DWORD, …), and printing values with the correct printf format specifiers. Your Classroom template may already contain an empty or stub main—you will replace or extend it using the patterns here.
Note
No separate “lab solution” tree—everything you need to start is in this chapter plus whatever stubs your template provides.
The entry point: main
Every C program needs a place to start. On Windows, when you build a typical console application and link the C runtime library (CRT), the executable does not begin executing your main immediately: the CRT’s startup code runs first (stack, globals, security cookie work, and so on). When that setup is done, the CRT calls your main. That is why you write a function named main: it is the contract between your code and the CRT for this kind of app.
Function signature
The signature is the function’s return type, name, and parameter list—taken together, they tell the compiler how main can be called and what it returns.
Common forms you will see:
int main(void);
int main(int argc, char *argv[]);
In Windows headers, INT is a typedef for a signed integer type (typically the same width as int), and VOID means “no parameters,” so you will often see:
INT main(VOID)
{
/* ... */
return 0;
}
Your template or rubric may use int or INT; both describe the same idea when INT is typedef’d to match int.
Function body
The body is the block in { ... } after the signature. That is where you:
- Declare variables
- Call functions (e.g.
printf) - Use control flow (
if,while, …) returna value to the caller
For main, the return value is passed back through the CRT to the operating system: by convention, 0 means success; non-zero often signals an error (your course may standardize on return 0 or return ERROR_SUCCESS from Windows.h).
Why main (and not something arbitrary)?
The linker + CRT are built to look for a symbol with a specific name for the C console entry path—traditionally main (or wmain for wide-character argv). If you rename it to start_here without also changing startup objects and linker settings, the link step will fail or you will get the wrong entry point. WinMain is a different story: that is the usual entry for Windows subsystem GUI programs, with the CRT calling WinMain instead of main. For this course’s console-style labs, main is the right name.
Variables that hold numbers
Before you print or combine values, you store them in variables: a name, a type, and (usually) an initial value.
On Windows, Windows.h (and related headers) provide typedefs so the same names appear in sample code and in system APIs:
| Typedef (typical use) | Role |
|---|---|
INT | Signed integer (often 32-bit); good for values that can be negative. |
UINT | Unsigned integer; counts and bit patterns that should not be negative. |
LONG, ULONG | Signed / unsigned long-width integers. |
DWORD | Unsigned 32-bit type (“double word”); very common in Win32 APIs. |
Example declarations with initializers:
INT courseNumber = 670;
UINT flags = 0U;
DWORD mask = 0x0000FFFFUL;
The type tells the compiler how much storage to use and how to interpret the bits (especially important for signed vs unsigned). The value after = sets the starting contents; you can change them later with assignment (=, +=, …).
sizeof: how big is this type or variable?
C provides the sizeof operator. It yields the size in bytes of a type or of an expression’s type. You use it when the correct amount of memory matters—buffers, clearing structs, or matching what an API expects—instead of hard-coding numbers like 4 that might be wrong on another platform or for another type.
On a type (parentheses required around the type name):
size_t n = sizeof(UINT); /* how many bytes one UINT occupies */
On a variable or expression (parentheses around the variable are optional but common):
DWORD mask = 0xFF00FF00UL;
printf("mask uses %zu bytes\n", sizeof mask);
sizeof is evaluated at compile time for normal types (not counting run-time variable values—only the type). The result has type size_t; use %zu with printf to print it portably.
Why bother?
- Clarity:
sizeof(buffer)documents “all of this object,” not a magic constant. - Safety: If you change a variable’s type later,
sizeofupdates automatically. - APIs: Many functions want a size in bytes; passing
sizeofavoids off-by-one errors when paired with the right object.
Tip
sizeofdoes not tell you the length of a string in characters—usestrlenfor that.sizeof(char_array)includes the'\0'if the array was declared with fixed size;strlendoes not.
How many bytes? Overflow and wrapping
Exact byte widths are implementation-defined, but on Visual Studio / MSVC for typical Windows x64 console programs, the Win32 typedefs you use early in this course are almost always 32-bit (4 bytes) for integers like INT, UINT, DWORD, LONG, and ULONG. Wider types exist for 64-bit values (e.g. LONG64, ULONG64, DWORD64) and are 8 bytes.
| Idea | Typical size (MSVC, x64 labs in this course) |
|---|---|
CHAR | 1 byte |
SHORT / USHORT | 2 bytes |
INT / UINT / DWORD / LONG / ULONG | 4 bytes |
LONG64 / ULONG64 / DWORD64 | 8 bytes |
Pointers (PCHAR, void *, …) | 8 bytes on x64, 4 on x86 |
To verify on your machine, print them once in a scratch main:
printf("sizeof(INT)=%zu, sizeof(DWORD)=%zu, sizeof(void*)=%zu\n",
sizeof(INT), sizeof(DWORD), sizeof(void *));
What if the value does not fit?
Each unsigned integer type can only hold 0 .. 2^(8×N) − 1 for N bytes. If you assign or compute a value that is too large:
-
Unsigned types: the C standard defines wrapping (reduction modulo
2^(8×N)). The value “folds” and you keep only the low bits—silently. Example: putting300into anunsigned char(often 1 byte) does not store 300; it stores300 % 256. -
Signed types: overflow in arithmetic (e.g.
INT_MAX + 1in a pureintexpression) is undefined behavior in C—you cannot rely on it wrapping like two’s-complement hardware might. Compilers may assume it never happens and optimize in surprising ways.
Practical takeaway: pick a type wide enough for the range you need (DWORD vs DWORD64, etc.), check sizes with sizeof when writing to buffers, and be careful mixing signed and unsigned in expressions (see the next section).
Signed vs unsigned—and mixing them
Signed types can represent negative numbers; unsigned types treat the same storage as non-negative only. If you assign or arithmetic-mix signed and unsigned values, C’s usual arithmetic conversions can widen or reinterpret values in ways that surprise you. Always know which type each variable has before you combine them.
Here is a small program that mixes INT and UINT and then prints the results. An INT is pushed past its maximum positive value by adding a UINT, and a UINT is decremented:
INT mix = 0x7FFFFFFF;
UINT match = 1U;
mix += match;
match -= 2;
printf("mix: (hex)[0x%x] (signed)[%i] (unsigned)[%u] \n", mix, mix, mix);
printf("match: (hex)[0x%x] (signed)[%i] (unsigned)[%u] \n", match, match, match);
The same bits in a variable can be printed three ways: %x (hex), %i (signed decimal), %u (unsigned decimal). The format specifier tells printf how to interpret the value you pass—not the other way around. Passing the wrong combination of type and format is undefined behavior or misleading output. That is why choosing the right type (INT, UINT, DWORD, …) and the matching format matters.
More arithmetic: compound assignment and bit shifts
Extend main with:
- More variables of types like
INT,UINT,DWORDwith given initial values (including hex constants and expressions like1 << 12). - Additional
printflines with correct specifiers for hex vs decimal. - Compound assignment, e.g. updating an
INTwith aUINT(mix += something).
Bit shift examples:
DWORD forty96 = 1 << 12; /* 4096 */
DWORD ten24 = 1 << 10; /* 1024 */
printf format cheat sheet (MSVC / Windows)
Use this table while you choose specifiers (see also Microsoft printf format specification)):
/*
* Common conversions:
* %d or %i signed decimal
* %u unsigned decimal
* %o unsigned octal
* %x / %X unsigned hex (lower/upper)
*
* Size / width hints (examples):
* %lld long long
* %I32d 32-bit int (MSVC)
* %I64d 64-bit int (MSVC)
* %zu size_t (C99; use for strlen result, etc.)
*/
Tip
If output looks wrong, check both the variable type and the format specifier. Mismatches (e.g. printing a
DWORDwith the wrong size or signedness) cause confusing output or undefined behavior.
Implement
- In your Classroom template, ensure
mainhas the correct signature and body for a console CRT app, then add the numeric variables, the signed/unsigned demo, and any bit-shift /printfwork your rubric requires. - Build (correct platform: usually x64 for later chapters, but this chapter may work in either—follow your template).
- Run and verify the printed hex/signed/unsigned lines match your expectations.
- Commit and push when this chapter’s goals are met.
Pointers and addresses
This chapter covers taking addresses, dereferencing, pointer sizes, and how pointer arithmetic depends on the pointed-to type. It ends with parallel arrays of names and function pointers—a simplified picture of an export table.
Address-of and dereference
ULONG ulCourse = 670UL;
PULONG pCourse = &ulCourse;
printf("1-address of ulCourse 0x%p \n", (void*)&ulCourse);
printf("2-address of ulCourse 0x%p \n", (void*)pCourse);
printf("3-address of pCourse 0x%p \n", (void*)&pCourse);
printf("4-dereference pCourse 0x%08lx \n", (unsigned long)*pCourse);
*pCourse = 665;
pCourse holds the address of ulCourse; *pCourse reads or writes the ULONG at that address.
Pointer size vs pointee size
printf("pointers, always the same size : %zu, %zu, %zu, %zu \n",
sizeof(PCHAR), sizeof(PULONG), sizeof(PWORD), sizeof(LPSTR));
printf("what they point to is NOT always the same size : %zu, %zu, %zu, %zu \n",
sizeof(CHAR), sizeof(ULONG), sizeof(WORD), sizeof(CHAR));
On a given platform, all data pointers are typically the same width (e.g. 8 bytes on x64), but sizeof(*p) depends on the type p points to.
Pointer arithmetic and CHAR vs WORD vs ULONG
The same underlying bytes (e.g. a string "yoda") can be walked with different pointer types; incrementing advances by sizeof(*p):
PCHAR/LPSTR: +1 bytePWORD: +2 bytesPULONG: +4 bytes
Then walk a C string byte-by-byte:
PCHAR p = jediMaster;
while ('\0' != *p)
{
printf("char: %c, address : 0x%p \n", *p, (void*)p);
p++;
}
Warning
Casting a
PCHARtoPULONGorPWORDand then incrementing is unsafe unless you know the buffer is large enough and aligned for that access. This exercise illustrates sizes and arithmetic, not production string code.
Parallel arrays (names and function pointers)
Finish with arrays of names and PVOID (or typed) function pointers, then find an index with strcmp—the same logical shape as names / ordinals / functions in a PE export directory.
/* Example shape only—adjust types and count for your rubric */
#define COUNTOF_NAMES 4
PCHAR AddressOfNames[COUNTOF_NAMES] = { "MyA", "MyB", "MyC", "MyD" };
PVOID AddressOfFunctions[COUNTOF_NAMES] = { /* &MyA, &MyB, ... */ };
for (ULONG i = 0UL; i < COUNTOF_NAMES; i++)
{
if (0 == strcmp("MyC", AddressOfNames[i]))
{
printf("found at index %lu @ %p \n", i, AddressOfFunctions[i]);
}
}
Implement
- In your template, implement the pointer demos, the unsafe multi-type walk only if your rubric requires it, and the parallel-array lookup with real stub functions.
- Build and run.
- Commit and push.
Structs, UNICODE_STRING, and doubly linked lists
This chapter covers Windows-style structs, designated initializers, allocation from the process heap, and linking records with LIST_ENTRY (doubly linked list), the same abstraction the loader uses for modules.
Struct types to model
Define types similar to kernel patterns (names may vary; match your rubric):
UNICODE_STRING—Length,MaximumLength,Buffer.STRING_EX(optional) — narrow or wide buffer depending on whether you compile withUNICODE.- A record (e.g.
PROCESS_INFO_EX) that embeds aLIST_ENTRY(e.g.ProcessInfoLinks) plus a module or process name.
Initializing structs
Examples:
STRING_EX Name2 = { 0 };
RtlSecureZeroMemory(&Name3, sizeof(STRING_EX));
STRING_EX Name4 = { .Buffer = s, .MaximumLength = sizeof(s), .Length = sizeof(s) - sizeof((s)[0]) };
Use an RTL_CONSTANT_STRING-style macro for string literals if your instructor provides or asks you to write one.
Heap allocation and list operations
Allocate a list head with HeapAlloc / GetProcessHeap, check for NULL, then:
InitializeListHead(TempHeadList);
bIsEmpty = IsListEmpty(TempHeadList);
Allocate a node struct, fill fields (including a UNICODE_STRING from RTL_CONSTANT_STRING or manual setup), then insert:
InsertTailList(TempHeadList, &(pProcInfo->ProcessInfoLinks));
List helper routines
You need InitializeListHead, IsListEmpty, InsertTailList, RemoveEntryList, and related routines—the same operations the Windows loader uses for module lists. Your template may already ship ListHelpers.h; if not, implement these FORCEINLINE-style functions in your own header (match the classic doubly linked list algorithms).
Note
Printing a
UNICODE_STRINGwithprintfmay require a vendor-specific format (e.g.%wZ) or manual loops overBuffer. If your build complains, printLength/Bufferin a simple loop for debugging.
Implement
- Add the struct definitions and list helpers to headers/sources in your template.
- In
main, allocate head + at least one node, insert, and verifyIsListEmptytoggles as expected. - Build, run, commit, and push.
The PEB, loader data, and module lists
This chapter covers reading the TEB/PEB on x64, reaching PEB->Ldr, and walking InMemoryOrderModuleList with CONTAINING_RECORD.
Warning
This topic requires x64. Use
__readgsqword(FIELD_OFFSET(TEB, ProcessEnvironmentBlock))and reject x86 builds with#errorif your rubric says so. Always select an x64 configuration in Visual Studio.
Get a pointer to the PEB
PPEB PebPtr = (PPEB)__readgsqword(FIELD_OFFSET(TEB, ProcessEnvironmentBlock));
PPEB_LDR_DATA LdrDataPtr = (PPEB_LDR_DATA)PebPtr->Ldr;
You will need winternl.h / ntddk.h-style types your template exposes (TEB, PEB, PEB_LDR_DATA, LDR_DATA_TABLE_ENTRY, etc.)—often via a single NT headers include your instructor supplies.
Walk InMemoryOrderModuleList
The list is circular; stop when CurrentModule == HeadList:
PLIST_ENTRY HeadList = &(LdrDataPtr->InMemoryOrderModuleList);
PLIST_ENTRY CurrentModule = HeadList->Flink;
while (CurrentModule != HeadList)
{
PLDR_DATA_TABLE_ENTRY LdrDataEntry =
CONTAINING_RECORD(CurrentModule, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
/* e.g. wprintf(L"%wZ @ %p\n", LdrDataEntry->BaseDllName, LdrDataEntry->DllBase); */
CurrentModule = CurrentModule->Flink;
}
CONTAINING_RECORD converts a pointer to an embedded LIST_ENTRY into a pointer to the containing LDR_DATA_TABLE_ENTRY.
Optional: hashing and the loader hash table
Advanced follow-on (if assigned): implement LdrpHashUnicodeString-style hashing, LdrHashEntry, and logic that takes the loader lock, walks initialization-order modules, and locates the hash bucket array. Treat this as a second milestone after the basic InMemoryOrder walk works.
Tip
Loader data can change under you—other threads load DLLs. Any serious walk should use
LoaderLock(EnterCriticalSection/LeaveCriticalSection) the way your rubric describes.
Implement
- In your template, implement PEB → Ldr → InMemoryOrder walk and print each module’s base and name (as your instructor specifies).
- Add the optional hash-table exploration only if assigned.
- Build x64, run, commit, and push.
Locating modules by name or hash and reading PE headers
This chapter covers finding a loaded module’s base address from the PEB module list, then parsing PE structures in memory (DOS header, NT headers, optional header, export directory).
FindSystemModule-style API
Design a function (name it as your rubric says) that accepts optional UNICODE_STRING* and/or ULONG* hash:
- If name is NULL, a hash must be supplied; otherwise return
ERROR_INVALID_PARAMETER(or equivalent). - Set an internal
bHashOnlyflag and call intoGetModuleBase.
GetModuleBase and the loader lock
GetModuleBase should:
- Resolve the PEB (e.g.
GetPebPtrusing__readgsqword/ TEB). EnterCriticalSection(PebPtr->LoaderLock)before reading loader lists.- Walk
InMemoryOrderModuleList, comparing either:BaseNameHashValueto the supplied hash (RtlCompareMemory), or_wcsicmpon wide name buffers when matching by name.
LeaveCriticalSectionon every path.
Important
Always release the loader lock on all exit paths. Early
returns during debugging must not skipLeaveCriticalSectionor you can deadlock the process.
PE parsing overview (in-memory)
After you have ModuleBase:
- Validate
IMAGE_DOS_SIGNATUREat the base (DOS header). - Use
e_lfanewto locate the NT headers. - Read the file header and optional header (32 vs 64 depends on Machine).
- Locate the export directory via
IMAGE_DIRECTORY_ENTRY_EXPORTand convert RVAs to VAs with a macro such as:
#define RVA2VA(Type, Base, Rva) ((Type)((DWORD_PTR)(Base) + (DWORD_PTR)(Rva)))
Add DumpDosHeader, DumpFileHeader, DumpExports (or similar) as separate functions that print fields you care about. Link imagehlp.lib if you use ImageHlp helpers (ImageRvaToVa, etc.).
Second pass (optional reinforcement)
If your course assigns a second milestone with the same APIs (e.g. hash-only lookup vs name lookup), treat it as practice—same lock rules, same PE steps—rather than a different codebase.
Implement
- Implement lookup by name and/or by hash per rubric.
- Parse and print DOS + file headers (and exports if required).
- Build x64, test against a known module (e.g.
KERNELBASE.dll), commit, and push.
Threads, heap args, and directory enumeration
This chapter covers passing a structure to a worker thread with CreateThread, and enumerating files with FindFirstFileA / FindNextFileA / FindClose.
Passing a parameter to CreateThread
Allocate a struct (e.g. ARGS) on the heap, fill fields such as a search pattern, pass the pointer as lpParameter, then wait and clean up:
typedef struct _ARGS {
CHAR fileName[MAX_PATH];
} ARGS, *PARGS;
PARGS pArgs = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(ARGS));
if (!pArgs)
return GetLastError();
memcpy(pArgs->fileName, "C:\\Users\\*", strlen("C:\\Users\\*") + 1);
HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)DirSearch, pArgs, 0, NULL);
if (!hThread) {
HeapFree(GetProcessHeap(), 0, pArgs);
return GetLastError();
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
HeapFree(GetProcessHeap(), 0, pArgs);
Warning
Casting
DirSearchtoLPTHREAD_START_ROUTINEonly works ifDirSearchuses the correct return type and calling convention (DWORD WINAPI ThreadProc(LPVOID)). Mismatches cause stack corruption.
Thread procedure: FindFirstFile loop
Inside DirSearch (or your name):
- If
lpParamis non-NULL, read((PARGS)lpParam)->fileName(or use a default pattern). FindFirstFileA→ checkINVALID_HANDLE_VALUE→GetLastErroron failure.do…while (FindNextFileA(...)).FileTimeToSystemTimefor display.- Combine
nFileSizeHigh/nFileSizeLowwithLARGE_INTEGERfor 64-bit size. - Skip
.and..; branch onFILE_ATTRIBUTE_DIRECTORY. FindClosebefore return.
Note
Pick a directory you are allowed to scan.
C:\*orC:\Users\*are teaching examples only.
Implement
- Define
ARGS(or use the template’s typedef), implementDirSearchin its own.cfile if your rubric splits modules. CreateThreadfrommain, wait, free heap.- Build, run, commit, and push.
HTTP with WinINet
This chapter covers a minimal HTTP GET using WinINet (wininet.dll), plus readable errors via FormatMessageW pulling messages from wininet.dll.
Link Wininet.lib and include Wininet.h.
Flow in main
Typical sequence:
InitSession—InternetOpenAwith a user agent andINTERNET_OPEN_TYPE_PRECONFIG.MakeRequest—InternetConnectA, thenHttpOpenRequestA(and related) to obtain anHINTERNETrequest handle.SendRequest— send headers/body; on failure, call your error helper.ProcessResults—InternetReadFile(or similar) in a loop until done.CloseSession—InternetCloseHandlefor each open handle (request, connection, session).
Split these across .c files in your template (HttpApis.c, Errors.c, etc.) if your rubric asks for separation—the book describes the pattern; your repo holds the actual file names.
Formatting WinINet errors
#define BUFFER_SIZE 1024
void CheckLastInetError(LPCWSTR message, DWORD dwLastErr)
{
WCHAR buf[BUFFER_SIZE];
DWORD n = FormatMessageW(
FORMAT_MESSAGE_FROM_HMODULE,
GetModuleHandleW(L"wininet.dll"),
dwLastErr,
0UL,
buf,
BUFFER_SIZE,
NULL
);
if (n)
wprintf(L"%s: %s", message, buf);
}
Tip
Distinguish Win32 errors from
InternetError/ extended WinINet codes your debugger shows—always threadGetLastError(or the API’s documented error path) intoFormatMessagewith the right module when possible.
Note
Samples often target
127.0.0.1:8080. Run a local listener or change host / port to match your lab.
Implement
- Add WinINet session/connect/request/send/read/close in your template.
- Wire
CheckLastInetError(or equivalent) for failed steps. - Build, test against a real or mock server, commit, and push.
Unicode vs ANSI builds
This short chapter explains how UNICODE / _UNICODE affect which printf family you use—and how easy it is to mix wide and narrow APIs by mistake.
One macro, two personalities
#ifdef _UNICODE
#define print wprintf
#else
#define print printf
#endif
int main(void)
{
print(L"this is a cool \n"); /* BUG if _UNICODE is not defined: printf + wide literal */
return 0;
}
When _UNICODE is defined, print becomes wprintf and the wide literal L"..." is consistent. When it is not defined, print is printf but the string is still wide—that is a bug.
Warning
Do not mix wide string literals with
printf, or narrow literals withwprintf, without explicit conversion. Pick one story for your template: A narrow (CHAR,printf,*AAPIs) or B wide (wchar_t,wprintf,*WAPIs,UNICODE_STRING).
Why this matters
Later topics use CHAR and FindFirstFileA alongside UNICODE_STRING and wide system types. Your project properties and #ifdef UNICODE blocks should match—avoid “half Unicode” builds.
Implement
- Align your template with your instructor’s required configuration (ANSI vs Unicode).
- Fix or remove any
printmacro that mixes families incorrectly. - Build, verify, commit, and push.
SIMD intrinsics, hashing, and low-level extras
This chapter is optional and more advanced than the core track. Topics include SSE-style hashing of wide strings, SIMD XOR of buffers, and (if assigned) shellcode or raw bytes embedded in C—only in environments your instructor authorizes.
Warning
These examples use SSE/AVX intrinsics (
immintrin.h,emmintrin.h, etc.). You need CPU + compiler support for the instruction set you use, and you must understand alignment and strict aliasing before using intrinsics in real code.
XOR-fold hash with SSE (wide string)
Pattern:
- Initialize
__m128i hashto zero. - For each 8-WCHAR chunk (16 bytes),
_mm_loadu_si128,_mm_xor_si128intohash. _mm_storeu_si128to aDWORD[4], XOR the four lanes into oneDWORD.- XOR remaining
WCHARs in a scalar loop.
Build a UNICODE_STRING for a module name using a wide char array if you need per-character control (e.g. 'K','E',...,'L','L', UNICODE_NULL).
IntrinsicXOR: 16-byte blocks
Load a 16-byte key into __m128i. For each full block of plaintext: _mm_loadu_si128, _mm_xor_si128, _mm_storeu_si128. Handle a tail smaller than 16 bytes with a byte loop. Print ciphertext as hex for debugging.
Shellcode / hexData arrays (assigned only)
Some courses embed machine code as `unsigned char hexData[] = { 0x48, … }; and jump into it or study it in a debugger. That belongs only in controlled lab VMs.
Caution
Running or adapting shellcode and low-level process memory tricks can violate policy or law outside an authorized class environment. Follow your instructor’s rules only.
Placeholder milestones
If your template includes a stub function (e.g. a single boo()), replace it when the rubric advances—or use it as a scratch entry point for intrinsic experiments.
Implement
- Only if assigned: add SIMD hashing and/or XOR demo
.cfiles to your template, enable required compiler intrinsics flags. - Build, run under a debugger if asked, commit, and push.
CRT-free DLL (optional)
Some courses end with a DLL that minimizes or avoids the C runtime, walks InMemoryOrderModuleList, and compares hashes—similar ideas to earlier chapters but with DllMain constraints.
Note
This is supplementary. Building DLLs involves exports,
DllMain, loader lock re-entrancy, and special linker settings. Follow your instructor’s template and checklist.
Warning
Calling LoadLibrary-style APIs from
DllMainwhile holding the loader lock can deadlock. Do not copy PEB-walking samples intoDllMainwithout understanding what your init path calls.
What to implement (if assigned)
- A
DllMainthat does minimal work (or defers heavy work per instructor guidance). - Optional export
booor similar that performs module enumeration / hash match outside dangerous lock contexts. - Linker settings and CRT avoidance exactly as the Classroom boilerplate specifies.
Implement
- Use only the DLL project and settings from your Classroom template.
- Build the DLL, load/test per rubric, commit, and push.