Sunday, December 28, 2008

Oh Huawei you piece of shit!

So I'm home visiting my family for christmas. And what else is there to do after the festivities, food, and presents except roam around the internet being a menace as usual? Nothing, that's what.

Fortunately my grandmother has acquired mobile broadband via 3G as opposed to the dialup I was stuck with previously when I visited. The modem was apparently a Huawei E170 and after connecting it to my MacBook I was offered the opportunity to install a driver for it. And then it "just worked". Don't get me wrong, I've used these modems before, in Linux. And don't get me started on this pile of shit. Ok, you don't have to. I'll do it anyway.

When plugged in, the "modem" presents itself as CD-ROM (for the drivers and software) and a few serial ports. Yay, serial ports. I bet one of those are the modem that you could just open and dial *99# with and it would work right? WRONG! Huawei in their infinite wisdom has decided that the modem needs to be kicked in the balls first before it actually becomes a modem!
So the first thing the official driver does is to send some secret voodoo incantation to the modem that will tell it to stop fucking around and become a modem already!
The device then "restarts" or whatever and a new serial port is discovered which can be used as a normal modem. WHY CAN'T IT JUST BE A MODEM FROM THE GET GO?! This retarted piece of shit always gets me steaming with anger. Who comes up with this shit?!
If the thing just reported itself as a serial port from the get go, we wouldn't have to jump through these fucking hoops made of feces, tears and misery! Jeez!

