DCoder's answer is a good one. To expand somewhat, I most often use DLL injection in the context of forcing an existing process to load a DLL through CreateRemoteThread. From there, the entrypoint of the DLL will be executed by the operating system once it is loaded. In the entrypoint, I will then invoke a routine that performs in-memory patching of all of the locations within the original binary that interest me, and redirects their execution into my DLL via a variety of modifications. If I am interested in modifying or observing the process' interaction with some imported function, then I will overwrite the IAT (Import Address Table) entry for that function and replace it with a pointer to something that I control. If I want to do the same with respect to some function that exists within the binary, I will make some sort of detours-style patch at the beginning of the function. I can even do very surgical and targeted hooks at arbitrary locations, akin to old-school byte patching. My DLL does its business within the individual hooks, and then is programmed to redirect control back to the original process.

DLL injection provides a platform for manipulating the execution of a running process. It's very commonly used for logging information while reverse engineering. For example, you can hook the IAT entry for a given imported operating system library function, and then log the function arguments onto disk. This provides you a data source that can assist in rapidly reverse engineering the target.

DLL injection is not limited to logging, though. Given the fact that you have free reign to execute whatever code that you want within the process' address space, you can modify the program in any way that you choose. This technique is frequently used within the game hacking world to code bots.

Anything that you could do with byte patching, you can do with DLL injection. Except DLL injection will probably be easier and faster, because you get to code your patches in C instead of assembly language and do not have to labor over making manual modifications to the binary and its PE structure, finding code caves, etc. DLL injection almost entirely eliminates the need for using assembly language while making modifications to a binary; the only assembly language needed will be small pieces of code nearby the entrance and exit to a particular hook to save and restore the values of registers / the flags. It also makes binary modification fast and simple, and does not alter cryptographic signatures of the executable that you are patching. (The comment about cryptographic signatures applies to the executable on disk, not in memory; of course, altering the contents in memory would affect a cryptographic signature computed on the altered memory contents.)

DLL injection can be employed to solve highly non-trivial reverse engineering problems. The following example is necessarily vague in some respects because of non-disclosure agreements.

I had a recurring interest in a program that was updated very frequently (sometimes multiple times daily). The program had a number of sections in it that were encrypted on disk after compilation time and had to be decrypted at run-time. The software included a kernel module which performed the run-time encryption/decryption. To request encryption or decryption of a given section, the program shipped with a DLL that exported a function which took as arguments the number of the section and a Boolean that indicated whether the section should be encrypted or decrypted. All of the components were digitally signed.

I employed a DLL injection-based solution that worked as follows:

Create the process suspended.

Inject the DLL.

DLL hooks GetProcAddress in the program's IAT.

GetProcAddress hook waits for a specific string to be supplied and then returns its own hooked version of that function.

The hooked function inspects the return address on the stack two frames up to figure out the starting address of the function (call it Func) that called it.

The hooked function then calls Func for each encrypted section, instructing it to decrypt each section. To make this work, the hooked function has to pass on the calls to the proper function in the DLL for these calls.

After having done so, for every subsequent call to the hooked function, it simply returns 1 as though the call was successful.

Having decrypted all the sections, the DLL now dumps the process' image onto the disk and reconstructs the import information.

After that it does a bunch of other stuff neutralizing the other protections.

Initially I was doing all of this by hand for each new build. That was way too tedious. One I coded the DLL injection version, I never had to undertake that substantial and manual work ever again.

DLL injection is not widely known or used within reverse engineering outside of game hacking. This is very unfortunate, because it is an extremely powerful, flexible, and simple technique that should be part of everyone's repertoire. I have used it dozens of times and it seems to find a role in all of my dynamic projects. The moment my task becomes too cumbersome to do with a debugger script, I switch to DLL injection.

In the spectrum of reverse engineering techniques, every capability of DLL injection is offered by dynamic binary instrumentation (DBI) tools as well, and DBI is yet more powerful still. However, DBI is not stealthy and incurs a serious overhead in terms of memory consumption and possibly performance. I always try to use DLL injection before switching to DBI.