The code we are about to write will look a tiny bit different from the code we are used to as we are not expecting an executable file to be generated out of it; instead, we will generate a binary file containing a 32-bit procedure assumed to be loaded at a specific address, and that is what we are going to tell the compiler in the first two lines of our patch.asm source file:
; Tell the assembler we are writing 32-bit code
use32
; Then specify the address where the procedure
; is expected to be loaded at
org 0x414d98
Then we will define two labels pointing at addresses outside our procedure. Fortunately, Flat Assembler allows us to define a label at an arbitrary address, like this:
; Assign label to the code where jump
; to fgets is performed
label fgets at 0x414bd8
; We will discuss this label in just a few seconds
label __acrt_iob_func at 0x41b180
After this, we are ready to begin our implementation of the actual shim code as a regular cdecl procedure:
fgets_patch:
; Standard cdecl prolog
push ebp
mov ebp, esp
; Ooops... We need to pass a pointer to
; the stdin as one of the fgets' parameters,
; but we have no idea what this pointer is...
The implementation of the standard C library on Windows provides us with a function for determining pointers to streams based on their number. The function is __iob_func(int). Luckily for us, our victim executable is importing this function from ucrtbased.dll as we can see in the Imports tab of IDA Pro (or in the 010 Editor too):

Although the name differs a bit (prepended with __acrt_), this is the function we are interested in and it is located at the virtual address 0x41b180. This is why we added the __acrt_iob_func label a few moments ago. Visiting that address, we may see that the address of the real __acrt_iob_func would be placed there after dynamic linking:

In order to call this external function for getting the pointer to the stdin stream, we must remember that the stdin number is 0 and that imported functions are called indirectly:
; Get the stdin stream pointer
push 0
call dword[__acrt_iob_func]
; The result is in the EAX register
; Do not forget to fix the stack pointer
; after calling a cdecl procedure
add esp, 4
Now, we are ready to forward the execution flow to fgets() and we do that in the following way:
; Forward the call to fgets()
push eax ; stdin
push 128 ; max input length
push dword [ebp + 8] ; forward pointer to the
; input buffer
call fgets
add esp, 12
; Standard cdecl epilog
mov esp, ebp
pop ebp
ret
The code for the patch is ready. As simple as that (in this particular case). Compiling this code would generate a 35-bytes binary file containing raw binary code. This is the code seen in the hex editor:
