Static linking of Bassmod in Delphi
BASS and BASSMOD are very well known freeware libraries for playing XM, IT, WAV and many more sound file formats. They are widely used in keygens and other apps. However, authors only distribute them in a DLL form, there is no LIB file or any other option for linking them statically.
Last week someone resurrected an old thread at Tuts4You and asked how to convert DLL to LIB and link it statically with Delphi. I gave poster the standard answer but he was still running in all sorts of issues. So, can it be done?
Then answer is - yes. But it's not easy.
Note - This article is written for good old Delphi 6/7/2007. Since Delphi XE2 the process should be easier as the linker was improved to use COFF OBJ files directly. However, I don't have those new Delphi versions, so I can't test the claims.
Steps to be taken
I'll make a list of all necessary steps first and then I'll discuss them in details.
- Unpack the DLL properly
- Convert DLL to LIB
- Extract OBJ files from LIB
- Convert COFF OBJ files to OMF OBJ files
- Make those OMF files usable by Delphi
- Write a wrapper unit that works around Delphi limitations
Unpack the DLL properly
First step sounds easy, right? It isn't.
The LIB provided in Tuts4you thread is badly unpacked. Sure, it can work when compiled with MASM. It can be made work with Delphi, but you'll need to hex-edit compiled EXE file first. The reason for this is extremely primitive Delphi compiler/linker. You have no control over PE section names or attributes. It relies on specific section names and always makes code section read-only. But the LIB from tuts4you uses one segment for both code and data and it must have read-write-execute characteristics. Ooops.
So, we need to unpack DLL ourselves using all the standard steps. PE packer is a very simple one, so you can easily find OEP, dump the file, load DLL at different imagebase, find the OEP and make a 2nd dump, use 2 dumps to fix the relocations using Relox and finally restore Import Table using Scylla or ImpRec. Nothing new here.
Once you've unpacked the DLL, you will have to detect section boundaries and create new PE section table. When you're at OEP of bass.dll, check the memory map in some process exploring tool. You'll see the sections and their characteristics nicely:
Now use any PE editor to create appropriate PE section headers:
To make Delphi happy, code section should be named _TEXT and data section should be named _DATA. All the sections you don't need in final OBJ file, should be named ".reloc", ".edata" or similar - Dll2lib will remove them automatically.
Convert DLL to LIB
Well, this step is easy. Use DLL2LIB (google "DLL.To.Lib.v1.42.Full.Retail-DLL2Lib" or get trial version from official site), leave all the default settings and press "Start convert".
Extract OBJ files from LIB
For next few steps you'll need objconv.exe by Agner Fog. It's better to download the latest version, as earlier versions didn't support extracting LIB files.
It's a simple command objconv.exe -lx bass.lib
Convert COFF OBJ files to OMF OBJ files
That's also simple. Just run objconv.exe -fomf bass.obj bass-omf.obj
Make those OMF files usable by Delphi
Delphi imposes quite a few limitations to OBJ file format. Some of them are documented, some of them aren't. So, it's better to rely on special tools made for this purpose, like omf2d.exe by EliCZ.
I'm sure that objconv.exe can do the same, but I'm too lazy to try to figure the right command line parameters. So, just run omd2f.exe bassmod-omf.obj bassmod-omf-d.obj
Note - omd2d.exe will mess up some decorated names from msvcrt.dll, like "??2@YAPAXI@Z". That's not a problem, we'll fix that in the wrapper unit.
Write a wrapper unit that works around Delphi limitations
This is also tough. And again the problems are caused by the primitive Delphi compiler/linker.
Delphi doesn't support direct API calls, all API calls will go through the thunk table. When you try to reference any external API from Delphi code, in reality you'll get address of the thunk code.
For the same reason in Delphi you can't access exported global variables from another DLL.
Unfortunately BASS/BASSMOD uses both direct API calls and global variables from msvcrt.dll. Little bit of clever hacking is required to work around that - you'll have to load msvcrt and other DLLs from unit initialization code and use GetProcAddress to get the required addresses.
So, the implementation part of the unit will look like this:
hKernel32 : dword;
AreFileApisANSI : pointer;
CloseHandle : pointer;
hKernel32 := Windows.LoadLibraryA('kernel32.dll');
AreFileApisANSI := Windows.GetProcAddress(hKernel32, 'AreFileApisANSI');
if AreFileApisANSI = nil then goto fail;
CloseHandle := Windows.GetProcAddress(hKernel32, 'CloseHandle');
if CloseHandle = nil then goto fail;
In addition to that we need to call the original DllMain function to make sure that BASS is initialized properly:
function Bass_DllMain(hinstDLL : dword; fdwReason : dword; lpvReserved : dword) : dword; stdcall; external;
Bass_DllMain(Windows.GetModuleHandle(nil), DLL_PROCESS_ATTACH, 0);
Bass_DllMain(Windows.GetModuleHandle(nil), DLL_THREAD_ATTACH, 0);
As a final touch, in the finalization part of unit we'll have to call DllMain again to make sure all resources are freed properly.
Putting it all together
I already outlined all the steps needed. Anyone with proper skills should be able to replicate them and make his/her own BASS unit.
For those who are lazy - here is the package with Delphi units+obj files + all the intermediate files + compiled projects from BASS/BASSMOD examples to show that it really works.
Unpacking DLLs #1: Tutorial by Mr. eXodia
Unpacking DLLs #2: How to use Relox in few simple steps
Excellent job, 10x
I really like this kind of stuff.
I'm sure it required a lot of research, test and work ... so a big THANK YOU for sharing your knoledge.
Thank you all for kind words. :) This is the kind of project I really like - not too easy but also not too time consuming.
Yes, it required some research. I had to disassemble & debug dcc32.exe to find out what's causing "invalid object file" errors. I also had to create small OMF file parser to analyze individual OBJ data entries - IDA shows just disassembly and hides all the details from user.
You're really of another planet kao :)
Thanks for your great work
by the way do you mind shaing your OMF file parser just in case someone need to investigate future lib ?
@sinan: It's a very simple command-line tool made for a specific purpose. The output from my tool is not useful, if you're not reading OMF specification at the same time. ;)
But I will consider making a proper template for 010Editor some day. ;) That way you'll be able to see the OMF contents in nice UI, edit data and see changes immediately.
I did everything I needed, but I constantly get the same error.
@mustafa: If you could upload those OBJ files somewhere, I will take a look and find out why it doesn't work for you. Most likely you didn't convert it properly and encountered some of Delphi compiler limitations.
Please make Video :)
Single Obj :)
Your OBJ file is corrupted. I don't know how it happened (probably you downloaded or copied it from somewhere).
This is how it looks in hex editor: http://i.imgur.com/89h05Jb.png - see all those "20"? There should be "00" instead.
Get a good OBJ file and try again. If it still doesn't work, send me a link again. :)
Sorry, I sended COFF Obj. This Patch.obj compiled MASM. (Patch Engine : duP2) ( I used to my VB6 project (2 years ago) , now i want to Delphi Project :) )
:) Sorry for bad english. I'dont writing :S I using google translate.
Your OMF file has segment named ".text$mn". If you read my article carefully, you'll see that Delphi needs code segment called "CODE".
So, you should run:
to rename the segment.
to make it usable with Delphi.
Problem solved! :)
I will make a longer post about these problems next week.
very very thanks :D
Thank kao on this topic.
I faced a problem with a simple library?
objconv -lx dTest.lib
objconv -fomf dTest.obj dTesto.obj
omf2d dTesto.obj dTestod.obj
I cannot debug screenshots. ;) Could you please upload the entire project (compiled EXE+Delphi source+OBJ file you are using) - and I will check it.
I see 'external' declaration of RtlUnwind in your screenshot. So, my first guess is that you have problems with imported APIs. So, I suggest that you re-read my article and see the source code on how the imported APIs should be done.
Src + Exe + Obj
I am Waiting...
I loaded your project in OllyDbg and was able to find the problem. It really is a problem with imported APIs.
this jumps to address 523825FF, which is wrong. And this is what should happen instead:
It should call TlsAlloc. :)
So, read again the chapter "Write a wrapper unit that works around Delphi limitations", study my sources and make sure you implement all imports in the way Delphi needs them.
Could you explain how to solve the problem.
Excuse me I'm not much good at english.
Even if you don't speak English, can you look at the source code? It shows you everything you need to do.
Here is a fixed version of your test project (without any error checking!): https://www.mediafire.com/?7rnx6y4kq7l4e7d
Thanks koa. wonderful work.
Will see the source code and back
Now everything is apparent.
the problem was a means of functions addresses.
i'll be back any problem.
DLL APIs Wrapper Generator / Coded by Sn!per X
based on kao's bass.pas wrapper.
- This tool creates a delphi wrapper unit (pas)
from DLL API Imports and Exports to be used later
after converting the DLL from LIB to OBJ.
Tool download link:
@Sn!per X: nice job, it saves quite a lot of manual work. :)
One small issue - Delphi DLLs often import the same API twice. In that case the unit you generate will fail to compile with error message like this:
DLLWrapper_wrapper.pas(108) Error: E2004 Identifier redeclared: 'GetModuleHandleA'
DLLWrapper_wrapper.pas(110) Error: E2004 Identifier redeclared: 'Sleep'
Link from Sn!per X comment: https://www.mediafire.com/?4a0dqbcutops7vl
thanks for the quick reply, i will fix the issue you mentioned previously
also i am planing to add more functions to the tool if to make the work easier.
btw, i am facing a weired obj file:
i followed every step in ur tut and finally the obj wont compile, getting bad object.... inspecting for the problem lead me to:
"All the sections you don’t need in final OBJ file, should be named “.reloc”, “.edata” or similar – Dll2lib will remove them automatically".
opening that obj with HexWorkshop shows in the second line that
there is a section named reloc did not deleted by Dll2lib.
any explanation for that?
- last question what if i have c LIB the question is: is it possible or is there a way to rename its sections ('_TEXT, _DATA') before i processed with converting it?
thanks in advance.
I will look at your DLL sometime next week.
Quick answer to your question - objconv.exe can rename segments. I already explained that it in my other post about OBJ files and Delphi: http://lifeinhex.com/linking-omf-object-files-with-delphi/
You should probably read that one and use 010Editor template to check your object file.
I looked at your DLL. I'm not sure how you generated your OBJ file, but it's definitely wrong.
#1 - when I convert DLL to LIB using Dll2Lib 1.42 Full with all default settings, ".reloc" segment is deleted from OBJ file. ".rdata" and other useful segments are present in OBJ file.
#2 - your DLL has a special ".shr" segment. You should keep it. It's a read/write data segment, so you should rename it properly as well.
Once you have all your segments named properly, Delphi will happily link your OBJ file.
Nice post and thank you.
DLL APIs Wrapper Generator
Here's the link: https://www.mediafire.com/?4a0dqbcutops7vl
Thank you very much for share your knowledge.
Specially, package "For those who are lazy" is useful to me (I'm newbie to Reverse Engineering).
You're welcome! :)
Do you have a copy of DLL.To.Lib.v1.42.Full.Retail-DLL2Lib ?
I was able to find a copy on MEGA: https://mega.nz/file/Jg5G0DQQ#yb1D9mrLDvhB9KV8DiJ7x6PUc-AbDWs7ZMgCNYekbSA
P.S. Google is a great nickname! :)