Modifying IL at runtime (step II+)
In my previous entry on IL modification we looked at the details for inserting a method call with a known (hardcoded) method token. We also used metadata to list the available methods, as a way to avoid this hardcoding.
When listing the methods on a given class, the method signatures were available, but we didn't use them. In this short entry, we'll extend our metadata inspection a little bit by using an existing method's signature to search for another method with a matching signature.
Update: I posted the zipped project.
Using a known signature to find a method
Here is today's Hello.cs. It contains multiple versions of the Log method, with different signatures, and a unique Test with a signature that matches one of the Log methods.
public class Hello
{
public static void Main(string[] prms)
{
Console.WriteLine("main!");
}
public static void Log()
{
Console.WriteLine("log!");
}
public static void Log(string test)
{
Console.WriteLine("log!");
}
public static void Test()
{
Console.WriteLine("test!");
}
}
Last time, when we enumerated the methods on the current class, we only printed their tokens and names out. Below is the modification to this loop, to have it search for a method called "Test" and use its signature to find the "Log" method with the matching signature.
The code refers to a PrintSignature method that cracks signature blobs open and outputs then in a readable format. I'll write about the details of this method sometime soon (when I understand it better), but if you are impatient, you should check out the "metainfo" sample from the "Tools Developper Guide".
ProfilerPrintf("tok:%X\n", rToks[i]);
// Get metadata for this method
mdTypeDef mdClassTok;
wchar_t wszFunctionName[512];
ULONG count = 0;
DWORD dwAttr;
PCCOR_SIGNATURE signature;
ULONG signatureLen;
ULONG ulCodeRVA; // ignored
DWORD dwImplFlags; // ignored
// Get the signature for the method with token rToks[i]
hr = pMetaDataImport->GetMethodProps(rToks[i], &mdClassTok, wszFunctionName, 512, &count, &dwAttr, &signature, &signatureLen, &ulCodeRVA, &dwImplFlags);
if (FAILED(hr)) { goto exit; }
fwprintf(m_pOutFile, L"function name: %s\n", wszFunctionName);
// Look at the signature
PrintSignature(signature, signatureLen, wszFunctionName, pMetaDataImport);
if (wcscmp(wszFunctionName, L"Test") == 0) {
ProfilerPrintf("found Test method\n");
mdMethodDef mdMatchTok;
hr = pMetaDataImport->FindMember(tkClass, L"Log", signature, signatureLen, &mdMatchTok);
if (FAILED(hr)) { goto exit; }
ProfilerPrintf("found Log method with matching signature, token: %X\n", mdMatchTok);
}
...
The IMetaDataImport::FindMember method looks methods up in a class, given a name and a signature blob. We can use the returned method token in the IL method call that we insert at runtime, to avoid hardcoding it.
Here the token that is found is 60 00 00 02, as we can see from the output of the modified profiler:
function name: Main
void Main(class System.String[])
tok:6000002
function name: Log
void Log()
tok:6000003
function name: Log
void Log(class System.String)
tok:6000004
function name: Test
void Test()
found Test method
found Log method with matching signature, token: 6000002
tok:6000005
function name: .ctor
instance void .ctor()
It is probably also possible to use a built signature blob instead of that of an existing method, but I didn't get that far yet.
Calling another class's method
Once you have the token for a method, even it belong to another class, you can easily use it in the IL modification.
If you look at the dis-assembly for Hello.exe (code below), you'll notice that the tokens for methods appear unique in the assembly. This means the call operation only needs a method's token, and doesn't care which class this method belongs to, as long as it remains within the current module.
So when modifying the Main method, if you lookup the Logger class token with FindTypeDefByName(L"Logger", 0, &tkLogger), then find the Log method token with EnumMethodsWithName(..., tkLogger, L"Log", ...) (or other technique of your liking), it is then just a matter of changing ILCode.method_token with the token you found for Log. This way we don't need to hardcode the method token in the generated call IL.
public class Hello
{
public static void Main(string[] prms)
{
Console.WriteLine("main!");
}
}
public class Logger
{
public static void Log()
{
Console.WriteLine("log!");
}
}
Next time, I'll either try to call a method from another assembly or a method that takes some parameters.
Cheers,
Dumky