Re: Multiplan for Xenix 3.0 on the Lisa solved.

From: Jason Perkins <perkins.jason_at_email.domain.hidden>
Date: Tue, 7 Mar 2017 22:59:09 -0500

A big thank you to Ray for his help on this!!!

Some notes:

Yes, I have a serial terminal hooked up to my 2/10, and was able to run mp on both the console and the terminal at the same time, with sector 102 0x184 set at both 70 01 and 70 02, with different user accounts. I didn't notice any differences.

The SN brute force script didn't seem to get any hits. I asked Amy Stevenson, Microsoft's archive director, if she had any resources on Xenix Multiplan. In her words "Our Lisa assets are a bit sparse." As far as I know she's still digging around.

To get the bits into my Lisa I use the BLU to write floppies. I used it the other week to write some disks for a Mac 128k - some images someone converted from the Twiggy Mac to work with Sony drives. I got a kick out of seeing a "Steve Sez" dialog on real hardware! To modify the Multiplan .dc42 image I used the Windows version of lisafsh-tool included LisaEm.

Bitsavers has a photo of the MultiPlan install disk here:

I did try a couple permutations of the sn on the disk label, which didn't work.

Now that we have a working MP install I'll make up a demo workbook.

PS: Lyrix has a similar install and serial number check to Multiplan. The only difference is if you bail out of the installer script before it removes the files from disk Lyrix will still run.


On Tue, Mar 7, 2017 at 9:36 PM, Ray Arachelian <ray_at_email.domain.hidden> wrote:

> This is based on the multiplan.dc42 disk image up on bitsavers, if there
> are others floating out there, it might not work.
> Howto:
> To patch the serial number check, run this from bash/zsh, on Linux, *BSD,
> or Mac OS X, cygwin, etc:
> cp multiplan.dc42 multiplan-hax.dc42
> (echo 102; echo 'editsector 0x184 70 01') | lisafsh-tool multiplan-hax.dc42
> alternatively, you can change the 2nd line to do this instead:
> (echo 102; echo 'editsector 0x184 70 02') | lisafsh-tool multiplan-hax.dc42
> Then write multiplan-hax.dc42 image to a physical disk and insert it into
> the machine, and then, as root on Lisa Xenix 3.0, run:
> cd /
> tar xpvf /dev/fd
> cd /once; rm brand
> To start multiplan just type in mp and hit enter.
> I'm not sure what the difference is between 01 and 02. The serial # check
> routine comes back with either 0 for not registered, or 1 or 2.
> Perhaps 1 or 2 are different somehow - maybe one of them allows for more
> users or unlocks extra features. Maybe one of them indicates a serial
> number from an older version that's accepted for upgrades, maybe one
> unlocks "god mode" etc. Not sure at this time, if you guys experiment and
> find out the difference, pls reply in this thread.
> About MultiPlan:
> Multiplan was a spreadsheet that Microsoft written many years ago by
> Douglas Klunder (or perhaps just the p-code interpreter?) during the
> Charles Simonyi era, and ported to many systems such as CP/M, the C64, the
> TRS/80, the Mac and since they wrote Xenix, of course they made a port to
> the Xenix on the Lisa.
> Since there was such a huge plethora of machines in the 80s with all sorts
> of unique architectures and CPUs, it was difficult to port code from one
> system to another, so many companies, Microsoft included, used what today
> would be called a virtual machine (in the JVM sense), but back then it was
> called a p-code machine, most famously after the UCSD Pascal System. To
> port a whole collection of software from one machine to the other just
> required you to rewrite the p-code interpreter.
> (Microsoft said that the "p" stood for "packed" rather than Pascal)
> See:
> -
> -
> -
> -
> work-charles-simonyi/
> -
> -
> -
> -
> Details of the crack for those interested: Like most Xenix software,
> Multiplan comes on a floppy, but in tar format. So you don't get to mount
> the floppy, but rather just do tar xvf /dev/fd to extract it. Somewhere
> in /etc there is an installer script that does this for you. Software
> distributed this way also dumps a few install scripts inside the /once
> directory, which as its name implies is intended to run once.
> In this case there is a binary executable called /once/brand, and a shell
> script called /once/ and a binary mkpath (which makes paths)
> brand -s serial path-to-binary runs some checks on the provided serial
> number, then looks for a string starting with SCOSN inside the binary to
> "brand" it with a serial number,
> is a bit annoying, it's a wrapper around brand, but if you enter
> the wrong serial # it will then uninstall all of multiplan making you start
> over, which is of course really slow on a Lisa.
> If successful, it does offer the useful feature of letting you move
> /usr/lib/mp to another disk, which if you're on a 5MB ProFile, would be
> good.
> brand also takes some other command line options like -q for quiet (which
> invokes it with) but also -m and -n which probably tell you
> where the identify string lives on a serialized copy of the binary, but
> running against the unserialized one, it says "brand: identifier string
> found at D in Memory fault - core dumped" and of course you get a core.
> Against /dev/null, it spits out "brand: identify string not found in
> /dev/null"
> The target of brand is /usr/lib/mp/inter - so why inter? and how does
> inter run? There is a script called mp that sets some variables and then
> runs /usr/lib/mp/inter /usr/lib/mp.
> Remember the p-code interpreter in the history section above? That's what
> that guy is!
> The string it looks for starts with SCOSN - this is found inside brand 4
> times (when using the strings command) (why?) It is found in
> /usr/lib/mp/inter just once.
> Here's what strings on brand looks like:
> %s: serial number checkdigits are not valid
> %s: cannot open %s
> %s: cannot create stream for %s