Ok, so this time I actually had a driver since I was running a supported OS. Great, no more sneakernet for getting some obscure application that will kick the modem in the balls to get it working. No more crying. No more problems. Great! Awesome! Let's get surfing!
It works without a hitch, although it sometimes has problems finding a 3G signal. But that's probably because I'm in the middle of the goddamn forest (Well not really, but the 3G network isn't as well developed here as in the big city where I usually reside).

Then came the tears..... the computer is slow as hell, it hickups, fans go wild and i see the spinning beach ball every now and then. What the hell is going on? I start the Activity Monitor and see to my suprise that it's not Firefox getting stuck on some JavaScript written by a 12 year old again. It's actually the syslogd-process. Although probably the same syslogd as in FreeBSD, this one has probably been blessed by Steve Jobs allmighty. So what is it doing?

Tailing /var/log/system.log reveals the following load of shit spewing on to my screen:

Dec 24 00:09:27 m83-188-193-26 kernel[0]: ::RemovefromQueue: Exit, Return: 448.
Dec 24 00:09:27 m83-188-193-26 kernel[0]: ::RemovefromQueue: Enter, MaciSize = 1020.
Dec 24 00:09:27 m83-188-193-26 kernel[0]: ::RemovefromQueue: Exit, Return: 0.
Dec 24 00:09:27 m83-188-193-26 kernel[0]: ::enqueueData: State:-431226754, Write len:134.
Dec 24 00:09:27 m83-188-193-26 kernel[0]: ::AddtoQueue: Enter, Size = 134.
Dec 24 00:09:27 m83-188-193-26 kernel[0]: ::AddtoQueue: Exit, Return: 134.
Dec 24 00:09:27 m83-188-193-26 kernel[0]: ::RemovefromQueue: Enter, MaciSize = 4096.
Dec 24 00:09:27 m83-188-193-26 kernel[0]: ::RemovefromQueue: Exit, Return: 134.
Dec 24 00:09:27 m83-188-193-26 kernel[0]: ::SetUpTransmit: Tx is empty.
Dec 24 00:09:27 m83-188-193-26 kernel[0]: ::dataReadComplete: dingjianjian = 0


And the goddamn log is 259MB already!
It didn't take long to figure out that this is from the Huawei-driver. If I disconnect it stops.

Searching the web reveals that I'm not alone, it is even discussed on the huawei-forums, on the Apple Support Forum, and some random blog. There is even a proposed solution here, although I don't really want to muck around and change the loglevel for syslogd. No I need another solution. And it doesn't seem Huawei is going to help! Time to put on some gloves and start digging through the crap that is the HuaweiDataCardDriver.

The driver is located in /System/Library/Extensions/HuaweiDataCardDriver.kext. So let's start digging.

First, let's see if there are any obvious log-calls?

$ /System/Library/Extensions/HuaweiDataCardDriver.kext/Contents/MacOS
$ objdump -t HuaweiDataCardDriver | grep -i log
00000000 g *UND* _IOLog
00000000 g *UND* _IOLog

Ah, of course. IOLog is a call exported by IOKit in OS X that allows drivers to log diagnostic information. It is dynamically linked as the driver is loaded into the kernel.
With some help of a very handy tool called otx (get it here). We can get a dissasembly of the driver with symbols and other useful information. Otx requires you to have the Apple Developer Tools installed.


+27 00001e89 a1983c0000 movl 0x00003c98,%eax
+32 00001e8e 89442404 movl %eax,0x04(%esp)
+36 00001e92 c7042410380000 movl $0x00003810,(%esp) ; ::dataReadComplete: dingjianjian = %d\n
+43 00001e99 e862e1ffff calll _IOLog ; <- spam the system.log
; with useless crap
+48 00001e9e bf00100000 movl $0x00001000,%edi

There you are you fucker! Well, one of the fuckers anyway. Now, due to this call to IOLog being dynamically linked there is a relocation entry associated with it which means that this call will be "patched" by the kernel as the extension is loaded into the kernel space. So simply NOP-ing out the call won't do (I tried at first, but it caused a kernel panic). What we want to do it skip the call alltogether, but leave it in place. Do you see it? Look closely at the code.

+36 00001e92 c7042410380000 movl $0x00003810,(%esp) ; ::dataReadComplete: dingjianjian = %d\n

Since we don't want to call IOLog this instruction is useless, we could just ignore it. Or rewrite it. This leaves us 7 whole bytes of space to play with. More than enough for what we need.
So we have 7 bytes we could rewrite to anywhing we want (as long as it fits in 7 bytes). And all we want to do is to skip the IOLog-call. But wait! If the IOLog instruction returns something we need to handle that. Return values are most often put in the EAX-register, so in the case where the call would return something in the EAX register we need to make sure that EAX has the value the rest of the code expects. Let's take a look at the Apple documentation.
void IOLog(
const char *format,
...) __attribute__((format(printf, 1, 2)));
Void? Oh well then, no problem here. Now what should we rewrite those 7 bytes to? How about a relative jump? I happen to know that there is a jump instruction in the x86 instruction set with hexcode 0xEB that takes one signed byte offset from the current EIP as the jump target. (Page 3-333 in Intel(R) Architecture Software Developer's Manual, Volume 2: Instruction Set Reference Manual). We have now used a total of two bytes of our seven bytes, 5 bytes to go. We could just ignore them, but I'll NOP (0x90) them just for the fuck of it. And it becomes prettier if you disassemble the driver again since the remaining 5 bytes of the movl instruction will become just jibberish otherwise.

+32 00001e8e 89442404 movl %eax,0x04(%esp)
+36 00001e92 ebXX jmp +XX ; <- jump from here
+38 00001e94 90 nop
+39 00001e95 90 nop
+40 00001e96 90 nop
+41 00001e97 90 nop
+42 00001e98 90 nop
+43 00001e99 e862e1ffff calll _IOLog
+48 00001e9e bf00100000 movl $0x00001000,%edi ; <- to here

The jump is calculated as XX+EIP and EIP after the instruction is read is pointing to 0x00001e94. We want to jump to 0x00001e9e a total of 10 bytes (0x0A hexadecimal). Done deal, the jump instruction will be "eb0a" in hex. Let's edit the driver.

Open the driver with a hexeditor and search for "c7042410380000" (the original movl-instruction) and change it to "eb0a9090909090". Now disassemble the file again and see what we got.

+32 00001e8e 89442404 movl %eax,0x04(%esp)
+36 00001e92 eb0a jmp 0x00001e9e
+38 00001e94 90 nop
+39 00001e95 90 nop
+40 00001e96 90 nop
+41 00001e97 90 nop
+42 00001e98 90 nop
+43 00001e99 e862e1ffff calll _IOLog
+48 00001e9e bf00100000 movl $0x00001000,%edi
Eureka! We have successfully skipped the call to IOLog. And it doesn't matter if the kernel patches the call using the relocation entries. It simply won't be executed.

One down...
$ otx HuaweiDataCardDriver | grep _IOLog | wc -l
75

Only 74 to go...
Long story short, I wrote a script that would patch the rest for me automatically. Get it here.
Absolutely NO guarantees that it will work. Note that the baseOffset (0x12DC) is hardcoded. Yours might be different. To find your offset, use the i386 offset + text segment offset.
$ lipo -output /tmp/foo -thin i386 HuaweiDataCardDriver
$ file /tmp/foo
/tmp/foo: Mach-O object i386
$ otool -l /tmp/foo
HuaweiDataCardDriver:
Load command 0
cmd LC_SEGMENT
cmdsize 600
segname
vmaddr 0x00000000
vmsize 0x00003c9c
fileoff 732
filesize 15464
maxprot 0x00000003
initprot 0x00000003
nsects 8
flags 0x0
Section
sectname __text
segname __TEXT
addr 0x00000000
size 0x0000290b
offset
732 <- Entry point offset
align 2^1 (2)
reloff 16196
nreloc 390
flags 0x00000000
reserved1 0
reserved2 0
---- SNIP ----

$ lipo -detailed_info HuaweiDataCardDriver
Fat header in: HuaweiDataCardDriver
fat_magic 0xcafebabe
nfat_arch 2
architecture i386
cputype CPU_TYPE_I386
cpusubtype CPU_SUBTYPE_I386_ALL
offset 4096 <- offset to i386 binary inside the universal binary
size 51028
align 2^12 (4096)
architecture ppc
cputype CPU_TYPE_POWERPC
cpusubtype CPU_SUBTYPE_POWERPC_ALL
offset 57344
size 66792
align 2^12 (4096)

732 + 4096 = 4828 (0x12DC).

Finished patch for my driver - Python version - (MD5 of HuaweiDataCardDriver: 18674b88ff0dd405580e39fd9318ed3d)

There you go, now you can fix the driver yourself. But please email Huawei and ask them to fix their crappy software!