View Full Version : Finally handling BD+ (?)
Pages :
[
1]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
gioowe
24th August 2008, 14:25
Now that MKBv7 is public I think it's time to concentrate on BD+. I can't imagine that SlySoft is the only one to manage that protection. So how do they do it? Magic? I don't think so! And obviously they didn't reverse engineer PowerDVD - this was the third time that AnyDVD handles BD+ but PowerDVD can't. PowerDVD users had to wait for an update. So, did they kill a player? Could be easier. Did anyone ever tried something like this? What's the current status of BD+?
Let's wind up and find a strategy. How can hardware players help? Has anyone any information about BD+ (beside that marketing crap from Macrovision)?
Only productive posts welcome.
Oopho2ei
24th August 2008, 15:15
Could be an implementation problem in powerdvd. For example you could accidentally do if (some_array[123] == 0x01) {...} and some_array is an array of integers (32 bit). You were supposed to compare one BYTE of the array and not 4. As long as there are enough zeros following the 01 you will never notice the bug. For some discs that is not true so they fail to playback because of that bug. Normally you will notice such mistakes when you reverse engineer the code.
Is there a list of BD+ discs so i could choose which one to buy? :rolleyes:
Edit: here are some:
28 Days Later
28 Weeks Later
August Rush
Cast Away
Dan in Real Life
Die Hard 1-4
Dogma
Fantastic 4 ROSS
From Hell (Region A)
Gattaca
Hitman
I Robot
Independence Day
Man on Fire
Master and Commander
Mr & Mrs Smith
Mr Brooks
No Country For Old Men
Rescue Dawn
Robocop
Ronin
Sleuth
Sunshine
The Day After Tomorrow
The Simpsons Movie
and almost all recent releases: http://bluray.highdefdigest.com/releasedates_historical.html
gioowe
24th August 2008, 17:04
I doubt that PowerDVD can implement their own BD+ code - they surely get a library from Macrovision. Closed source. Everything else would be extremely stupid. So the bug is in Macrovision's library. That would encourage my opinion that SlySoft has something in the loop with hardware players.
Peer van Heuen
24th August 2008, 17:19
I doubt that PowerDVD can implement their own BD+ code - they surely get a library from Macrovision. Closed source. Everything else would be extremely stupid. So the bug is in Macrovision's library. That would encourage my opinion that SlySoft has something in the loop with hardware players.
I'll just say: due to certain properties of BD+, once you're past a certain point, you can handle it pretty much without reversing - BD+ itself then helps you out - on any player ;)
Oopho2ei
24th August 2008, 17:23
I doubt that PowerDVD can implement their own BD+ code - they surely get a library from Macrovision. Closed source. Everything else would be extremely stupid. So the bug is in Macrovision's library. That would encourage my opinion that SlySoft has something in the loop with hardware players.
What makes you think the bug is inside that "library"? It could even be a mastering problem and powerdvd is simply following the specification too closely. Why do you explicitly point at powerdvd? What about the other software players?
gioowe
24th August 2008, 18:07
What makes you think the bug is inside that "library"? It could even be a mastering problem and powerdvd is simply following the specification too closely. Why do you explicitly point at powerdvd? What about the other software players?
What other players? WinDVD? ArcSoft? Or Nero?
Anyway, if AnyDVD can play all BD+ and PowerDVD requires an update - that's something very, very interesting.
gioowe
24th August 2008, 18:10
I'll just say: due to certain properties of BD+, once you're past a certain point, you can handle it pretty much without reversing - BD+ itself then helps you out - on any player ;)
That sounds interesting. So do you say that BD+ can "updates" itself or for instance a player helps out?
Oopho2ei
24th August 2008, 18:19
I am guessing he means that because bd+ enforces a certain memory footprint (e.g. keys, video etc have to be at a certain fix virtual memory address) he can use a memory dump of the virtual machine interpreter and include it in anydvd as precompiled code module. He would only have to make sure the remaining data vm opcode/keys/video content is at the correct address.
Peer van Heuen
24th August 2008, 19:44
I am guessing he means that because bd+ enforces a certain memory footprint (e.g. keys, video etc have to be at a certain fix virtual memory address) he can use a memory dump of the virtual machine interpreter and include it in anydvd as precompiled code module. He would only have to make sure the remaining data vm opcode/keys/video content is at the correct address.
No, that's not it (I thought we were past that silly memory dumping era anyway, that only works for badly written security code :) ). This foot-/fingerprint stuff is only a very minor part of BD+ and what you're saying would be rather trivial.
Actually you'd have to know how BD+ really works, to know what I meant (and even then you probably wouldn't ).
But if I start unraveling that, I'd be finding myself looking for a new job by next week :)
So, I'll better shut up from this point on - should have done that in the first place, sorry... :o
gioowe
24th August 2008, 19:49
I am guessing he means that because bd+ enforces a certain memory footprint (e.g. keys, video etc have to be at a certain fix virtual memory address) he can use a memory dump of the virtual machine interpreter and include it in anydvd as precompiled code module. He would only have to make sure the remaining data vm opcode/keys/video content is at the correct address.
I don't think that. Or you can never create new hardware players or software players that can decode the first released BD+ disks. And if e.g. each certain data has to be at a specific location for all known and future player then there's no point. That's not a footprint.
Oopho2ei
24th August 2008, 20:16
I don't think that. Or you can never create new hardware players or software players that can decode the first released BD+ disks. And if e.g. each certain data has to be at a specific location for all known and future player then there's no point. That's not a footprint.
To be honest i don't understand what you are trying to say. Also i can't follow the way you try to argue that slysoft reverse engineered a hardware player instead of a specific software player to uncover the secrets of BD+. The code which is run on the virtual machine needs to be able to interfere with the decryption process and therefor BD+ imposes some restrictions on the way the memory is organized. But as Peer pointed out in his previous posting those restrictions are rather small.
No, that's not it (I thought we were past that silly memory dumping era anyway, that only works for badly written security code :) ). This foot-/fingerprint stuff is only a very minor part of BD+ and what you're saying would be rather trivial.
It was the best i could think of with my very limited knowledge about BD+. For an open source implementation the VM would have to be fully reverse engineered anyway. I will get a BD+ movie and see what the players do.
Actually you'd have to know how BD+ really works, to know what I meant (and even then you probably wouldn't ).
But if I start unraveling that, I'd be finding myself looking for a new job by next week :)
So, I'll better shut up from this point on - should have done that in the first place, sorry... :o
Thank you for your help.
Oopho2ei
24th August 2008, 20:28
I made some notes of what i was able to find out about BD+ browsing the web. Maybe this helps to clear things up and avoid misunderstandings:
BD+ involves the player running a small virtual machine whenever a BD+ disc is inserted. The virtual machine is a small BD+ content code interpreter that includes 100 lines of code and 60 different instructions. The VM loads additional code from the Blu-ray disc, which can perform one of three functions:
- Transform code, which is used to correct sections of video data which are deliberately corrupted on the video stream part of the disc. If hackers strip out only the video code itself, it will display corrupted sections without the application of transform code. This code may also be used to add digital watermarks to certain discs, which can then be used for forensic investigations of leaked copyrighted material.
- Basic countermeasures, which can check a player that is known to have had its hardware hacked for example, a patch to the drive's firmware and detect and respond to this hack. This allows new discs to disable playback on standalone players that have been hacked, without having to revoke the license keys of the entire model or line of players.
- Advanced countermeasures, which can load native code that runs directly on the Blu-ray player during the playback process. This code can do any number of things, but is designed to provide additional methods of protection should the basic countermeasures fail.
Once the disc is removed from the player, the virtual machine unloads and is no longer present in memory. This returns the Blu-ray player to its initial state, so that even if a machine fails to play a BD+ title, it should still (again, in theory) play previously-released Blu-ray titles that do not have BD+ protection.
According to the Blu-ray Disc Association (BDA), for hackers to successfully attack the BD+ system they would have to first extract the AACS keys (which has already been done quite successfully) and then overcome title-specific security code by reverse-engineering the BD+ virtual machine. While this last bit is definitely an additional challenge, it is by no means impossible.
The announcement of the BD+ spec means that some studios that have been holding off on releasing high-definition content may now be more willing to release titles: it was rumored that Fox Studios had been delaying titles because of the previous AACS cracks. The availability of more titles on Blu-ray than HD DVD (since the latter does not support BD+) could swing the format wars even farther in Blu-ray's direction, especially when combined with events such as the Blockbuster announcement.
More worrying than a resolution of the high-definition format wars, however, is what studios might want to do with the additional powers that BD+ provides them. The ability to run any sort of code in the name of "advanced countermeasures" also brings the power to limit content by other means: timed-release and expiring discs are just some of the possibilities.
If you have any further (useful) information please feel free to post them here.
Oopho2ei
25th August 2008, 19:34
I have had a look at the difference of the output of dumphd and anydvd for a bd+ movie. Using vbindiff i compared the 00001.m2ts:
- 072D4764: C3 7C 9C 9C 95
+ 072D4764: AA 2D 40 8E 4A
- 072D85F1: 0A 0E BC E9 99
+ 072D85F1: 4D EF 4B 6B E5
- 0736ED6A: FB 51 33 7A 48
+ 0736ED6A: B6 F7 78 EC CB
- 0736FA18: 2F 03 43 31 2E
+ 0736FA18: 7E 61 A3 C6 C1
- 073C83B4: EC E9 4D A4 39
+ 073C83B4: 06 F3 AA 7C 59
...
It seems to be always 5 bytes which have to been replaced by the virtual machine. Has anyone had a look at this? Please post your findings.
chavonbravo
26th August 2008, 05:42
Is there a certain file on bd+ discs that contains the bd+ code? How does the vm in hw/sw player extract the information that the disc requires to play?
Edit:
found better explanations for the vm:
http://securityevaluators.com/pdf/spdc_aacs_2005.pdf
http://appft1.uspto.gov/netacgi/nph-Parser?Sect1=PTO1&Sect2=HITOFF&d=PG01&p=1&u=%2Fnetahtml%2FPTO%2Fsrchnum.html&r=1&f=G&l=50&s1=%2220070033419%22.PGNR.&OS=DN/20070033419&RS=DN/20070033419
That last one is a gold mine of information about the vm instructions used. Wish I had a bd+ disc to test with.
Revgen
26th August 2008, 06:37
due to certain properties of BD+, once you're past a certain point, you can handle it pretty much without reversing
Sounds like what I had to go through with my GF. :D
Oopho2ei
26th August 2008, 18:29
found better explanations for the vm:
http://securityevaluators.com/pdf/spdc_aacs_2005.pdf
http://appft1.uspto.gov/netacgi/nph-Parser?Sect1=PTO1&Sect2=HITOFF&d=PG01&p=1&u=%2Fnetahtml%2FPTO%2Fsrchnum.html&r=1&f=G&l=50&s1=%2220070033419%22.PGNR.&OS=DN/20070033419&RS=DN/20070033419
That last one is a gold mine of information about the vm instructions used.
Thank you! That looks really interesting!
Is there a certain file on bd+ discs that contains the bd+ code? How does the vm in hw/sw player extract the information that the disc requires to play?
There is a directory "BDSVM" (at the same level as aacs) containing the files "00000.svm, 00001.svm and 00001.svm". In the subdirectory "backup" there is a copy of those files.
Wish I had a bd+ disc to test with.
After the long header the data looks very random. So it is possibly encrypted (with the media key mabe?). I currently try to figure this out.
Sounds like what I had to go through with my GF. :DWell if you know the instruction set you can simply attach a compatible debugger to her brain when she misbehaves to see what is going on in her mind. :D Maybe peer was trying to tell us that the virtual machine is running a well known (extended) instruction set. :rolleyes:
Btw the DLX processor architecture is very primitive. Usually it is being used in university to demonstrate some basic principles of processor architecture as vhdl/verilog code.
Oopho2ei
26th August 2008, 21:51
A note about the files stored in the BDSVM directory: 00000.svm, 00001.svm and 00002.svm. They all have a size of exactly 20MB and each file consists of exactly 10 sections. Every section has the same size of 2MB.
The first section starts with:
0000:0000 42 44 53 56 4d 5f 43 43 01 00 00 00 01 07 d7 0b
0000:0010 06 00 00 00 00 07 ec e4
followed by:
b3 ff ff fc 00 00 ... lots of zeros .. some data ... zeros
Every other section starts with:
XXX0:0010 42 44 53 56 4d 5f 43 43 01 00 00 00 01 07 d7 0b
XXX0:0010 06 00 00 00 00 1f ff c0
followed by random data.
The beginning of the last file (00002.svm) has a visible pattern (vector table?):
0000:0000 42 44 53 56 4d 5f 43 43 01 00 00 00 01 07 d7 0b
0000:0010 06 00 00 00 00 1f ff c0 00 24 00 00 00 01 01 53
0000:0020 00 00 05 54 00 00 05 58 00 00 0f 84 00 00 1f c8
0000:0030 00 00 2e 54 00 00 3c f4 00 00 4b 94 00 00 59 80
0000:0040 00 00 69 4c 00 00 78 c8 00 00 84 ac 00 00 91 1c
0000:0050 00 00 9c ec 00 00 a8 80 00 00 b4 3c 00 00 c0 5c
0000:0060 00 00 cc 40 00 00 d7 20 00 00 e2 b4 00 00 ee 84
0000:0070 00 00 f8 24 00 01 02 28 00 01 0e e8 00 01 1c c0
0000:0080 00 01 29 44 00 01 35 00 00 01 40 bc 00 01 4c c8
0000:0090 00 01 5a 78 00 01 66 48 00 01 72 cc 00 01 7f 28
0000:00a0 00 01 8a 80 00 01 95 38 00 01 a2 0c 00 01 ae 68
0000:00b0 00 01 bc 2c 00 01 ca 68 00 01 d7 dc 00 01 e4 10
0000:00c0 00 01 f1 70 00 01 fe 1c 00 02 0a 78 00 02 17 38
0000:00d0 00 02 24 20 00 02 32 34 00 02 3d 78 00 02 49 fc
0000:00e0 00 02 56 6c 00 02 60 ac 00 02 6b f0 00 02 79 28
0000:00f0 00 02 85 70 00 02 92 6c 00 02 9f 18 00 02 ac f0
0000:0100 00 02 b9 74 00 02 c5 58 00 02 d1 00 00 02 da 14
0000:0110 00 02 e5 bc 00 02 ef c0 00 02 fb 40 00 03 05 08
0000:0120 00 03 13 94 00 03 20 68 00 03 2b ac 00 03 36 8c
0000:0130 00 03 45 7c 00 03 52 14 00 03 5f 24 00 03 6a e0
0000:0140 00 03 77 f0 00 03 86 7c 00 03 93 78 00 03 9f 0c
0000:0150 00 03 ad 34 00 03 b8 3c 00 03 c3 1c 00 03 cc bc
0000:0160 00 03 da a8 00 03 e4 48 00 03 ed e8 00 03 f9 68
0000:0170 00 04 08 d0 00 04 15 04 00 04 20 ac 00 04 2d 30
0000:0180 00 04 37 d4 00 04 44 6c 00 04 50 dc 00 04 5e 28
0000:0190 00 04 6c 8c 00 04 7a dc 00 04 88 c8 00 04 94 5c
0000:01a0 00 04 a0 2c 00 04 ac 24 00 04 b7 f4 00 04 c2 70
0000:01b0 00 04 ce e0 00 04 da d8 00 04 e8 74 00 04 f5 d4
0000:01c0 00 05 01 e0 00 05 0f 40 00 05 1c 28 00 05 26 90
0000:01d0 00 05 31 c0 00 05 3d 7c 00 05 49 c4 00 05 56 c0
0000:01e0 00 05 61 dc 00 05 6d 84 00 05 7b fc 00 05 86 a0
0000:01f0 00 05 93 38 00 05 9d 8c 00 05 a8 e4 00 05 b2 d4
0000:0200 00 05 bf 30 00 05 cb 50 00 05 d6 1c 00 05 df 80
0000:0210 00 05 ea 10 00 05 f7 84 00 06 02 3c 00 06 0f 88
0000:0220 00 06 1c 84 00 06 26 b0 00 06 32 6c 00 06 3e c8
0000:0230 00 06 4d cc 00 06 5b 54 00 06 68 a0 00 06 73 08
0000:0240 00 06 7d c0 00 06 89 18 00 06 94 20 00 06 9d d4
0000:0250 00 06 a9 90 00 06 b3 08 00 06 bd 5c 00 06 c8 78
...
The following section is a copy of this section. All other sections look random. I don't see any pattern. Btw. "42 44 53 56 4d 5f 43 43" = "BDSVM_CC". I have seen a player parsing the first section by first verifying this string and then comparing [section offset+0x14] with 0x001FFFC0. Note that the next section starts at current section offset+0x00200000.
Edit: If your section headers look different please post them! :)
gioowe
27th August 2008, 16:09
Mine look the same. I think I read something about a 2 MB memory address space for the VM.
Oopho2ei
27th August 2008, 23:13
I have looked at section 2 of 000001.svm. It is decrypted with some sort of stream cipher. I have uploaded the first 64 kb of the decrypted section payload (skipping the 0x17 header bytes): http://uploaded.to/?id=59db0f
Note that the byte order for every dword has be reversed before application (xor) of the bitstream from the stream cipher (big endian <--> little endian conversion). So 0000:0000 c4 4e e6 65 00 01 00 00 02 00 00 00
is probably 0000:0000 65 e6 4e c4 00 00 01 00 00 00 00 02
I will look at the decryption algorithm soon.
Oopho2ei
29th August 2008, 20:46
The VM is at least involved (if not doing it all alone) in the decryption of those sections.
An typical instruction I (32 bit) is decoded like that:
Instruction I: C R[X], R[Y], R[Z]
C = I >> 0x1A
X = ((I >> 0x13) & 0x7C) >> 2
Y = ((I >> 0x0E) & 0x7C) >> 2
Z = ((I >> 0x09) & 0x7C) >> 2
C is the command number (6 bit so 64 possible commands) and X,Y,Z are register numbers (in this case). (>> means 'shift right').
The machine:
- 32 registers each 32 bit long
- probably big endian
- 4MB address space (first 128 bytes are register?)
instruction/command decoding:
cmd00: NOP
cmd01: ADD R[X],R[Y],R[Z] -> R[X] = R[Y] + R[Z]
cmd02: SUB R[X],R[Y],R[Z] -> R[X] = R[Y] - R[Z]
cmd03: MUL R[X],R[Y],R[Z] -> R[X] = R[Y] * R[Z]
cmd04: IDIV R[X],R[Y],R[Z] -> R[X] = unsigned(R[Y]) / R[Z]
cmd05: DIV R[X],R[Y],R[Z] -> R[X] = signed(R[Y]) / R[Z]
cmd06: SHL R[X],R[Y],R[Z] -> R[X] = R[Y] << (R[Z] & 0x1F)
cmd07: SHR R[X],R[Y],R[Z] -> R[X] = R[Y] >> (R[Z] & 0x1F)
cmd08: SAR R[X],R[Y],R[Z] -> R[X] = signed(R[Y]) >> (R[Z] & 0x1F)
cmd09: AND R[X],R[Y],R[Z] -> R[X] = R[Y] & R[Z]
cmd0A: OR R[X],R[Y],R[Z] -> R[X] = R[Y] | R[Z]
cmd0B: XOR R[X],R[Y],R[Z] -> R[X] = R[Y] ^ R[Z]
cmd0C: EQU R[X],R[Y],R[Z] -> if (R[Y] == R[Z]) R[X] = 1 else R[X] = 0
cmd0D: NEQU R[X],R[Y],R[Z] -> if (R[Y] != R[Z]) R[X] = 1 else R[X] = 0
cmd0E: LESS R[X],R[Y],R[Z] -> if (signed(R[Y]) < signed(R[Z])) R[X] = 1 else R[X] = 0
cmd0F: BLW R[X],R[Y],R[Z] -> if (unsigned(R[Y]) < unsigned(R[Z])) R[X] = 1 else R[X] = 0
cmd10: GRE R[X],R[Y],R[Z] -> if (signed(R[Y]) > signed(R[Z])) R[X] = 1 else R[X] = 0
cmd11: ABV R[X],R[Y],R[Z] -> if (unsigned(R[Y]) > unsigned(R[Z])) R[X] = 1 else R[X] = 0
cmd12: LEQ R[X],R[Y],R[Z] -> if (signed(R[Y]) <= signed(R[Z])) R[X] = 1 else R[X] = 0
cmd13: BEQ R[X],R[Y],R[Z] -> if (unsigned(R[Y]) <= unsigned(R[Z])) R[X] = 1 else R[X] = 0
cmd14: GEQ R[X],R[Y],R[Z] -> if (signed(R[Y]) >= signed(R[Z])) R[X] = 1 else R[X] = 0
cmd15: AEQ R[X],R[Y],R[Z] -> if (unsigned(R[Y]) >= unsigned(R[Z])) R[X] = 1 else R[X] = 0
cmd16: JMP R[Y] -> PC = R[Y]
cmd17: CALL R[Y] -> R[0x1F] = PC; PC = R[Y]
cmd18: ADDE R[X],R[Y],C -> C = sign_extend(I & 0xFFFF); R[X] := R[Y] + C
cmd19: ADD R[X],R[Y],C -> C = I & 0xFFFF; R[X] := R[Y] + C
cmd1A: SUBE R[X],R[Y],C -> C = sign_extend(I & 0xFFFF); R[X] := R[Y] - C
cmd1B: SUB R[X],R[Y],C -> C = I & 0xFFFF; R[X] := R[Y] - C
cmd1C: SHL R[X],R[Y],C -> C = I & 0x1F; R[X] = unsigned(R[Y]) << C
cmd1D: SHR R[X],R[Y],C -> C = I & 0x1F; R[X] = unsigned(R[Y]) >> C
cmd1E: SAR R[X],R[Y],C -> C = I & 0x1F; R[X] = signed(R[Y]) >> C
cmd1F: AND R[X],R[Y],C -> C = I & 0xFFFF; R[X] = R[Y] & C
cmd20: OR R[X],R[Y],C -> C = I & 0xFFFF; R[X] := R[Y] | C
cmd21: XOR R[X],R[Y],C -> C = I & 0xFFFF; R[X] := R[Y] ^ C
cmd22: EQU R[X],R[Y],C -> C = sign_extend(I & 0xFFFF); if (R[Y] == C) R[X] = 1 else R[X] = 0
cmd23: NEQU R[X],R[Y],C -> C = sign_extend(I & 0xFFFF); if (R[Y] != C) R[X] = 1 else R[X] = 0
cmd24: GRE R[X],R[Y],C -> C = sign_extend(I & 0xFFFF); if (signed(C) > signed(R[Y])) R[X] = 1 else R[X] = 0
cmd25: ABV R[X],R[Y],C -> C = I & 0xFFFF; if (unsigned(C) > unsigned(R[Y])) R[X] = 1 else R[X] = 0
cmd26: LESS R[X],R[Y],C -> C = sign_extend(I & 0xFFFF); if (signed(C) < signed(R[Y])) R[X] = 1 else R[X] = 0
cmd27: BLW R[X],R[Y],C -> C = I & 0xFFFF; if (unsigned(t0) < unsigned(R[Y])) R[X] = 1 else R[X] = 0
cmd28: GEQ R[X],R[Y],C -> C = sign_extend(I & 0xFFFF); if (signed(C) >= signed(R[Y])) R[X] = 1 else R[X] = 0
cmd29: AEQ R[X],R[Y],C -> C = I & 0xFFFF; if (unsigned(C) >= unsigned(R[Y])) R[X] = 1 else R[X] = 0
cmd2A: LEQ R[X],R[Y],C -> C = sign_extend(I & 0xFFFF); if (signed(C) <= signed(R[Y])) R[X] = 1 else R[X] = 0
cmd2B: BEQ R[X],R[Y],C -> C = I & 0xFFFF; if (unsigned(C) <= unsigned(R[Y])) R[X] = 1 else R[X] = 0
cmd2C: JMP C -> C = PC + signed(unsigned(I) << 6) >> 6; PC = C
cmd2D: CALL C -> C = PC + signed(unsigned(I) << 6) >> 6; R[0x1F] = PC; PC = C
cmd2E: JZ R[Y], C -> C = PC + sign_extend(I & 0xFFFF); if (R[Y] == 0) PC = C
cmd2F: JNZ R[Y], C -> C = PC + sign_extend(I & 0xFFFF); if (R[Y] != 0) PC = C
cmd30: STR R[X] -> R[X] = I << 0x10
cmd31: LBSE R[X],R[Y] -> R[X] = sign_extended(mem[((R[Y] + sign_extended(I & 0xFFFF)) & 0x3FFFFF) ^ 0x03] & 0xFF)
cmd32: LB R[X],R[Y] -> R[X] = mem[((R[Y] + sign_extended(I & 0xFFFF)) & 0x3FFFFF) ^ 0x03] & 0xFF
cmd33: LWSE R[X],R[Y] -> R[X] = sign_extended(mem[((R[Y] + sign_extended(I & 0xFFFF)) & 0x3FFFFE) ^ 0x02] & 0xFFFF)
cmd34: LW R[X],R[Y] -> R[X] = mem[((R[Y] + sign_extended(I & 0xFFFF)) & 0x3FFFFE) ^ 0x02] & 0xFFFF)
cmd35: LDW R[X],R[Y] -> R[X] = mem[(R[Y] + sign_extended(I & 0xFFFF)) & 0x3FFFFC]
cmd36: MOVB R[X],[R[Y]+C] -> C = sign_extended(I & 0xFFFF); mem[((R[Y]+C) & 0x3FFFFF) ^ 0x03] = (unsigned char) R[X]
cmd37: MOVW R[X],[R[Y]+C] -> C = sign_extended(I & 0xFFFF); mem[((R[Y]+C) & 0x3FFFFE) ^ 0x02] = (unsigned int16) R[X]
cmd38: MOVDW R[X],[R[Y]+C] -> C = sign_extended(I & 0xFFFF); mem[(R[Y]+C) & 0x3FFFFC] = (unsigned int32) R[X]
cmd39: TRAP C -> trap[immediate]
cmd3A: SWDC R[Y] -> watch_cat_counter = R[Y];
cmd3B-cmd3F: NOP
Notation:
PC = program counter
C = a constant (part of instruction word)
I = the instruction word (uint32)
watchcat_counter = some counter which is set by cmd3A and decremented by every instruction by a command specific value. As long as the counter is > 0 program execution continues.
mem[] = the virtual machine memory
R[X] = register number X (uint32 R[32])
sign_extend(V) = MSB(V) ? 0xFFFF0000 | V : V;
MSB() = get the most significant bit
signed() = make it a signed integer (int32)
unsigned() = make it an unsigned integer (uint32)
Note that by the time the command handler is called the program counter (pc) has already been incremented by 4 (pointing at the next instruction).
It would be great if someone could start implementing the command interpreter (open source please). Please feel free to ask for more details. I will post the purpose of the remaining commands soon.
Oopho2ei
30th August 2008, 11:59
I have added some more commands. It is becoming increasingly difficult to find good names eg. BEQ (for 'below or equal' inintel terms) is usually used for 'branch if equal'. If you can think of better names/abbreviations you are also welcome to post your ideas. :)
Oopho2ei
31st August 2008, 02:39
I can see that things are getting more complicated and my C-like expressions become quite ugly. A lot of people are following this thread but no one dares to say something. So i decided to give a very small example of how this works.
You have probably seen the BDSVM folder on your blue rays with BD+ protection. Now open the 00000.svm in a hex editor (like khexedit) and scroll down to address around 0x1000. You are looking now at some of the instructions which are executed by the virtual machine at the very beginning.
...
0000:1000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000:1010 00 00 00 00 00 00 00 00 b0 00 00 40 e0 3d 00 08
0000:1020 e3 fd 00 04 d4 20 00 00 e0 1d 00 00 d7 e0 11 54
0000:1030 e3 e0 11 50 b4 01 9b 0c d7 e0 11 58 e3 e0 11 50
0000:1040 e0 20 00 04 d7 fd 00 04 d4 3d 00 08 67 bd 00 1c
0000:1050 e4 00 00 10 b3 ff ff fc 00 00 00 00 bc 00 ff fc
0000:1060 bc 01 ff fc bc 02 ff fc bc 03 ff fc bc 04 ff fc
0000:1070 bc 05 ff fc bc 06 ff fc bc 07 ff fc bc 08 ff fc
0000:1080 bc 09 ff fc bc 0a ff fc bc 0b ff fc bc 0c ff fc
0000:1090 bc 0d ff fc bc 0e ff fc bc 0f ff fc bc 10 ff fc
0000:10a0 bc 11 ff fc bc 12 ff fc bc 13 ff fc bc 14 ff fc
0000:10b0 bc 15 ff fc bc 16 ff fc bc 17 ff fc bc 18 ff fc
0000:10c0 bc 19 ff fc bc 1a ff fc bc 1b ff fc bc 1c ff fc
0000:10d0 bc 1d ff fc bc 1e ff fc bc 1f ff fc c3 a0 00 00
0000:10e0 67 bd 11 5c c3 80 00 00 67 9c 10 00 d7 bd 00 00
...
Now let's see how this works:
1. Fetch: Execution starts with PC = 0x1000. Because we have a 0x18 byte long header this translates to the start address 0x1000+0x18=0x1018. If we look at the section from 00000.svm posted above we see our first instruction: "b0 00 00 40".
2. Decode: If we look at the highest 6 bits of the leftmost byte of this instruction. The binary represenation of 0xB0 = 0b10110000. The highest 6 bits are 0b101100 which is 0x2C. (Instead of converting it binary representation and back we could have simply divided by 4: 0xB0/4 = 0x2C.) Now if we lookup 0x2C in the table i posted above you will see that this is the encoding of the command "JMP C".
3. Execute: Now lets look at what this instruction does. The corresponding entry of the table says: "C = PC + signed(unsigned(I) << 6) >> 6; PC = C". So we first calculate a constant C = PC + signed(unsigned(I) << 6) >> 6. What the second part does is simply setting the highest 6 bits of our instruction I = 0xB0000040 to the value of the 7th highest bit. This is done here by first doing a logic shift left and then an arithmetic shift right. (The expressions "signed" and "unsigned" are used to distinguish between logic and arithmetic shift because C handles the shift operator differently of signed and unsigned values.) Now in our case the 7th highest bit is 0 so we can just overwrite the highest 6 bits with zeros. The result is C = PC + 0x40.
Now when the command dispatcher runs it already increments the value of the program counter (pc) by 4 (4 bytes is the length of every instruction) so that it is pointing at the next instruction even before the command handler (in this case for "jmp c") is executed. So in this case our program counter is PC = 0x1000 + 4 = 0x1004. Now according to the description "PC = PC + signed(unsigned(I) << 6) >> 6" our new program counter is PC = 0x1004 + 0x40 = 0x1044.
4. Write back: PC = 0x1044
Now lets look at the next instruction.
1. Fetch: At 0x1044+0x18 we have "bc 00 ff fc"
2. Decode: 0xBC / 4 = 2F so we have a "JNZ R[X],C". Looking in the instruction table we find the description "C = PC + sign_extend(I & 0xFFFF); if (R[Y] != 0) PC = C".
3. We first calculate C. Therefor we overwrite the 16 highest bits with the value of the 17th highest bit (this is what this expression simply does). The 17th highest bit is 1 so we result is C = PC + 0xFFFFFFFC = 0x1044. Now we see that R[Y] is checked for being different from zero. According to the description Y = ((I >> 0x0E) & 0x7C) >> 2. This expression reads the 5 bits directly following the command number (highest 6 bits) from the instruction. In our case this is Y = 0 so we have to check register 0 (R[0]) for being zero. In this case it is really zero because the virtual machine has just been started and we haven't manipulated anything except the program counter so far. Consequently "PC = C" is not being executed and we can forget C and proceed with the next instruction.
4. Write back: nothing....
Execution proceeds at 0x1048 ...
You will find more details scattered across the postings of this thread. Feel free to ask if you have questions. Explaining things to other people really helps finding bugs. :)
bmnot
31st August 2008, 13:05
Hi Oopho2ei,
I've been following the threads dealing with HDDVD/BluRay-Decryption on doom9 since the beginning and I must say you're doing an truly amazing job – despite the limited interest and resonance lately...
As probably many other members, I really appreciate your work on this topic (one reason is that there's no AnyDVD HD for my preferred operating systems...). :thanks:
Regarding the topic...
As already stated somewhere in the SlySoft forums, the BD+ virtual machine (obviously) has not much in common with Java (no stack operands, no objects). It looks more like some simple MIPS dialect.
Therefore - up to this point - it's pretty straightforward to implement an interpreter. Actually, you already more or less posted the source code...
It appears, there are at least three types of instructions (just another notation of what was already posted):
Type1 [6bit command][5bit Operand X][5bit Operand Y][5bit Operand Z][11 bit unused]
Type2 [6bit command][5bit Operand X][5bit Operand Y][16bit constant] (5bit constant in shift operations)
Type3 [6bit command][26bit constant]
Regarding the functionality of commands 36-3A, there are still operations missing to store values in memory, to perform systems calls and to execute native code.
It would be interesting to know how the VM deals with arithmetic overflows (ADD, MUL)... or if they are simply not allowed in BD+.
Is the PC counted in bytes or words? In the below implementation it's bytes (as in your tutorial). However, this would unnecessarily restrict the range of jump commands.
Below is a simple, experimental C++ implementaion of the BD+ VM based on your specs (compiles, but not tested). Targets were keeping it short, maintainable and close to your notation. Hope I got all the signs right... Not sure about the commands for loading values from memory...
/* this code is public domain */
// the following lines could be moved to a separate header file later
typedef unsigned char byte; // for less typing
typedef unsigned int uint; // dito
class ExperimentalBDVM {
public:
void runProgram(byte* pcBaseAddress, uint pcStartOffset = 0x1000);
};
// end header
const int MEMSIZE = 0x400000; // 4MB address space
uint makeint(byte b3, byte b2, byte b1, byte b0) {
return ((int)b3) << 24 | ((int)b3) << 16 | ((int)b2) << 8 | ((int)b0);
}
int _signed(uint x) { return (int)x; }
int _signed(int x) { return x; }
int _unsigned(uint x) { return x; }
int _unsigned(int x) { return (uint)x; }
void ExperimentalBDVM::runProgram(byte* pcBaseAddress, uint pcStartOffset) {
// init VM
uint PC = pcStartOffset; // program counter
uint R[32]; // registers
byte mem[MEMSIZE]; // memory
// init memory
for (int i = 0; i < MEMSIZE; i++) {
mem[i] = 0;
}
// temporary variables
byte* addr;
int b = 0;
// process commands in (currently infinite) lo
while(true) {
// instruction fetch
byte* pca = pcBaseAddress + PC; // PC memory address
uint I = makeint(pca[3], pca[2], pca[1], pca[0]); // instruction
uint C = I >> 0x1A; // command
uint X = ((I >> 0x13) & 0x7C) >> 2; // operand X
uint Y = ((I >> 0x0E) & 0x7C) >> 2; // operand Y
uint Z = ((I >> 0x09) & 0x7C) >> 2; // operand Z
uint C16 = I & 0xFFFF; // 16 bit constant
int C16S = _signed(_unsigned(I) << 16) >> 16; // same signed
uint C5 = I & 0x1F; // 5 bit constant
//uint C26 = I & 0x3FFFFFF; // 26 bit constant
int C26S = _signed(_unsigned(I) << 6) >> 6; // same signed
PC += 4; // already increase PC
// execute instruction
switch (C) {
case 0x01: R[X] = R[Y] + R[Z]; break; // ADD
case 0x02: R[X] = R[Y] - R[Z]; break; // SUB
case 0x03: R[X] = R[Y] * R[Z]; break; // MUL
case 0x04: R[X] = _unsigned(R[Y]) / R[Z]; break; // IDIV
case 0x05: R[X] = _signed(R[Y]) / R[Z]; // DIV
case 0x06: R[X] = R[Y] << (R[Z] & 0x1F); break; // SHL
case 0x07: R[X] = R[Y] >> (R[Z] & 0x1F); break; // SHR
case 0x08: R[X] = _signed(R[Y]) >> (R[Z] & 0x1F); break; // SAR
case 0x09: R[X] = R[Y] & R[Z]; break; // AND
case 0x0A: R[X] = R[Y] | R[Z]; break; // OR
case 0x0B: R[X] = R[Y] ^ R[Z]; break; // XOR
case 0x0C: R[X] = (R[Y] == R[Z]) ? 1 : 0; break; // EQU
case 0x0D: R[X] = (R[Y] != R[Z]) ? 1 : 0; break; // NEQU
case 0x0E: R[X] = (_signed(R[Y]) < _signed(R[Z])) ? 1 : 0; break; // LESS
case 0x0F: R[X] = (_unsigned(R[Y]) < _unsigned(R[Z])) ? 1 : 0; break; // BLW
case 0x10: R[X] = (_signed(R[Y]) > _signed(R[Z])) ? 1 : 0; break; // GRE
case 0x11: R[X] = (_unsigned(R[Y]) > _unsigned(R[Z])) ? 1 : 0; break; // ABV
case 0x12: R[X] = (_signed(R[Y]) <= _signed(R[Z])) ? 1 : 0; break; // LEQ
case 0x13: R[X] = (_unsigned(R[Y]) <= _unsigned(R[Z])) ? 1 : 0; break; // BEQ
case 0x14: R[X] = (_signed(R[Y]) >= _signed(R[Z])) ? 1 : 0; break; // GEQ
case 0x15: R[X] = (_unsigned(R[Y]) >= _unsigned(R[Z])) ? 1 : 0; break; // AEQ
case 0x16: PC = R[Y]; break; // JMP
case 0x17: R[0x1F] = PC; PC = R[Y]; break; // CALL
// same with constants
case 0x18: R[X] = R[Y] + C16S; break; // ADDE
case 0x19: R[X] = R[Y] + C16; break; // ADD
case 0x1A: R[X] = R[Y] - C16S; break;// SUBE
case 0x1B: R[X] = R[Y] - C16; break; // SUB
case 0x1C: R[X] = _unsigned(R[Y]) << C5; break; // SHL
case 0x1D: R[X] = _unsigned(R[Y]) >> C5; break; // SHR
case 0x1E: R[X] = _signed(R[Y]) >> C5; break; // SAR
case 0x1F: R[X] = R[Y] & C16; break; // AND
case 0x20: R[X] = R[Y] | C16; break; // OR
case 0x21: R[X] = R[Y] ^ C16; break; // XOR
case 0x22: R[X] = (_signed(R[Y]) == C16S) ? 1 : 0; break; // EQU
case 0x23: R[X] = (_signed(R[Y]) != C16S) ? 1 : 0; break; // NEQU
case 0x24: R[X] = (_signed(C16S) > _signed(R[Y])) ? 1 : 0; break; // GRE
case 0x25: R[X] = (_unsigned(C16) > _unsigned(R[Y])) ? 1 : 0; break; // ABV
case 0x26: R[X] = (_signed(C16S) < _signed(R[Y])) ? 1 : 0; break; // LESS
case 0x27: R[X] = (_unsigned(C16) < _unsigned(R[Y])) ? 1 : 0; break; // BLW
case 0x28: R[X] = (_signed(C16S) >= _signed(R[Y])) ? 1 : 0; break; // GEQ
case 0x29: R[X] = (_unsigned(C16) >= _unsigned(R[Y])) ? 1 : 0; break; // AEQ
case 0x2A: R[X] = (_signed(C16S) <= _signed(R[Y])) ? 1 : 0; break; // LEQ
case 0x2B: R[X] = (_unsigned(C16) <= _unsigned(R[Y])) ? 1 : 0; break; // BEQ
case 0x2C: PC += C26S; break; // JMP
case 0x2D: R[0x1F] = PC; PC += C26S; break; // CALL
case 0x2E: if (R[Y] == 0) PC += C16S; break; // JZ
case 0x2F: if (R[Y] != 0) PC += C16S; break; // JNZ
case 0x30: R[X] = I; break; // STR (correct??)
case 0x31: // LBSE
case 0x32: // LB
// correct?? (the ^ 0x03 appears strange...)
addr = &mem[((R[Y] + C16S) & 0x3FFFFF) ^ 0x03];
b = addr[0];
if (C == 0x31 && b >= 0x80) { // process sign
b-= 0x100;
}
R[X] = b;
break;
case 0x33: // LWSE
case 0x34: // LW
// correct??
addr = &mem[((R[Y] + C16S) & 0x3FFFFE) ^ 0x02];
b = (int)makeint(0, 0, addr[1], addr[0]);
if (C == 0x33 && b >= 0x8000) { // process sign
b -= 0x10000;
}
R[X] = b;
break;
case 0x35: // LDW
addr = &mem[(R[Y] + C16S) & 0x3FFFFC];
R[X] = makeint(addr[3], addr[2], addr[1], addr[0]);
break;
default: break; // NOP or unknown
}
}
}
int main() {
// TODO
}
Oopho2ei
31st August 2008, 15:04
Regarding the functionality of commands 36-3A, there are still operations missing to store values in memory, to perform systems calls and to execute native code.
I have updated commands 0x31-0x35 (improved syntax) and added the remaining commands 0x36-0x3A.
It would be interesting to know how the VM deals with arithmetic overflows (ADD, MUL)... or if they are simply not allowed in BD+.
Exceptions are handled by the interpreter but i haven't documented them yet.
Is the PC counted in bytes or words? In the below implementation it's bytes (as in your tutorial). However, this would unnecessarily restrict the range of jump commands.
Actually the PC is double word (32 bit) aligned. Every instruction is 32 bit long anyway. In my notes the PC is 4 bytes but the lower and higher bits are masked (PC & 0x3FFFFC). Have a look at what the dispatcher does:
previous instruction handler returns (has maybe changed PC)
...
command dispatcher is called:
...
PC = PC AND 0x3FFFFC; // make it dword aligned
I = mem[PC] ^ instruction_filter_value;
PC = PC + 4;
...
command_handler(I >> 0x1A);
....
command handler returns and dispatcher is called again. and so on...
Ignore the "instruction_filter_value". It's set by command ??? and is zero in my case.
Below is a simple, experimental C++ implementaion of the BD+ VM based on your specs (compiles, but not tested). Targets were keeping it short, maintainable and close to your notation. Hope I got all the signs right... Not sure about the commands for loading values from memory...
Great! :)
But you should probably use your own notation and keep my weird looking/confusing notations as comments. :)
I will record a trace of the first few commands executed by the virtual machine. Like 0000:1000 b0 00 00 40 -> PC = 0x1044
0000:1044 bc 00 ff fc -> no change
0000:1048 ...
We will use this for debugging. The 00000.svm is here: http://uploaded.to/?id=jqstc8
Edit: please use "cmd" for command and not "C" because i use "C" already otherwise. thanks.
Oopho2ei
31st August 2008, 17:03
Here is a short trace of the beginning of the VM execution. It should be enough to get the machine started. PC = 0x00001000
WD = 0x7FFFFFFF
0000:1000 B0000040 WD=0x7FFFFFFD; PC=0x00001044;
0000:1044 BC00FFFC WD=0x7FFFFFFB;
0000:1048 BC01FFFC WD=0x7FFFFFF9;
0000:104C BC02FFFC WD=0x7FFFFFF7;
0000:1050 BC03FFFC WD=0x7FFFFFF5;
0000:1054 BC04FFFC WD=0x7FFFFFF3;
0000:1058 BC05FFFC WD=0x7FFFFFF1;
0000:105C BC06FFFC WD=0x7FFFFFEF;
0000:1060 BC07FFFC WD=0x7FFFFFED;
0000:1064 BC08FFFC WD=0x7FFFFFEB;
0000:1068 BC09FFFC WD=0x7FFFFFE9;
0000:106C BC0AFFFC WD=0x7FFFFFE7;
0000:1070 BC0BFFFC WD=0x7FFFFFE5;
0000:1074 BC0CFFFC WD=0x7FFFFFE3;
0000:1078 BC0DFFFC WD=0x7FFFFFE1;
0000:107C BC0EFFFC WD=0x7FFFFFDF;
0000:1080 BC0FFFFC WD=0x7FFFFFDD;
0000:1084 BC10FFFC WD=0x7FFFFFDB;
0000:1088 BC11FFFC WD=0x7FFFFFD9;
0000:108C BC12FFFC WD=0x7FFFFFD7;
0000:1090 BC13FFFC WD=0x7FFFFFD5;
0000:1094 BC14FFFC WD=0x7FFFFFD3;
0000:1098 BC15FFFC WD=0x7FFFFFD1;
0000:109C BC16FFFC WD=0x7FFFFFCF;
0000:10A0 BC17FFFC WD=0x7FFFFFCD;
0000:10A4 BC18FFFC WD=0x7FFFFFCB;
0000:10A8 BC19FFFC WD=0x7FFFFFC9;
0000:10AC BC1AFFFC WD=0x7FFFFFC7;
0000:10B0 BC1BFFFC WD=0x7FFFFFC5;
0000:10B4 BC1CFFFC WD=0x7FFFFFC3;
0000:10B8 BC1DFFFC WD=0x7FFFFFC1;
0000:10BC BC1EFFFC WD=0x7FFFFFBF;
0000:10C0 BC1FFFFC WD=0x7FFFFFBD;
0000:10C4 C3A00000 WD=0x7FFFFFBC; R[0x1F]=0x00000000;
0000:10C8 67BD115C WD=0x7FFFFFBB; R[0x1D]=0x0000115C;
0000:10CC C3800000 WD=0x7FFFFFBA; R[0x1C]=0x00000000;
0000:10D0 679C1000 WD=0x7FFFFFB9; R[0x1C]=0x00001000;
0000:10D4 D7BD0000 WD=0x7FFFFFB7; R[0x1D]=0x6FBD001C;
0000:10D8 E3BC0000 WD=0x7FFFFFB5; mem[0x001000]=0x6FBD001C;
0000:10DC D7A10000 WD=0x7FFFFFB3; R[0x1D]=0x00000000;
0000:10E0 C3A0001F WD=0x7FFFFFB2; R[0x1D]=0x001F0000;
0000:10E4 67BDFFFC WD=0x7FFFFFB1; R[0x1D]=0x001FFFFC;
0000:10E8 B4000008 WD=0x7FFFFFAE; PC=0x000010F4; R[0x1F]=0x000010EC;
0000:10F4 D43F0000 WD=0x7FFFFFAC; R[0x01]=0x00000000;
0000:10F8 B8010018 WD=0x7FFFFFAA; PC=0x00001114;
0000:1114 6FBDFFFC WD=0x7FFFFFA9; R[0x1D]=0x001F0000;
0000:1118 C3800000 WD=0x7FFFFFA8; R[0x1C]=0x00000000;
0000:111C 679C1160 WD=0x7FFFFFA7; R[0x1C]=0x00001160;
0000:1120 C0200000 WD=0x7FFFFFA6; R[0x01]=0x00000000;
0000:1124 64211040 WD=0x7FFFFFA5; R[0x01]=0x00001040;
0000:1128 D4210000 WD=0x7FFFFFA3; R[0x01]=0x00000000;
0000:112C 6FBD0004 WD=0x7FFFFFA2; R[0x1D]=0x001EFFFC;
0000:1130 E03D0000 WD=0x7FFFFFA0; mem[0x1EFFFC]=0x00000000;
0000:1134 B4000004 WD=0x7FFFFF9D; PC=0x0000113C; R[0x1F]=0x00001138;
The watchdog (WD) is decremented by the corresponding amount of cycles after the execution of every instruction according to the table below. As long as the watchdog is greater than zero execution continues. Otherwise it breaks.
Edit: the table is:0x01,0x01,0x01,0x04,0x10,0x10,0x01,0x01, // cmd00-cmd07
0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01, // cmd07-cmd0F
0x01,0x01,0x01,0x01,0x01,0x01,0x02,0x03, // cmd10-cmd17
0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01, // cmd18-cmd1F
0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01, // cmd20-cmd27
0x01,0x01,0x01,0x01,0x02,0x03,0x02,0x02, // cmd28-cmd2F
0x01,0x04,0x04,0x04,0x04,0x02,0x04,0x04, // cmd30-cmd37
0x02,0x00,0x06,0x01,0x01,0x01,0x01,0x01 // cmd38-cmd3F
Cmd39 resets the timer back to 7FFFFFFFh. Btw the command 0x30 was wrong. I forgot a shift. It's fixed now.
ala42
31st August 2008, 18:07
It might make sense to use the DLX mnemonics, like at http://www.csee.umbc.edu/courses/undergraduate/411/spring96/dlx.html and http://www.ee.byu.edu/class/ee428/labs/DLXinst.html.
Oopho2ei
31st August 2008, 20:08
It might make sense to use the DLX mnemonics, like at http://www.csee.umbc.edu/courses/undergraduate/411/spring96/dlx.html and http://www.ee.byu.edu/class/ee428/labs/DLXinst.html.
hi! :)
Thanks for the hint. I have started the mapping. It is much easier to read now.
Instruction Formats:---------------------------------------------------------------------------------
|Format | Bits |
|--------------------------------------------------------------------------------
| | 31 ... 26 | 25 ... 21 | 20 ... 16 | 15 ... 11 | 10 ... 06 | 05 ... 00 |
|--------------------------------------------------------------------------------
|R-type | opcode | Rd | Rs1 | Rs2 | ??? | ??? |
|I-type | opcode | Rd | Rs1 | <---------- immediate ---------> |
|J-type | opcode | <------------------- immediate ---------------------> |
|--------------------------------------------------------------------------------
Instructions:Opcode | Instr | Description | Format | Operation (C-style coding)
-------------------------------------------------------------------------------------------------
0x00 | NOP | do nothing | |
0x01 | ADD | add | R-type | Rd = Rs1 + Rs2
0x02 | SUB | substract | R-type | Rd = Rs1 - Rs2
0x03 | MUL | multipliply | R-type | Rd = Rs1 * Rs2
0x04 | DIV | signed integer division | R-type | Rd = signed(Rs1) / signed(Rs2)
0x05 | DIVU | unsigned integer division | R-type | Rd = unsigned(Rs1) / unsigned(Rs2)
0x06 | SLL | shift left logical | R-type | Rd = Rs1 << (Rs2 & 0x1F)
0x07 | SRL | shift right logical | R-type | Rd = Rs1 >> (Rs2 & 0x1F)
0x08 | SRA | shift right arithmetic | R-type | as SRL & see notes below
0x09 | AND | and | R-type | Rd = Rs1 & Rs2
0x0A | OR | or | R-type | Rd = Rs1 | Rs2
0x0B | XOR | exclusive or | R-type | Rd = Rs1 ^ Rs2
0x0C | SEQ | set if equal | R-type | Rd = (Rs1 == Rs2 ? 1 : 0)
0x0D | SNE | set if not equal | R-type | Rd = (Rs1 != Rs2 ? 1 : 0)
0x0E | SLT | set if less than | R-type | Rd = (signed(Rs1) < signed(Rs2) ? 1 : 0)
0x0F | SBT | set if below than | R-type | Rd = (unsigned(Rs1) < unsigned(Rs2) ? 1 : 0)
0x10 | SGT | set if greater than | R-type | Rd = (signed(Rs1) > signed(Rs2) ? 1 : 0)
0x11 | SAT | set if above than | R-type | Rd = (unsigned(Rs1) > unsigned(Rs2) ? 1 : 0)
0x12 | SLE | set if less than or equal | R-type | Rd = (signed(Rs1) <= signed(Rs2) ? 1 : 0)
0x13 | SBE | set if below or equal than | R-type | Rd = (unsigned(Rs1) <= unsigned(Rs2) ? 1 : 0)
0x14 | SGE | set if greater than or equal | R-type | Rd = (signed(Rs1) >= signed(Rs2) ? 1 : 0)
0x15 | SAE | set if above or equal than | R-type | Rd = (unsigned(Rs1) >= unsigned(Rs2) ? 1 : 0)
0x16 | JR | jump register | I-type | PC = Rs1
0x17 | JALR | jump and link register | I-type | R31 = PC + 4 ; PC = Rs1
0x18 | ADDIE | add immediate sign extended | I-type | Rd = Rs1 + extend(immediate)
0x19 | ADDI | add immediate | I-type | Rd = Rs1 + immediate
0x1A | SUBIE | substract immediate sign extended | I-type | Rd = Rs1 - extend(immediate)
0x1B | SUBI | substract immediate | I-type | Rd = Rs1 - immediate
0x1C | SLLI | shift left logical immediate | I-type | Rd = Rs1 << (immediate & 0x1F)
0x1D | SRLI | shift right logical immediate | I-type | Rd = Rs1 >> (immediate & 0x1F)
0x1E | SRAI | shift right arithmetic immediate | I-type | as SRLI & see notes below
0x1F | ANDI | and immediate | I-type | Rd = Rs1 & immediate
0x20 | ORI | or immediate | I-type | Rd = Rs1 | immediate
0x21 | XORI | exclusive or immediate | I-type | Rd = Rs1 ^ immediate
0x22 | SEQI | set if equal to immediate | I-type | Rd = (Rs1 == extend(immediate) ? 1 : 0)
0x23 | SNEI | set if not equal to immediate | I-type | Rd = (Rs1 != extend(immediate) ? 1 : 0)
0x24 | SLI | set if less than immediate | I-type | Rd = (signed(Rs1) < extend(immediate) ? 1 : 0)
0x25 | SBI | set if below than immediate | I-type | Rd = (unsigned(Rs1) < unsigned(immediate) ? 1 : 0)
0x26 | SGI | set if greater than immediate | I-type | Rd = (signed(Rs1) > extend(immediate) ? 1 : 0)
0x27 | SAI | set if above than immediate | I-type | Rd = (unsigned(Rs1) > unsigned(immediate) ? 1 : 0)
0x28 | SLEI | set if less than or equal to immediate | I-type | Rd = (signed(Rs1) <= extend(immediate) ? 1 : 0)
0x29 | SBEI | set if below than or equal to immediate | I-type | Rd = (unsigned(Rs1) <= unsigned(immediate) ? 1 : 0)
0x2A | SGEI | set if greater than or equal to immediate | I-type | Rd = (signed(Rs1) >= extend(immediate) ? 1 : 0)
0x2B | SAEI | set if avove than or equal to immediate | I-type | Rd = (unsigned(Rs1) >= unsigned(immediate) ? 1 : 0)
0x2C | J | jump | J-type | PC += extend(value)
0x2D | JAL | jump and link | J-type | R31 = PC + 4 ; PC += extend(value)
0x2E | BEQZ | branch if equal to zero | I-type | PC += (Rs1 == 0 ? extend(immediate) : 0)
0x2F | BNEZ | branch if not equal to zero | I-type | PC += (Rs1 != 0 ? extend(immediate) : 0)
0x30 | LHI | load high bits | I-type | Rd = immediate << 16
0x31 | LBE | load byte sign extended | I-type | Rd = extend(MEM[Rs1 + extend(immediate)] && 0xFF) (?)
0x32 | LB | load byte | I-type | Rd = MEM[Rs1 + extend(immediate)] && 0xFF (?)
0x33 | LWE | load word sign extended | I-type | Rd = extend(MEM[Rs1 + extend(immediate)] && 0xFFFF) (?)
0x33 | LW | load word | I-type | Rd = MEM[Rs1 + extend(immediate)] && 0xFFFF (?)
0x35 | LDW | load double word | I-type | Rd = MEM[Rs1 + extend(immediate)]
0x36 | SB | store byte | I-type | MEM[Rs1 + extend(immediate)] = (unsigned char) Rd
0x37 | SW | store word | I-type | MEM[Rs1 + extend(immediate)] = (uint16) Rd
0x38 | SDW | store double word | I-type | MEM[Rs1 + extend(immediate)] = (uint32) Rd
0x39 | TRAP | call a trap function | J-type | trap[immediate]
0x3A | INSTF | set instruction filter value | I-type | IF = Rs1
0x3B | NOP | do nothing | |
0x3C | NOP | do nothing | |
0x3D | NOP | do nothing | |
0x3E | NOP | do nothing | |
0x3F | NOP | do nothing | |
-------------------------------------------------------------------------------------------------
Notes:
- All immediate values are padded with zeros on the left unless indicated by extend(), in which case they are padded with copies of the left-most bit in the immediate value. Also, some instructions don't use all of the fields available for the format.
- SRA and SRAI are arithmetic right shifts. This means that, instead of shifting in zeroes from the left, the sign bit of the operand is duplicated. SRL and SRA perform identically if Rs1 is positive. If Rs1 is negative (bit 31 == 1), 1's are shifted in from the left for SRA and SRAI.
// correct?? (the ^ 0x03 appears strange...)
addr = &mem[((R[Y] + C16S) & 0x3FFFFF) ^ 0x03];
The ^ 0x03 and ^ 0x02 in the address calculation is probably an adjustment for the reversed byte order of the dword so the byte or word is written at the correct position. Conversion between big endian and little endian does happen quite often inside the vm interpreter. Maybe we can just ignore this. Probably depends on the data structure we use for the memory.
evdberg
31st August 2008, 21:14
X = ((I >> 0x13) & 0x7C) >> 2
Y = ((I >> 0x0E) & 0x7C) >> 2
Z = ((I >> 0x09) & 0x7C) >> 2
Why not simply:
X = (I >> 21) & 31
Y = (I >> 16) & 31
Z = (I >> 11) & 31
Just returned from holiday and very pleased to see all your work! I assume you all got it from the patent description?
Oopho2ei
31st August 2008, 21:53
Why not simply:
X = (I >> 21) & 31
Y = (I >> 16) & 31
Z = (I >> 11) & 31
It's equivalent. The >> 2 does the conversion from a byte address to the corresponding address in a dword (32 bit) aligned memory structure. In this case our Register array uint32 R[32]. This is really confusing sometimes. I hope the implementation does somebody else. :)
Just returned from holiday and very pleased to see all your work! I assume you all got it from the patent description?
Most of it is from compromised players. I won't give any details about which and how. Don't even try. :)
evdberg
31st August 2008, 23:00
Which is the TRAP instruction? According to the patent "one exemplary embodiment" (BD+?) would use the DLX "trap" instruction to provide access to external procedure calls (see item 127).
Oopho2ei
1st September 2008, 07:55
Which is the TRAP instruction? According to the patent "one exemplary embodiment" (BD+?) would use the DLX "trap" instruction to provide access to external procedure calls (see item 127).
I don't know but cmd39 (called "INSTF" here) looks weird. It is also executed quite often although the instruction filter value is set to 0 all the time.
Anyway I am pretty sure i observed trap_xorblock(...) doing the decryption of section 2 in file 00001.vsm so i would only need to trace it back to an instruction.
A general note:
I am trying to make a top-down approach (to get an overview) adding more details with every sweep. The exception handling for the instructions is still missing too as you might have noticed. Furthermore the implementation needs to be close to 'perfect' because every mistake which distinguishes our interpreter from a licensed one can be targeted by future bd+ movie releases. :rolleyes:
schluppo
1st September 2008, 20:31
nice work... i was thinking that maybe some kind of disasm for .svm-files would be appreciated.
so, which parts of the .svm-files are encrypted and is the decryption done by feeding the few unencrypted setup commands in the beginning of the file to the vm or is it always (i.e. for the complete file(s)) happening in the standard way that you mentioned?
----
(by standard way, i mean: )
Instruction I: C R[X], R[Y], R[Z]
C = I >> 0x1A
X = ((I >> 0x13) & 0x7C) >> 2
Y = ((I >> 0x0E) & 0x7C) >> 2
Z = ((I >> 0x09) & 0x7C) >> 2
----
Oopho2ei
1st September 2008, 21:07
nice work... i was thinking that maybe some kind of disasm for .svm-files would be appreciated.
Yes this would certainly be helpful.
so, which parts of the .svm-files are encrypted and is the decryption done by feeding the few unencrypted setup commands in the beginning of the file to the vm or is it always (i.e. for the complete file(s)) happening in the standard way that you mentioned?
It currently looks like the machine decrypts itself (at least part of it's data) with the help of traps. Furthermore there is a "instruction filter" which is a 32 bit value that is xored with the current 'obfuscated' instruction to decrypt it. This value can be updated dynamically and was introduced to make static code analysis impossible. But if your program can run 'on top' of our interpreter/emulator i don't see any problems. By running 'on top' i mean your disassembler can access usefull information of the emulator at any time and maybe store it for later use.
----
(by standard way, i mean: )
Instruction I: C R[X], R[Y], R[Z]
C = I >> 0x1A
X = ((I >> 0x13) & 0x7C) >> 2
Y = ((I >> 0x0E) & 0x7C) >> 2
Z = ((I >> 0x09) & 0x7C) >> 2
----
Please use the much clearer description of posting 27 of this thread (link: http://forum.doom9.org/showthread.php?p=1177737#post1177737)
Oopho2ei
1st September 2008, 21:19
Which is the TRAP instruction? According to the patent "one exemplary embodiment" (BD+?) would use the DLX "trap" instruction to provide access to external procedure calls (see item 127).
Actually cmd39 is the trap instruction. I was simply fooled by the save and restore of the VM state (which includes the current instruction filter). :stupid:
The trap id seems to be the immediate value. I found a jump/vector table in the vm code of my sample blue ray disk calling cmd39 with the following immediate values:
000010 = void TRAP_Finished();
000020
000110 = UINT32 TRAP_Aes(UINT8 *dst, UINT8 *src, UINT32 len, UINT8 *key, UINT32 opOrKeyID);
000120 = UINT32 TRAP_PrivateKey(UINT32 keyID, UINT8 *dst, UINT8 *src, UINT32 srcLen, UINT32 controlWord);
000130 = UINT32 TRAP_Random(UINT8 *dst, UINT32 len);
000140 = UINT32 TRAP_Sha(UINT8 *dst, UINT8 *src, UINT32 len, UINT32 op);
000210 = UINT32 TRAP_AddWithCarry(UINT32 *dst, UINT32 *src, UINT32 len);
000220 = UINT32 TRAP_MultiplyWithRipple(UINT32 *dst, UINT32 *src, UINT32 len, UINT32 multiplicand);
000230 = UINT32 TRAP_XorBlock(UINT32 *dst, UINT32 *src, UINT32 len);
000310 = UINT32 TRAP_Memmove(UINT8 *dst, UINT8 *src, UINT32 len);
000320 = UINT32 TRAP_MemSearch(UINT8 *Region, UINT32 RegionLen, UINT8 *SearchData, UINT32 SearchDataLen, UINT32 *Dst);
000330 = UINT32 TRAP_Memset(UINT8 *dst, UINT8 fillvalue, UINT32 len);
000410 = UINT32 TRAP-SlotAttach(UINT32 slot, UINT32 codeLen);
000420 = UINT32 TRAP-SlotRead(UINT8 *dst, UINT32 slot);
000430 = UINT32 TRAP_SlotWrite(UINT8 *newContents);
000510 = UINT32 TRAP_DeviceAccess(UINT32 dev, UINT32 opID, UINT8 *buf);
000520 = UINT32 TRAP_DeviceDiscovery(UINT32 dev, UINT32 qID, UINT8 *buf, UINT32 *len);
000530 = UINT32 TRAP_DiscoveryRAM(UINT32 unknown, UINT32 dst, UINT32 len);
000540 = UINT32 TRAP_MediaReadFile(UINT8 *FileName, UINT32 SectionNumber, UINT32 Unknown, UINT32 *len, UINT8 *dst);
000550 = UINT32 TRAP_MediaSHAFileHash(UINT8 *FileName, UINT32 FileNameLen, UINT32 FileOffsetHigh, UINT32 FileOffsetLow, UINT32 *len, UINT8 *dst);
000560 = UINT32 TRAP_RunNative(UINT8 *signature, UINT32 sigLen, UINT8 *code, UINT32 codeLen);
000570
008010 = UINT32 TRAP_DebugLog(UINT8 *txt, UINT32 len);
008020
008030
For example "E4000230" would be the call for trap_xorblock. I don't know about the others yet. There may be more...
This is a small example of some instructions between two trap calls (trap 0x110 and trap 0x230) to show how parameters are passed:
0001:ABEC E4000110 TRAP #110h // call trap 0x110
0001:ABF0 581F0000 JR r31 // return to the previous address
0001:B374 04800800 ADD r4,r0,r1
0001:B378 63BD0014 ADDIE r29,#14h // r29 = stack pointer?
0001:B37C BC010038 BNEZ r1,#0001B3B8
0001:B380 63BDFFF4 ADDIE r29,#FFFFFFF4h
0001:B384 E15D0000 SDW r10,r29,#0h // first parameter: *dst
0001:B388 E17D0004 SDW r11,r29,#4h // second parameter: *src
0001:B38C 74270002 SRLI r1,r7,#2h
0001:B390 E03D0008 SDW r1,r29,#8h // third parameter: len
0001:B394 B7FFF8EC JAL #0001AC84 // jump into trap vector table
0001:AC84 E4000230 TRAP #230h // execute trap 0x230 ( TRAP_XorBlock(UINT32 *dst, UINT32 *src, UINT32 len) )
I wish i already had schluppo's disassembler :)
evdberg
1st September 2008, 23:05
Should JAL #PC+3FFF8EC not be JAL #PC-714 ? Considering the immediate value is 26 bits, and offsets are usually signed? And considering the address space is only 4MB (22 bits PC).
Oopho2ei
1st September 2008, 23:30
Should JAL #PC+3FFF8EC not be JAL #PC-714 ? Considering the immediate value is 26 bits, and offsets are usually signed? And considering the address space is only 4MB (22 bits PC).
You are right (again :) ). I just tried to uniformly represent the immediate part as unsigned value. You would get the same result after the addition and & 0x3FFFFC. Before any memory access you need to calculate "address = address & 0x3FFFFC" anyway. (Except if you want to access 16 bit or 8 bit values then of course you wouldn't mask the least significant 2 bits). But it is much easier to read so i corrected it. I didn't record the program counter otherwise i would have just replaced #PC-714h with the result.
Disabled
2nd September 2008, 17:48
Very nice work, especially to Oopho2ei!
Till the 12th post in this thread I never thought this would become anything at all. (And I still don't know how much is done after the bdvm is implemented.)
First I thought about trying to continue the bdvm, but then my little C knowledge kept me from continuing. (And I never was sure if anyone else is working on it.)
So I took an easier task and tried to start with the disassembler.
I started with the source from the bdvm but now very few is left. Here it goes:
#include <iostream>
#include <sstream>
#include <string>
#include <fstream>
using namespace std;
typedef unsigned char byte; // for less typing
typedef unsigned int uint; // dito
class BDDasm {
public:
BDDasm();
string dasmCmd(uint);
void dasmFile(string, uint, uint);
uint makeint(byte, byte, byte, byte);
private:
uint pc;
};
// end header
int _signed(uint x) { return (int)x; }
int _signed(int x) { return x; }
int _unsigned(uint x) { return x; }
int _unsigned(int x) { return (uint)x; }
BDDasm::BDDasm(){
pc = 0;
}
uint BDDasm::makeint(byte b3, byte b2, byte b1, byte b0) {
return ((int)b0) << 24 | ((int)b1) << 16 | ((int)b2) << 8 | ((int)b3);
}
void BDDasm::dasmFile(string fname, uint pep, uint len){
ifstream file;
file.open(fname.c_str());
file.seekg(pep);
pc = pep-6*4;
uint I; //Instruction
while(pc <= pep+len){
pc +=4;
I = makeint((byte)file.get(),(byte)file.get(),(byte)file.get(),(byte)file.get());
cout << dasmCmd(I);
}
file.close();
}
string BDDasm::dasmCmd(uint I) {
std::ostringstream out;
char buffer [100];
// instruction fetch
uint C = I >> 0x1A; // command
uint Rd = ((I >> 0x13) & 0x7C) >> 2; // operand X
uint Rs1 = ((I >> 0x0E) & 0x7C) >> 2; // operand Y
uint Rs2 = ((I >> 0x09) & 0x7C) >> 2; // operand Z
uint C16 = I & 0xFFFF; // 16 bit constant
int C16S = _signed(_unsigned(I) << 16) >> 16; // same signed
uint C5 = I & 0x1F; // 5 bit constant
uint C26 = I & 0x3FFFFFF; // 26 bit constant
int C26S = _signed(_unsigned(I) << 6) >> 6; // same signed
sprintf (buffer, "%6X ", pc-4);
out << buffer;
sprintf (buffer, "%8X ", I);
out << buffer;
// instructions
string ops[0x3C] = {"NOP", "ADD", "SUB", "MUL", "IDIV", "DIVU",
"SLL", "SRL", "SRA", "AND", "OR", "XOR", "SEQ", "SNE", "SLT",
"SBT", "SGT", "SAT", "SLE", "SBE", "SGE", "SAE", "JR", "JALR",
"ADDIE", "ADDI", "SUBIE", "SUBI", "SLLI", "SRLI", "SRAI",
"ANDI", "ORI", "XORI", "SEQI", "SNEI", "SLI", "SBI", "SGI",
"SAI", "SLEI", "SBEI", "SGEI", "SAEI", "J", "JAL", "BEQZ",
"BNEZ", "LHI", "LBE", "LB", "LWE", "LW", "LDW", "SB", "SW",
"SDW", "TRAP", "SWDR", "unknown"};
if(C>=0x00 && C<=0x3A){
out << ops[C];
}
else {
out << ops[0x3b];
}
if(C>=0x01 && C<=0x15){
out << " R" << Rd << ", R" << Rs1 << ", R" << Rs2;
}
else if(C==0x16||C==0x17){
out << " R" << Rs1;
}
else if(C>=0x18 && C<=0x3A && C!=0x2c && C!=0x2d && C!=0x39 && C!=0x2e && C!=0x2f){
sprintf(buffer, ", #%Xh", C16);
out << " R" << Rd << ", R" << Rs1 << buffer;
}
else if(C==0x2c || C==0x2d){
sprintf(buffer, " #%Xh", (pc+(C16S& 0x3FFFFC))& 0x3FFFFC);
out << buffer;
}
else if(C==0x2e || C==0x2f){
sprintf(buffer, ", #%Xh", (pc+(C16S& 0x3FFFFC))& 0x3FFFFC);
out << " R" << Rs1 << buffer;
}
else if(C==0x39){
sprintf(buffer, " #%Xh", C26);
out << buffer;
}
out << endl;
return out.str();
}
int main() {
BDDasm a;
a.dasmFile("00000.svm",0x1000,0xF000);
}
Now you can laugh at my coding style. Whats left to be done is some improvements to the dasm and of course a file reader and much more.
For a start, here is the output of the hardcoded commands:
1000 E4000110 TRAP #110h
1004 42445356 SGT R18, R4, R10
1008 581F0000 JR R31
100C 4800800 ADD R4, R0, R1
1010 63BD0014 ADDIE R29, R29, #14h
1014 BC010038 BNEZ R0, R1, #38h
1018 63BDFFF4 ADDIE R29, R29, #FFF4h
101C E15D0000 SDW R10, R29, #0h
1020 E17D0004 SDW R11, R29, #4h
1024 74270002 SRLI R1, R7, #2h
1028 E03D0008 SDW R1, R29, #8h
102C B7FFF8EC JAL, #918h
1030 E4000230 TRAP #230h
Looks quite familliar to what Oopho decoded, except for the JR and some constants.
*edit* Fixed JR handling...
*edit2* Added trivial stream handling... and watch out if someone ever tries to use the experimental BDVM, b2 is used twice in the makeint function...
*edit3* Added Offset to adress display and executed pc+r1 for jumps
*edit4* Final update with file reader and correct jumps (I hope). There is an offset for the pc of 6dwords to align with the trace in post #35, I guess thats some kind of header or something... I will post the dasm of the first 0xF000 bytes in post #40
chavonbravo
2nd September 2008, 18:08
Wow, gone a few days and all this progress.
This being a source code solution for the future, it will probably turn into a cat and mouse game with future bd+ movies, where flaws will be found and attacked, and will have to be patched up.
I beleive Peter meant before about once you're past a certain point, bd+ only helps, is that at the beginning of the vm code are all the checks for a valid player, and non-modified firmware. After you successfully emulate a valid player and pass the values needed for the disc to believe it's safe, you use the bd+ code to decypher the video stream.
This is probably why anydvd comes out with updates whenever a new bd+ disc can't be played, same cat and mouse game.
Oopho2ei
2nd September 2008, 19:35
40 B7FFF8EC JAL, #PC+FFFFF8ECh
I regret i didn't record the program counter (pc). Maybe i will add it later. In your case the PC = 40 so you could just evaluate the expression "PC+FFFFF8ECh" :)
Looks quite familliar to what Oopho decoded, except for the JR and some constants.
Please also look at some DLX disassembly examples. Like this one in section 2.2: http://heather.cs.ucdavis.edu/~matloff/DLX/Report.html and others.
Thank you for your effort. A disassembler will be helpful to study the traps.
This being a source code solution for the future, it will probably turn into a cat and mouse game with future bd+ movies, where flaws will be found and attacked, and will have to be patched up.
This is likely to happen but at least people will know how BD+ works. All discs which can be decrypted up to that point can also be decrypted in the future. I can't prevent anyone from writing and selling proprietary software based on the information from this thread. But keep in mind that if the interpreter is open source everyone can make modifications maybe based on research of other licensed players. Making it more difficult to target all those different version. It gets more people involved and also improves the knowledge of the community. Finally if your open source implementation has been targeted and is not working anymore for newer discs you would "only" need to record and compare the instruction trace (and maybe register values) of both your implementation and a working licensed player to find the exploit.
Anyway:
To guide your implementation efforts i dumped the register file and the machine memory directly before the very first trap instruction (trap 110h in this case) is executed.
register file (32 register each 32 bit): http://uploaded.to/?id=8kkmfn
machine memory (4MB): http://uploaded.to/?id=nqpu3z
execute this from offset 0x1000: http://uploaded.to/?id=jqstc8
Keep in mind that the players do a lot of big endian <-> little endian conversion (improves vm speed probably) . When you look at offset 0x55D0 of the vm_mem_dump.000 you will know what i mean.
Btw i have added some more comments on the few instruction between two trap instruction calls in posting #34. Some of you have probably already guessed where the parameters are stored and in which order. :rolleyes:
The parameters are written to the address [r29] in the order given by the function declaration. Maybe think of r29 as ESP (stack pointer) and every SDW (store double word) here would be a push. (This is not true in general of course but for calling a trap function. I haven't verified that for other traps yet.)
Disabled
2nd September 2008, 21:06
In your case the PC = 40 so you could just evaluate the expression "PC+FFFFF8ECh" :)
I added that...
Please also look at some DLX disassembly examples...
Will do...
I tried to dissasemble your dump and something was fishy:
1000 0 NOP
1004 0 NOP
1008 0 NOP
100C 0 NOP
1010 0 NOP
1014 0 NOP
1018 B0000040 J #1058h
101C E03D0008 SDW R1, R29, #8h
1020 E3FD0004 SDW R31, R29, #4h
1024 D4200000 LDW R1, R0, #0h
1028 E01D0000 SDW R0, R29, #0h
102C D7E01154 LDW R31, R0, #1154h
1030 E3E01150 SDW R31, R0, #1150h
1034 B4019B0C JAL #3FAB40h
1038 D7E01158 LDW R31, R0, #1158h
103C E3E01150 SDW R31, R0, #1150h
1040 E0200004 SDW R1, R0, #4h
1044 D7FD0004 LDW R31, R29, #4h
1048 D43D0008 LDW R1, R29, #8h
104C 67BD001C ADDI R29, R29, #1Ch
1050 E4000010 TRAP #10h
1054 B3FFFFFC J #1050h
1058 0 NOP
105C BC00FFFC BNEZ R0, #1058h
1060 BC01FFFC BNEZ R1, #105Ch
1064 BC02FFFC BNEZ R2, #1060h
1068 BC03FFFC BNEZ R3, #1064h
106C BC04FFFC BNEZ R4, #1068h
1070 BC05FFFC BNEZ R5, #106Ch
1074 BC06FFFC BNEZ R6, #1070h
1078 BC07FFFC BNEZ R7, #1074h
107C BC08FFFC BNEZ R8, #1078h
1080 BC09FFFC BNEZ R9, #107Ch
1084 BC0AFFFC BNEZ R10, #1080h
1088 BC0BFFFC BNEZ R11, #1084h
108C BC0CFFFC BNEZ R12, #1088h
1090 BC0DFFFC BNEZ R13, #108Ch
1094 BC0EFFFC BNEZ R14, #1090h
1098 BC0FFFFC BNEZ R15, #1094h
109C BC10FFFC BNEZ R16, #1098h
10A0 BC11FFFC BNEZ R17, #109Ch
10A4 BC12FFFC BNEZ R18, #10A0h
10A8 BC13FFFC BNEZ R19, #10A4h
10AC BC14FFFC BNEZ R20, #10A8h
10B0 BC15FFFC BNEZ R21, #10ACh
10B4 BC16FFFC BNEZ R22, #10B0h
10B8 BC17FFFC BNEZ R23, #10B4h
10BC BC18FFFC BNEZ R24, #10B8h
10C0 BC19FFFC BNEZ R25, #10BCh
10C4 BC1AFFFC BNEZ R26, #10C0h
10C8 BC1BFFFC BNEZ R27, #10C4h
10CC BC1CFFFC BNEZ R28, #10C8h
10D0 BC1DFFFC BNEZ R29, #10CCh
10D4 BC1EFFFC BNEZ R30, #10D0h
10D8 BC1FFFFC BNEZ R31, #10D4h
10DC C3A00000 LHI R29, R0, #0h
10E0 67BD115C ADDI R29, R29, #115Ch
10E4 C3800000 LHI R28, R0, #0h
10E8 679C1000 ADDI R28, R28, #1000h
10EC D7BD0000 LDW R29, R29, #0h
10F0 E3BC0000 SDW R29, R28, #0h
10F4 D7A10000 LDW R29, R1, #0h
10F8 C3A0001F LHI R29, R0, #1Fh
10FC 67BDFFFC ADDI R29, R29, #FFFCh
1100 B4000008 JAL #1108h
1104 0 NOP
...
Are you sure its really PC += value with jumps? I don't know much asm, but I always thought they were just PC = value or something with an absolute offset like the program entry point or a memory offset... But even then that code doesn't make much sense...
Another question: In the .svm files, are there just instructions or is there data that has not length%4==0? Else I could try to implement a quick jump label generation. If there is data whose length is not dividable by 4 I'm sure I wouldn't be able to do this, as I would have to really analyze the code and decide where to start again with the dasm. Else a quick jump-search-prepass would do.
*edit* he is right and here is the (hopefully) correct dasm of the first 0xF000 bytes:
dasm.zip - 0.14MB (http://www.zshare.net/download/180874810c41c8b9/)
superman500
2nd September 2008, 21:47
Great job so far!
Oopho2ei
2nd September 2008, 21:58
105C BC00FFFC BNEZ R0, #1058h
Please look at the pseudocode in posting #24. The program counter is already incremented during the fetch stage so when the execution of the current instruction (in this case 'BNEZ') starts the PC has already been incremented by 4. Like in the old (bad syntax) that would be "BNEZ R0,#(PC+4)-4" So this instruction actually checks if R0 is zero. If it is everything is fine and the next register is checked. If it is not zero then the machine enters a infinite loop in other words halts. The instruction actually is:
105C BC00FFFC BNEZ R0, #105Ch
The same applies to "1018 B0000040 J #1058h" you are always jumping one instruction too short. :)
If i see more mistakes i will edit this posting. Sorry that the information is scattered all over this thread (and sometimes more confusing than helpful :) ). Maybe somebody could make a summery.
Another question: In the .svm files, are there just instructions or is there data that has not length%4==0?
When you look at the 00000.svm file from my last posting you will find strings (copyright foo bar...) so yes there is data. I don't know if there is any data which is not dword aligned but remember that every instruction is dword aligned (instruction address = address & 0x3FFFFC. you know). Hope that helps a little.
In posting #25 is a instruction trace starting from 0x1000 (executing that 00000.svm file). You might want to use that too.
Btw i have now added the instruction offsets for the trace between two trap calls (posting #34).
Disabled
2nd September 2008, 22:11
If it is not zero then the machine enters a infinite loop in other words halts.
And I just thought noone would programm like that... But then everything is clear.
... don't know if there is any data which is not dword aligned but remember that every instruction is word aligned ... Hope that helps a little.
dword would make it quite simple, with word alignement I would have to trace the program flow and ... well I think its pretty hard to make a jump analysis like "BNEZ r1, 2" with r1==0 then I don't know where to continue with the dasm. But that is a problem that occurs anyway when I want to dasm the whole file and I wonder how that would be solved in a professional dasm.
In posting #25 is a instruction trace starting from 0x1000 (executing that 00000.svm file). You might want to use that too.
I will do that! Nice suggestion.
btw: I got a file reader running. I will update the code once I'm a little more sure about its correctness.
Oopho2ei
2nd September 2008, 22:23
Before you invest a lot of effort in static code analysis please keep that in mind:
Instruction Filter. The Instruction Filter provides a mechanism to protect content code from static disassembly, or to enable device-specific behavior. Prior to executing an instruction, the VM first computes the XOR of the instruction with the current 32-bit Instruction Filter value. Content code may set the value of the Instruction Filter dynamically via the INSTF instruction. The Instruction filter potentially enhances a content creator’s ability to obfuscate code by forcing adversaries to trace the full code execution path rather than disassembling code in a single pass. Instruction filter values may be computed via cryptographic operations, or from device-specific operations, which may require the adversary to correctly emulate this behavior in order to disassemble code.
The instruction filter is currently zero (couldn't observe any other value) but it is unlikely it will stay that way on future releases. So you have to work together with the guys who write the emulator/interpreter.
Disabled
2nd September 2008, 22:33
Before you invest a lot of effort in static code analysis please keep that in mind:
The instruction filter is currently zero (couldn't observe any other value) but it is unlikely it will stay that way on future releases. So you have to work together with the guys who write the emulator/interpreter.
Ok, then we really can't create a disassembler that way...
perhaps I'll look into the VM the other day as it didn't look too complicated. At least I got started with cpp again and some of my code might proove usefull in the future - at least for me.
*edit* I'll wrap a few things together and release what I have till now in a few minutes...done
Oopho2ei
2nd September 2008, 23:07
"bmnot" has already started with the emulator/interpreter (see posting #23). (I don't know how far he has come.) Now instead of reading the instruction from the file you would get a sequence of (address, instruction) pairs from the emulator. You would take the instruction and place it at the given address in your machine memory. (Same for register and memory accesses.) The memory would slowly be filled with (plain) instructions and data you can analyze.
Anyway one doesn't really have to study every program run by every disc on the virtual machine. Currently it helps to see the dataflow in and out of the trap calls. But beyond that it is probably only useful to analyze exploits used to disable successful emulation. (which might happen a lot in the future if this succeeds).
What is currently needed most is a working emulator. In posting #39 i provided a memory and register file snapshot prior the execution of the first trap command (#110). If anyone makes it that far and the register/memory state of the emulator matches the given snapshot please leave a note here.
Disabled
2nd September 2008, 23:30
Of course I know about his attempts and I first started with improving it, but as I didn't write c++ code for a long time I started with something that seemed easier to me.
At the end you just can't really code a dasm for these files because as you said its a quite static code analysis and they can make the code non-static. They could even jump twice to a single dword with a different instruction filter and let it do different things. And if the code decrypts itself, dasm is also useless.
So a VM is the only way to go. Of course it can have a debugger that disassembles the current ops, but a static dasm is impossible imo.
*edit* one thing about the traps... we would have to implement them in another way than the vm do we? Or better said: The VM has to know what each trap exactly does and provide (c++)code accordingly does it?
Oopho2ei
2nd September 2008, 23:54
*edit* one thing about the traps... we would have to implement them in another way than the vm do we? Or better said: The VM has to know what each trap exactly does and provide (c++)code accordingly does it?
Yes, the traps are implemented in the host system. They provide the data interface between player (host system) and the program running on the virtual machine (guest system). Please refer to http://securityevaluators.com/pdf/spdc_aacs_2005.pdf section "The SPDC Interface Specification" for details. The traps are described more closely here: http://appft1.uspto.gov/netacgi/nph-Parser?Sect1=PTO1&Sect2=HITOFF&d=PG01&p=1&u=%2Fnetahtml%2FPTO%2Fsrchnum.html&r=1&f=G&l=50&s1=%2220070033419%22.PGNR.&OS=DN/20070033419&RS=DN/20070033419 (links taken from posting #14 from "chavonbravo")
But before that i would have to figure out the mapping between the trap number and the Trap name assuming they are all listed in that patent above.
Disabled
3rd September 2008, 01:42
And here I present: An improved draft experimental alpha BDVM!
I can run your dump and the machine shows the same trace as in post #25. That doesn't mean much, as there are no conditional jumps (after I calculate something) in that trace and on the first conditional jump after a calculation it goes in an infinitive loop.
But the basics work - now I (someone) has to debug it.
I used bmnots work as a great start. I hope our developement didn't collide here.
I tried to convert some of his syntax to the new one from post #27 and tried to add the last cmds. That was before I did the dasm, so it added a lot of clutter, as I did some things I didn't really understand. I will investigate into that later.
Now the dasm is back, to tell us what happens. So here is the trace of the file:
1000 0 NOP
1004 0 NOP
1008 0 NOP
100C 0 NOP
1010 0 NOP
1014 0 NOP
1018 B0000040 J #105Ch
105C BC00FFFC BNEZ R0, #105Ch
1060 BC01FFFC BNEZ R1, #1060h
1064 BC02FFFC BNEZ R2, #1064h
1068 BC03FFFC BNEZ R3, #1068h
106C BC04FFFC BNEZ R4, #106Ch
1070 BC05FFFC BNEZ R5, #1070h
1074 BC06FFFC BNEZ R6, #1074h
1078 BC07FFFC BNEZ R7, #1078h
107C BC08FFFC BNEZ R8, #107Ch
1080 BC09FFFC BNEZ R9, #1080h
1084 BC0AFFFC BNEZ R10, #1084h
1088 BC0BFFFC BNEZ R11, #1088h
108C BC0CFFFC BNEZ R12, #108Ch
1090 BC0DFFFC BNEZ R13, #1090h
1094 BC0EFFFC BNEZ R14, #1094h
1098 BC0FFFFC BNEZ R15, #1098h
109C BC10FFFC BNEZ R16, #109Ch
10A0 BC11FFFC BNEZ R17, #10A0h
10A4 BC12FFFC BNEZ R18, #10A4h
10A8 BC13FFFC BNEZ R19, #10A8h
10AC BC14FFFC BNEZ R20, #10ACh
10B0 BC15FFFC BNEZ R21, #10B0h
10B4 BC16FFFC BNEZ R22, #10B4h
10B8 BC17FFFC BNEZ R23, #10B8h
10BC BC18FFFC BNEZ R24, #10BCh
10C0 BC19FFFC BNEZ R25, #10C0h
10C4 BC1AFFFC BNEZ R26, #10C4h
10C8 BC1BFFFC BNEZ R27, #10C8h
10CC BC1CFFFC BNEZ R28, #10CCh
10D0 BC1DFFFC BNEZ R29, #10D0h
10D4 BC1EFFFC BNEZ R30, #10D4h
10D8 BC1FFFFC BNEZ R31, #10D8h
10DC C3A00000 LHI R29, R0, #0h
10E0 67BD115C ADDI R29, R29, #115Ch
10E4 C3800000 LHI R28, R0, #0h
10E8 679C1000 ADDI R28, R28, #1000h
10EC D7BD0000 LDW R29, R29, #0h
10F0 E3BC0000 SDW R29, R28, #0h
10F4 D7A10000 LDW R29, R1, #0h
10F8 C3A0001F LHI R29, R0, #1Fh
10FC 67BDFFFC ADDI R29, R29, #FFFCh
1100 B4000008 JAL #110Ch
110C D43F0000 LDW R1, R31, #0h
1110 B8010018 BEQZ R1, #112Ch
112C 6FBDFFFC SUBI R29, R29, #FFFCh
1130 C3800000 LHI R28, R0, #0h
1134 679C1160 ADDI R28, R28, #1160h
1138 C0200000 LHI R1, R0, #0h
113C 64211040 ADDI R1, R1, #1040h
1140 D4210000 LDW R1, R1, #0h
1144 6FBD0004 SUBI R29, R29, #4h
1148 E03D0000 SDW R1, R29, #0h
114C B4000004 JAL #1154h
1154 D43F0000 LDW R1, R31, #0h
1158 B8010004 BEQZ R1, #1160h
1160 67BD0004 ADDI R29, R29, #4h
1164 B3FFFEB0 J #1018h
1018 B0000040 J #105Ch
105C BC00FFFC BNEZ R0, #105Ch
1060 BC01FFFC BNEZ R1, #1060h
1064 BC02FFFC BNEZ R2, #1064h
1068 BC03FFFC BNEZ R3, #1068h
106C BC04FFFC BNEZ R4, #106Ch
1070 BC05FFFC BNEZ R5, #1070h
1074 BC06FFFC BNEZ R6, #1074h
1078 BC07FFFC BNEZ R7, #1078h
107C BC08FFFC BNEZ R8, #107Ch
1080 BC09FFFC BNEZ R9, #1080h
1084 BC0AFFFC BNEZ R10, #1084h
1088 BC0BFFFC BNEZ R11, #1088h
108C BC0CFFFC BNEZ R12, #108Ch
1090 BC0DFFFC BNEZ R13, #1090h
1094 BC0EFFFC BNEZ R14, #1094h
1098 BC0FFFFC BNEZ R15, #1098h
109C BC10FFFC BNEZ R16, #109Ch
10A0 BC11FFFC BNEZ R17, #10A0h
10A4 BC12FFFC BNEZ R18, #10A4h
10A8 BC13FFFC BNEZ R19, #10A8h
10AC BC14FFFC BNEZ R20, #10ACh
10B0 BC15FFFC BNEZ R21, #10B0h
10B4 BC16FFFC BNEZ R22, #10B4h
10B8 BC17FFFC BNEZ R23, #10B8h
10BC BC18FFFC BNEZ R24, #10BCh
10C0 BC19FFFC BNEZ R25, #10C0h
10C4 BC1AFFFC BNEZ R26, #10C4h
10C8 BC1BFFFC BNEZ R27, #10C8h
10CC BC1CFFFC BNEZ R28, #10CCh
10CC BC1CFFFC BNEZ R28, #10CCh
10CC BC1CFFFC BNEZ R28, #10CCh
And now the code (and someone has to laugh this time!)
*edited* code removed, as its not the latest version. I don't have the latest version for download right now, but will release it instantly, if someone asks.
And here is the trace with register dump:
out.txt (http://www.zshare.net/download/18093691a1a910e8/)
You can very easily change the .c file to act as a non interactive debugger.
Thats it for today from me.
Oopho2ei
3rd September 2008, 07:56
I can run your dump and the machine shows the same trace as in post #25. That doesn't mean much, as there are no conditional jumps (after I calculate something) in that trace and on the first conditional jump after a calculation it goes in an infinitive loop.
Ok i will post a longer trace soon. The infinite loop suggests that the emulator did something wrong :rolleyes:
Btw. you missed something i explained in posting #22: The 00000.svm file has a 18h byte long header so when i say the execution starts at 0x1000 this translates to address 0x1000+0x18=0x1018 in the file.
Thats it for today from me.
Looks good. Thank you. :)
vBulletin® v3.8.11, Copyright ©2000-2026, vBulletin Solutions Inc.