> %s: location set with multiple flag
> %s: location %D is incorrect for %s
> %s: identify string not found in %s
> %s:ident string multiply found in %s
> %s: identifier string found at %D in %s

> %s: serial number is wrong length in %s
> %s: usage: %s [-s serialnum][-l location][-n][-m] files
> 0123456789ABCDEF
> 0123456789abcdef
> (null)
> *((((( H*
> The interesting thing about inter is that it's not a generic p-code
> interpreter, well, it might be, but it has specific multiplan related
> strings such as a product ID. If you run it, it wants a path to a pair of
> files, one ending in .cod and the other in .dat. The serial number check
> in inter is wholly in inter, and not the interpreted code. I know this
> because I touched */usr/lib/inter/mp/foo.cod* and passed it as a
> parameter to inter. It then complained about /usr/lib/inter/mp/foo.dat
> missing (so it needs both), which I then touched, and again it complained
> about an invalid serial number, but it does print a serial number of
> NUL-000000 Ok, good stuff.
> Now, the target string inside of inter is this guy: *SCOSNMFO9999990000*
> Ok, that's kinda weird, because somehow inter says it's NUL-000000. So
> what if we modify inter and overwrite the strings. Say maybe we put in
> SCOSNAAA000000. Well now, inter spits out the serial number is invalid,
> and it's ZZZ-999999. Interesting. So if you swap A for Z and B for Y and
> continue like that, MFO (that's not a zero) is NUL. Ok, another clue. And
> if you now try other numbers you see that 0 becomes 9 and 1 becomes 8,
> etc. Nice obfuscation there. :)
> But the complaint is in the "check digits" whatever those are. Jason
> tried to brute force them without luck. Those are the extra 4 zeros at the
> end of the serial #.
> Note that last string after (null) with all the spaces and the open
> parens? I highly suspect it's the IV (initialization vector) for the serial
> number and it's check digits. I'm not 100% sure, but it's a good guess.
> Maybe it's true.
> Ok, that was day 1. Got some details but not very far. Meanwhile Jason
> wrote a script that generates serial numbers like SCO-000000 and counts
> up - the brute force attempt. That didn't quite work. So there's more to
> it.
> Day 2
> The next attempt was to try to use a debugger against brand. Great idea,
> and w00t! Oh what wonderful luck, someone forgot to strip the brand
> binary of its symbols! If we run nm brand we can see all the tasty
> symbols! Very useful. The debugger Xenix has is adb. Hmm, I used that
> guy before in Solaris. It's really old it turns out. It's been a while so
> I google adb man page. D'oh all the results I get are for android's adb.
> Grrrr argh. Well, this is because modern unixes such as Solaris have
> replaced adb with mdb (or gdb/ldb). I eventually find an ancient adb man
> page and go to town.
> So how do you use adb? I won't give you a whole lesson on it, just a few
> things.
> If you type in a symbol name, it takes you to that address. If you type
> in ? and hit enter it will disassemble the instruction at that address. If
> you hit enter again, the next opcode is displayed and so on. If you type
> in ?x you get a 2 byte (word) hex dump. Cool.
> If you type in :b you can set a breakpoint and $b will print them out and
> $r will show you the registers.
> If you type in :r -s SCO-123456 /usr/lib/mp/inter it will run the program
> being debugged and it will pass those arguments to it, and since I've set a
> breakpoint, it will break at that location. Yeay! But, if you try to step
> through the program one instruction at a time, instead, it continues to run
> the whole program! D'oh!
> Still, it's very useful though it's got some issues. Being lazy, I
> decided to disassemble all of brand, so I wrote a small script that calls
> adb and types in the right stuff for me like _main to start, then ? and
> then keeps hitting enter. I piped the output of this through my friend tee
> and wrote it to a file. But.... how to get it out of Xenix? Oh, sure
> enough, I dd the file onto a floppy and then run strings against it. Yeay!
> I now possess the fully disassembled copy of brand.
> I do the same thing with inter, but ofc there's no symbols, so no names.
> There's some interesting stuff here. As usual 68K C functions start with
> a link instruction and end with an RTS, and calls start by pushing stuff to
> the stack and then releasing them off the stack pointer (A7). Good stuff.
> So since inter doesn't have symbols I have no idea what's what. So I just
> start to look for RTS and LINK and break up the file into individual
> functions.
> Now looking back at the structure of brand, first comes the magic _start
> code, then _main, then the program's functions, then the linked in libc
> code, then strings and other data. Ok, cool. But... there was an
> optimization, since this is ancient unix (well, Xenix), and all binaries
> are statically linked, (dynamic libs weren't invented yet), and disk space
> was tight, the linker would only link in the libc.a functions that were
> actually used. Good news, so there's less code to look at when trying to
> reverse engineer it.
> So there's interesting stuff like long division, remainder of division,
> fdopen, seek, printf, read/write, etc. All what you'd expect. The hint is
> that the serial number has some sort of division and long integer math -
> maybe 32 bit, maybe 64 bit, whatever. And there's interesting code at a
> function called _verifys and at _untrp (untrap?). So I spend some time
> there until my eyes glaze over trying to figure out the format of the
> serial number and the check digits, but no clue as to what the 3 letters
> should be and how the two parts of the serial number work.
> Day 3
> Ok, let's try something else. I don't know what crazy math was involved,
> and it's soooo booooring. So I compare the disassembly of brand which has
> symbols to that of inter which does not. I eventually figure out the few
> libc functions that brand and inter have in common, but it's enough. The
> big one I care about is _printf. That's the baby that spits out the serial
> number and more importantly "Invalid Serial Number" So how do I find out
> what does that?
> Well, going back to adb against inter, I look at the end of the inter
> binary where strings live and I tediously use ?x until I find what I'm
> looking for - the address offset to "Invalid Serial Number"
> adb says it lives at 0x86BC. and _print (or _printf) is at 0x8282 - but
> the printf in inter is actually a call to trap 0 unlike the one in brand,
> but that's a story I'll skip over telling you how I found out because it's
> long and boring and involves setting random breakpoints. But ok, now I
> know the address for the pointer to the "Invalid Serial Number\0" and I
> know something somewhere calls that and then it's game over. So I look for
> that pointer. I find it. Here's the routine:
> 0x90E: link a6,#0x0
> 0x912: tstb -144.(a7)
> 0x916: moveml #<>,-(a7)
> 0x91A: moveq #0x18,d0
> 0x91C: movl d0,-(a7) push 0x18 whatever that is to stack
> 0x91E: movl #0x86BC,-(a7) <<< push pointer of "Invalid Serial
> Number\0" to stack
> 0x924: moveq #0x2,d0 probably stderr? since that's file descriptor
> 2 always
> 0x926: movl d0,-(a7)
> 0x928: jsr 0x8282 print
> 0x92E: addw #0xC,a7
> 0x932: moveq #0x1,d0
> 0x934: movl d0,-(a7)
> 0x936: jsr 0x79C8
> 0x93C: addql #0x4,a7
> 0x93E: bra 0x942
> 0x942: unlk a6
> 0x944: rts
> Oh yeah, if you know 68K you'll notice the compiler used to generate this
> is retarded. It inserted an extra branch before the unlink for some
> retarded reason. There's a lot of that here, but we'll ignore it.
> Ok, great, so we know anything that calls 0x90E means it failed the serial
> # check and we need to find that, and avoid it. But, there's no direct
> call to this! Instead there's this beauty and it's right after _main:
> _main
> 0x50: link a6,#0xFFFFFFE8
> 0x54: tstb -146.(a7)
> 0x58: moveml #<>,-(a7)
> 0x5C: movl #0x1,-4.(a6)
> 0x64: clrl -8.(a6)
> 0x68: movl #0x2,-12.(a6)
> 0x70: jsr 0x6394
> here:
> *0x76: movl #0x90E,-(a7) routine for invalid serial#?*
> 0x7C: movl #0x8F0,-(a7) routine for something
> 0x82: movl #0x8D2,-(a7) routine for something else
> 0x88: jsr 0x6516
> 0x8E: addw #0xC,a7 12 bytes off
> Interesting, that's the only reference to this. And weird. Well, let's
> see what the function at 0x6516 does.... (I'd guess 0x6394 spits out the
> banner announcing it's multiplan and prints out the serial number and such,
> don't know, didn't look there.)
> And here are those other two something functions:
> 0x8D2: link a6,#0x0
> 0x8D6: tstb -132.(a7)
> 0x8DA: moveml #<>,-(a7)
> 0x8DE: movl #0x8000,0x8694
> 0x8E8: bra 0x8EC
> 0x8EC: unlk a6
> 0x8EE: rts
> 0x8F0: link a6,#0x0
> 0x8F4: tstb -132.(a7)
> 0x8F8: moveml #<>,-(a7)
> 0x8FC: movl #0x1A0D,0x8694
> 0x906: bra 0x90A
> 0x90A: unlk a6
> 0x90C: rts
> They both just write some value at 8694. One writes 1A0D, the other 8000.
> Ok, whatever. Both have the retarded extra useless branch before the
> unlink, but whatever. Maybe those values are a clue for what the 01 or 02
> mean in terms of the serial number.
> So let's see what's at 6516 which gets called from 0x88 right after the
> address of a function that says invalid serial number (and two others) are
> pushed to the stack.
> 0x6516: link a6,#0x0
> 0x651A: tstb -132.(a7)
> 0x651E: moveml #<>,-(a7)
> 0x6522: jsr 0x60C6 some function call is done here, likely the
> SN check
> 0x6528: bra 0x6550
> 0x652C: movl 8.(a6),0x93A6 one of the function calls
> 0x6534: bra 0x656A
> 0x6538: movl 12.(a6),0x93A6 another of the function calls
> 0x6540: bra 0x656A
> 0x6544: movl 16.(a6),0x93A6 and the (3/tu)rd - the "Invalid Serial
> Number" one.
> 0x654C: bra 0x656A
> 0x6550: tstl d0 whoa! a decision is made here!
> 0x6552: beq 0x6544 is it 0?
> 0x6556: cmpl #0x1,d0 is it 1?
> 0x655C: beq 0x6538
> 0x6560: cmpl #0x2,d0 is it 2?
> 0x6566: beq 0x652C
> 0x656A: bra 0x656E
> 0x656E: unlk a6
> 0x6570: rts
> Interesting. So it's a selector function that writes a function pointer
> that got passed to it earlier to a 0x93A6. Could be something.
> And for sure something else will call this address, but...
> First, that TST looks interesting. Let's set a breakpoint there and run
> it with adb. What do I get back in d0? Why I get 0. This is the value
> for an unserialized system.
> Ok, so for shizzle we don't want a zero there.
> (Fast forward past some really boring hex searches to find the location
> for that exact bit of code on the multiplan disk image - why it's at sector
> 102, and that TST.L is at offset 0x184 in that sector and MOVEQ.L to D0 is
> 0x70 and the value to move to register D0 follows it.)
> Let's try to overwrite that TST.L D0 with MOVEQ.L #1,D0 just to see what
> happens. That way it will always return the same value.
> And now we have a winner. Multiplan came up. (That's what the 70 01 in
> the command at the top was: MOVEQ.L #1,D0. If you want 2, we use 70 02.)
> I then send the magic lisafsh-tool commands to Jason and ask him to test
> both 01 and 02 to see what they do different. He reports both work and for
> multiuser (I'm assuming over serial port for the 2nd tty session), so all
> is good with both.
> At this point I lose interest in trying to figure out the rest because I
> have a working MultiPlan that Jason can demo at VCF, so I break out the 25
> year old Auchentoshan, get Schwifty, and declare victory. Wabba Lubba Dub
> Dub!
> Day 5 (or 4 or whatever)
> I decide to vomit out this text like one of Unity's hosts - I thought I'd
> share the knowledge of this incase anyone else wants to play or has similar
> situations with Xenix.
> If you got this far and are not comatose, you're insane like me,
> congrats! Your mission is to find out the difference between 01 and 02 and
> what 1A0D vs 8000 mean.
> The bigger mission for you and your Morty to fully reverse engineer inter
> and figure out the mp.cod/mp.dat and port it to a modern system so you can
> run multiplan on OS X and Linux natively.
> If you gave up at the top, no worries, I'm still happy I was able to help
> you get some Multiplan nostalgia.

Jason Perkins
313 355 0085

Received on 2017-03-08 10:55:32

This archive was generated by hypermail 2.4.0 : 2020-01-13 12:15:14 EST