jief666 / hdimount

Mount apple disk image (HFS, APFS, partitioned or not, compressed, encrypted) via Fuse
GNU General Public License v3.0
33 stars 2 forks source link

Windows getpass doesn't update passLength, nor null-terminates the string #13

Open jjr90 opened 9 months ago

jjr90 commented 9 months ago

I was able to install the DokanSetup.exe from the "msvc" subdir to launch hdimount on Windows.

However, for the same exact DMG file that Linux hdimount can mount correctly, the Windows hdimount is saying "Error: Not encrypted or wrong password."

I think I might've found the issue.

In the Windows version of getpass, the passLength is never updated, so it stays at 0 and keeps writing the console input to index 0. Not only that, but it looks like it never NULL-terminates the string afterward (update: thus the password retains the RETURN at the end.) https://github.com/jief666/hdimount/blob/b1a31ae3ef9f12ce125840d84d3b6494c05ec639/src/CrossPlatform.cpp#L37 [snipped]

jjr90 commented 9 months ago

As a Linux/Mac user, I felt an exe patch would be easier than learning Visual Studio, and it was pretty easy.

Patched hdimount-win10x64 is now able to mount an encrypted DMG with the included Dokany and copy files off of it.

Preface: I'm really glad hdimount exists, as it seems to be the only utility that can mount an encrypted DMG on Windows without extracting an entire copy of everything (a la HFSExplorer, TransMac, dmgwiz, etc.), which isn't always feasible.

I'll give a high-level walkthrough of the patch process, then the exact bytes to patch with a hex editor (e.g. HxD) or dd (commands included), to save others' time.

Note: There seems to be an additional, 3rd bug in this function that we can't fix this way (namely: the BACKSPACE char not being emitted in the way the author expected) and a 4th bug that is caused by our changes but is important to note in case someone implements the changes as described. (See section 2.)

Outline: I. Process walkthrough II. Persisting bug III. Exact bytes to patch and MD5 checksum <------ start here if you just want the patch IV. Example usage and workaround for hung process

I. Process walkthrough

Tools used:

  1. Hopper (Linux/Mac) for analysis : the trial version should work well enough https://www.hopperapp.com/
  2. Defuse online x86 assembler : https://defuse.ca/online-x86-assembler.htm
  3. A hex editor (recommended: HexFiend, GHex, HxD)

Steps:

  1. Download the "hdimount-win10x64" binary from the release tag 1.0__c13f231

  2. Find the Windows getpass routine. Because the symbols are stripped, this would be difficult without tools. In Hopper just do an assembly search for "ReadConsoleA" (Find... > String in Assembly) and the only routine that calls it will show up, which will be the unlabeled getpass.

  3. We need to increment passLength at the end of each loop iteration. To do this, I just wrote over the size check code, because you're not going to buffer-overflow yourself.

Change these assembly lines:

4881FB00010000  cmp  rbx, 0x100
72C0            jb   0x140012193

To read this instead:

48FFC3          inc  rbx
90              nop
90              nop
90              nop
90              nop
EBC0            jmp  0x140012193

You see we are now incrementing rbx insteading of comparing it to 0x100 (256, the size of the pass buffer).

The change of byte '72' to 'EB' for the jump instruction turned it into an unconditional jump, instead of the original jb (jump-if-cmp-below), basically removing the test for the now-nonexistent comparison (cmp).

  1. Now we need to null-terminate the string to remove the RETURN byte, since your password probably doesn't end with a carriage return. To do this, I just wrote over the printf("\n") code, since that's just a cosmetic detail, and we'd rather be able to mount the image.

Change this assembly line (near the bottom of the function):

E8B1B8FFFF  call  0x14000da90

To read this instead:

C6441D0000  mov   byte [rbp+rbx], 0x0

rbp is the address of the pass buffer, and rbx is the passLength. So we are storing 0x0 (null) at the address obtained by adding those two values. The desired instruction is the same length as the original call that we're overwriting, so there's no nop padding needed.

The lea that was loading an argument prior to that instruction is now useless but it's safe to leave it there.

II. Persisting bug

To get backspaces to work now, you would IN THEORY now need to remove the zero-test from the passLength decrement, i.e. to decrement it unconditionally upon a BACKSPACE char. This is because we're now unconditionally incrementing it at the end of each iteration. (Run a few test-cases in your head, you'll see what I mean.)

HOWEVER, even if we patch that, backspace still doesn't work properly. So I'm guessing the BACKSPACE char is not being emitted in the way the author expected.

But that's okay - you just have to type your password correctly without errors.

III. Exact bytes to patch and MD5 checksum

Overwrite the 7 bytes at file offset 71115 (0x115cb), changing 81FB0001000072 to be FFC390909090EB instead.

Overwrite the 5 bytes at file offset 71130 (0x115da), changing E8B1B8FFFF to be C6441D0000 instead.

If you did it correctly, the MD5 checksum of the resulting exe should be 840727596238e203ce7d0714f367f43f.

Linux/Mac commands:

printf '\xff\xc3\x90\x90\x90\x90\xeb' | dd of=hdimount-win10x64.exe bs=1 seek=71115 conv=notrunc
printf '\xc6\x44\x1d\x00\x00' | dd of=hdimount-win10x64.exe bs=1 seek=71130 conv=notrunc

Linux check: md5sum hdimount-win10x64.exe Mac check: md5 -r hdimount-win10x64.exe

IV. Example usage and workaround for hung process

Uninstall any existing Dokany, and install the DokanySetup that's in the hdimount source tree "msvc" folder.

Now if you open Windows Command as Administrator and run: hdimount encrypted.dmg g:

It will ask for your password. If you enter the wrong password, there will be feedback. If you enter the right password, there will be no feedback-- but refresh your disks window, and the mounted disk image should be there as drive G, "DOKANY".

I couldn't figure out how to unmount the disk in the GUI, and Control-C was not working in the Command window.

Beware using mountvol G: /D to unmount it and simply force-closing the window, because the hdimount process will still be running in the background, doing nothing.

Instead, use the -f flag when mounting the image: hdimount -f encrypted.dmg g:

Then if you do Control-C, the process will still hang there, but the disk will have unmounted (refresh your disks window).

Then you can close the window and the process will close (not be stuck in the background).