FakePDB
FakePDB copied to clipboard
add debug section to exe
referencing the newly created pdb.
a possible way todo this would be to store a kind of template debug section for both x86 and x64 targets and configure it as necessary on the fly before writing it to the dll/exe
typedef struct _IMAGE_DEBUG_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Type;
DWORD SizeOfData;
DWORD AddressOfRawData;
DWORD PointerToRawData;
} IMAGE_DEBUG_DIRECTORY, *PIMAGE_DEBUG_DIRECTORY;
this seems to be the format for a debug directory entry
struct CV_INFO_PDB70
{
DWORD CvSignature;
GUID Signature;
DWORD Age;
BYTE PdbFileName[];
} ;
And this seems to be the format of the inner debug data. source used: http://beefchunk.com/documentation/sys-programming/os-win32/debug/www.debuginfo.com/articles/debuginfomatch.html
Adding to this:
Some of the executables I created a fake .pdb for with IDA labels have the Debug Directory stripped. In this case, it would need to artificially be added to the executable. At the same time, if a fake random PDB id is generated, it would have to be patched in the executable as well.
P.S.: If you know any PE Editors that let you add the Debug Directory on the fly, do let me know.
Sadly I dont know of any pe editors that expose that functionality. A possibly simple outcome would be to expand the executable's last section a certain number of bytes or add a new section at the end of the executable.
I've sorted it out.
- To "create" the Debug Directory, you need to find a spot where to write 0x1C bytes. I recommend using an existing section, at the end of it, rather than creating a new empty section. Why.. because PE Editors, as well as tools that parse the exe will not find the RSDS data (Debug Directory) information if an empty section, last section of the executable, and at the end of the section.
I planted it as I saw in most executables, at the end of .rdata section:
- Then patch the Debug Directory RVA and Size in your PE editor (I use CFF Explorer):
Size is always 0x1C bytes.
- Then patch in the Debug Directory information:
- Then patch in the RSDS information (in my case AddressOfRawData points to where this should be; I put it close by so it's continguous -> 000A6200 + 400000 = 4A6200):
SizeOfData represents the whole block + null terminator. The RSDS section has to have the first 4 characters as 'RSDS', then GUID as the next 16 bytes, then Age as DWORD (01 00 00 00) and lastly the path to the pdb. I chose to use the pdb name directly, no path given. The GUID is also something random (well, a GUID I borrowed from another pdb).
Once you've done all this, ONLY THEN open the executable in IDA and dump the fake .pdb. WHY? Because if you've done your work right, this will happen:
FakePDB/generate pdb (with function labels):
* generating JSON: C:\Users\SunBeam\Desktop\debug\lithtech.exe.json
* generating PDB: C:\Users\SunBeam\Desktop\debug\lithtech.pdb
* symserv EXE id: b'3AE746D9C7000'
* symserv PDB id: b'3810F85E542A40B394ABED5EDF1399C31'
* done
Instead of:
FakePDB/generate pdb (with function labels):
* generating JSON: F:\Games\NOLF\lithtech.exe.json
* generating PDB: F:\Games\NOLF\lithtech.pdb
* symserv EXE id: b'3AE746D9C7000'
* symserv PDB id: b'00000000000000000000000'
* done
Notice how, without a Debug Directory, the symserv PDB id is 0. If you patch your executable beforehand, then FakePDB will read that information and use it when generating the PDB ;)
I've opened a ticket here thinking it was an x64dbg issue not being able to load the .pdb. The x64dbg author mentioned in there that you can force the pdb to be loaded by editing the x32dbg/x64dbg .ini to force load the pdb. Might work without all the work I mentioned above.. to just load the .pdb in.. Thought it'd be interesting for you to read up on it :)
BR, Sun
P.S.: If you know any PE Editors that let you add the Debug Directory on the fly, do let me know.
There is none but CFF Explorer is scriptable, here's a starting point:
filename = GetOpenFile()
pehandle = OpenFile(filename)
local random = math.random
local function uuid()
local template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
return string.gsub(template, '[xy]', function (c)
local v = (c == 'x') and random(0, 0xf) or random(8, 0xb)
return string.format('%x', v)
end)
end
math.randomseed(os.time())
function string.random(length)
local randomString = ''
for i = 1, length do
randomString = randomString .. string.char(math.random(0, 255))
end
return randomString
end
data = "RSDS" .. string.random(16) .. string.char(1, 0, 0, 0) .. "executablenamebuffer.pdb"
AddSectionWithData(pehandle, data, ".debug", IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_DISCARDABLE | IMAGE_SCN_CNT_INITIALIZED_DATA)
As this page points out: https://lordjeb.com/2023/03/10/how-the-hell-things-work-how-windows-debugger-finds-symbols-for-your-code/
The debugger (WindDbg et al) will actually look first for a GUID that is made up of the module timestamp plus the size of the module (which incidentally makes smaller GUID).
So if the module doesn't have a debug section, the debugger will look for the short GUID version,
If you manually add a module pdb to you're symbol store doing:
symstore.exe add /f MyModule.pdb /s C:\ProgramData\Dbg\sym /t AnyName
("C:\ProgramData\Dbg\sym" is the default WinDbg symbol store location, yours might be different.)
It should figure automatically make the shorter GUID version when it sees it doesn't have the other.
Worth a try.
If the tool doesn't. Then you can manually create it using a script.
At any rate do:
!sym noisy
.reload /i /v /f MyModule.dll
It will show the search for the PDB and you'll see the problem if any.