So finally, a guide for ThinkPad BIOS hacking. Not recommended for anyone who does not have basic grasp of how computers work. If you can't understand something, chances are you're playing with fire by not having enough knowledge. Not saying you're safe if you understood everything at once. Flashing even original BIOS can render your computer unbootable and with ThinkPads, there's no recovery option (aside from some high-tech "soldering" tools). Modified may be even worse. All responsibility for damaging your computer using anything from this page is yours.

Terminology, so that I don't have to repeat it all the time.

When you download a BIOS update from Lenovo, you'll find there something.FL1 (to open the bootable CD version of last generation get perhaps UltraISO). It's the actual BIOS image. There's also .FL2, that's the Embedded controller. The actual BIOS has first obstacle - get PhComp.exe to unpack it from some specific compression only Lenovo uses (inherited from IBM). The BIOS consists of many different modules, which are in last years also compressed.

The decompose the BIOS to individual modules for previous generation, get perhaps phnxdeco - the version a little fixed by me mostly works. If you search enough, you can also find Phoenix BIOS Editor 2.2, something made by Phoenix to allow some quick changes for OEMs - when you open any recent ThinkPad BIOS in it, it will fail, but when that happens, there's already TEMP folder with everything but holes (fixed-location code) extracted, so just grab it and you're done. Be aware that because of write buffering, some modules might be incomplete - if that happens, or you're not sure, try again and compare.

To decompose the BIOS to individual modules for last generation, get phnxsplit written by me (partially based on phnxdeco). It's very dumb tool, just searched the whole BIOS file for module headers and if it finds one, tries to guess compressed or not and then copies / decompresses. Also gives the modules original header, where possible, to allow use of Phoenix tools later.

So you have individual modules. There are some option ROMs for all supported graphics (integrated, discrete, all versions), for ethernet card (boot) etc. There are logos. There are some ACPI things - you can find some tool by M$ to compile/decompile the biggest acpi-related file. Security modules, and much more. However, most things you might be after are located in Bios Code modules. Whitelist checking functions are there. HDD checking functions are there. Processor fan error messages are there (actually directly, these are, to my surprise, not in Strings module). The modules have 27-byte header mentioned earlier, which mostly does not interest you, except four-byte integer at position 0xB. That is module location, and when there are far jumps or procedure calls, that is the address module will be referred with. Be aware it's big endian. Get this number, it should end with zero (in hex). Strip the last zero, and you should get four-digit hex number. Use that as segment when using disassemblers.

Okay, so let's say we search the modules for something known... like... Intel Wifi 3945ABG card has vendor ID 8086 and product ID 4227. Write that in the big endian way (as two-byte integers), and search for 0x86802742. It should get found in one module many times (in *60 series, that is). That's the whitelist... What now, though. You can modify it, yes, to include your card, but then you will have trouble putting the module back, because it might change size after compression - and it won't fit in place. You can mitigate that by changing also some other cards which you don't need. And you will probably succeed... still, it's not the preferred way.

Now strip the 27-byte header. It would make positions wrong. Then let's find the beginning of the whitelist block. You have to guess, there are other cards, from other vendor IDs... but when you have the number, and search for it (don't forget, big endian), you should get a few matches at most. So let's load that module to IDA Disassembler (change extension if it complains) and navigate to that position(s). Press C to tell IDA to try parse that place to instructions. Now it's up to your coding skills... if you think it's some comparing routine, perhaps some register is filled with the whitelist block address and then later being added some (9 for wlan, 4 for wwan) bytes to it, you're probably there.


In T60, when you find one of the two WLAN card checks, you might end up with something like this... or basically in all ThinkPads from *30 series on, it will be very similar. You can find the routine searching for 0x2C01 and 0x2E01 (as you can see in the picture being set to dx) - these two-bytes are pretty unique. Easier than guessing the whitelist block beginning.

In all WLAN/WWAN routines which are responsible for the actual 180x error, you'll find commands "clc" and "stc". There are Clear Carry and Set Carry - effectively set one bit in state register to zero (clear) or one (set). And that's the routines way of returning value. With carry bit set, there's some unsupported card. With carry bit clear, there's either no card, or there's a supported card. The easiest way now is to change the set carry instruction to clear carry.

However... there's more to this. For WLAN, there's one more check - against second whitelist - which controls whether the card is connected to the hardware wireless switch - and if it does not pass there, it will get "radio disable" signal to pin 20. So you need to modify one more check, and that one's a little more tough.

Essentially, I've solved this by replacing the whitelist check by test whether we don't have all zeroes (eax==0 && edx==0; hopefully to ignore when there's no card) and if not, then save the saveApproval.

To be continued.

Preview of things to come: You can look at PhnxPatch, which will find the whitelist checks in your decompressed BIOS modules and patch them. This tool has been used to do all patched BIOSes currently available from me, since around summer 2008, except for some rare cases when I had to do it manually (because the patterns weren't found). However after the patching something needs to be done to match compressed module filesize - I'm using the most straightforward way possible, locate the actual, now unused, whitelist and keep writing random data there until the filesize matches.