While everybody in my area are preparing for Passover and doing some cleaning, I came across a bunch of CD-ROMs that have be left on a wall near my house for the public. These CDs contained movies that are directed at the Jewish orthodox community in my area. I really did not have an interest in these CDs, as they are very poor quality and have an atrocious plot. But while browsing through the titles, I noticed a small anti-piracy warning printed on the cover stating that “Any attempt at copying this disk will cause damage to the disk drive”. How will that work exactly? Since I was not near my computer (yes, it has a CD drive) I inspected the disk visually. I found that some of the CDs had 1-4 dimples on the reflective side and some didn’t. I picked a few with and without the dimples and decided to take a look at them at home.

Protection dimples on the CD

When I got back home, I put the CDs into the computer and tried to play them. On all the CDs, I noticed there were a bunch of EXE/PE files and a larger ~600MB file probably containing the movie. The first thing I tried was to play the movies on the CDs. I noticed there were 2 types of CDs, some simply contained a MPEG-1 sequence file (with a mx extension) and 3 PE files (mpfull.exe, Player.exe and run.dll) but others were significantly more complicated with 15 different PE files and the suspected video file (the largest 600MB file) was in an unknown format with a grt extension. I decided to start from the CDs that had the plain MPEG-1 sequence on them, I opened the file in VLC and started playing them, the movie played for a while and then got stuck at around the 10 minute mark, I checked dmesg and as expected, the CD drive was stuck trying to read a bad sector. This was expected due to the dimples on the CDs surface, but raises the question how can a standard user play these movies without it getting stuck?

The dmesg errors warning about bad sectors:

[  +7.067739] sr 2:0:0:0: [sr0] tag#30 FAILED Result: hostbyte=DID_OK driverbyte=DRIVER_SENSE
[  +0.000008] sr 2:0:0:0: [sr0] tag#30 Sense Key : Medium Error [current]
[  +0.000004] sr 2:0:0:0: [sr0] tag#30 Add. Sense: L-EC uncorrectable error
[  +0.000006] sr 2:0:0:0: [sr0] tag#30 CDB: Read(10) 28 00 00 00 c4 f8 00 00 02 00
[  +0.000004] print_req_error: I/O error, dev sr0, sector 201696
[  +0.000011] Buffer I/O error on dev sr0, logical block 50424, async page read
[  +0.000007] Buffer I/O error on dev sr0, logical block 50425, async page read

Next, I wanted to try and play the other set of CDs that had the 15 PE files on it. As I mentioned before, the CDs contained a large file with a grt extension that I could not open with VLC. I ran file 03.grt but only got 03.grt: data, I also run binwalk 03.grt just in case but all it found was some false-positives. Reading the file with hexdump -Cn512 03.grt also did not really help, but it did give me clue that the file has some structure, as it started with the magic letters GT. Looking back at the CD cover, the company logo that was making these movies did say גרינטק (GreenTech in hebrew) and the EXE file included in the CD was called GRTPlayer.exe, so GT makes sense to be some king of magic based on the company initials. Playing the movies with the included GRTPlayer seemed to work just fine, this is true for the previous type of CD I wrote about.

The built it GRTPlater.exe playing the movie (stopped at the anti-piracy warning, notifying me there is enough space in hell for me too :O )

The crappy built in player

After looking at the 2 types of formats the company makes, I’m left with 2 big questions:

  1. What is this mysterious GRT movie format?
  2. How do I break the DRM and copy these movies?

I decided to work on the GRT format question first, since the PE files on the CD are small (all totaling 11.9MB) I could copy them to the computer without worrying about the bad sectors. I then started to filter the files and come up with a list of files that are most probable of holding the key. I used strings on the files and greped for strings such as grt,greentech and more. Most of the files were easy do dismiss, as they all had standard names and strings that marked them as standard Borland C++ libraries. I was left with 6 files that seemed to be custom Borland package file:

  • GRTPlayer.exe
  • GRTPlayerPkg.bpl
  • Krs.bpl
  • KrsBase.bpl
  • KrsLocal.bpl
  • XMLServer.bpl
  • DShow.bpl

Another interesting thing about the files, was the bpl extension and strings allowed me to identify the langugae and environment the program was built in as Borland C++, and the file named DShow.bpl gave away the fact that the media player was using Microsoft DirectShow. I started up IDA and loaded GRTPlayer.exe as this is the main player file, Without even needing to search to far, I found in the WinMain initialization of the ECX and EDX parameters with a TKrsGrtPlayerForm as the object and GrtPlayerUnit as a parameter. Looking at the TGrtPlayerForm callbacks, the FormCreate method is called when the window is being created and from there, some custom code can already be seen. The first thing that TGrtPlayerForm::FormCreate does is to check weather a video file was specified. If a video file was not specified, it searches for a *.grt file in the EXEs working directory and will use that file as it’s media source. Once a video file path is available the method continues and calls two more methods from the DShow library TKrsCustomDShowPlayer::Open and then TKrsCustomDShowPlayer::Play. Following the execution path in the DShow library, the mentioned methods use a TKrsGrtPlayer object from the TGrtPlayerForm to open the media file and read it.

The search and running of a GRT file

After following the execution path into the GRTPlayerPkg.bpl library, it is clear that all the logic for handling the file is located in it. Looking at the virtual table of the TKrsGrtPlayer object, there are some interesting methods handling the file. The TKrsGrtPlayer::Render method is the first method to be called from the main executable, this method accepts a System::AnsiString as a parameter and checks weather the string ends with a .grt extension. If the string is not a GRT file, the method TKrsCustomDShowPlayer::Render is called (located in DShow) and is used a a standard media player. But, if the file has a .grt extension, then TKrsGrtPlayer kicks in and initializes the custom media player.

The init method for the GRT handler

The next thing that can be learned from the custom init code is that the movie expected to play is indeed a standard MPEG-1 stream, this is clear in the initialization of DirectShow (as seen below) as the inilization uses MEDIATYPE_Stream and the subtype as MEDIASUBTYPE_MPEG1System. At this point, I know the GRT file is a standard MPEG-1 stream, but looking throught the file with hexdump I could still not find any matching MPEG-1 headers. But not fat down, I found the first breakthrough that yielded results. I found the head parsing method.

MediaType settings passed to DirectShow

The first thing the header parsing method does, it to call CreateFileA with standard parameter to open a file for reading. Next, it reads 2 bytes of the file and compares them to GT, as expected it’s checking the header magic. After that, it reads in a DWORD and compares it to 1. I suspect this is a version check, but in all files that I have the field is always 1. Now is where things get interesting. It read another DWORD but thins time it uses this DWORD as a counter for a loop. While counting down, for each iteration the code reads in 8 bytes (split into 2 DWORDS) and adds them to a list with Classes::TList::Add. Once done it returns to the callee and continues. At this point I wrote a python function to parse the head to see if the list of values made any sense.

import struct
def parseGRTHeader(fd):
    fd.seek(0)
    magic, version, chunk_list_len = struct.unpack('<2sII', fd.read(10))
    if magic != 'GT':
        raise Exception('Not a GRT file, bad magic in header (should be GT)')
    if version != 1:
        raise Exception('Bad version field in header (should be 1)')
    print("Header: {0}\nVersion: {1}\nChucks: {2}".format( \
        magic, version, chunk_list_len))
    return [struct.unpack('<II', fd.read(8)) for _ in range(chunk_list_len)]
In [75]: f = open('03.grt', 'rb')

In [76]: clist = parseGRTHeader(f)
Header: GT
Version: 1
Chucks: 907

In [77]: len(clist)
Out[77]: 907

In [78]: clist[:10]
Out[78]:
[(671797238, 684549),
 (85300020, 584604),
 (211306286, 668986),
 (95893255, 821944),
 (160460287, 578712),
 (158481151, 553283),
 (460379005, 648556),
 (255951063, 840734),
 (526785614, 985286),
 (43287269, 660861)]

After reading the header and the list, I still did not get anything that looked like a MPEG-1 stream. So I decided to take a new approach. I searched the import table and looked for references to any methods that are relevant and deal with lists. I found a method that used Contnrs::TOrderedList::Count on the list, and as long as the list was not empty, it set an event. Then, by cross referencing the list object offset and the event object offset with other methods, I found a loop which loops as long as list count is non zero. Inside this loop, a call to a different readFile wrapper was done, this time within a wrapper that XORed the output of the read function with 0xFF, this XORed read method was called from several callbacks of the player object. Inside the warped read method the list is usedand for each entry, the player also passes the first list parameter to SetFilePointer and the second list parameter as the nNumberOfBytesToRead for readFile. After this, the purpose of the list was clear and the file could be reconstructed from scratch.

Chunk list reader with XOR

I wrote another simple python method that reads the data and will XOR it and store it back into a file.

def convertGRTData(fd_in, fd_out, chunk_list):
    for chunk in chunk_list:
        fd_in.seek(chunk[0])
        idata = fd_in.read(chunk[1])
        fd_out.write(''.join(chr(ord(c) ^ 0xff) for c in idata))

After running the python script I had a fully working MPEG-1 stream that could be played in any player. The reason the movie time in the VLC shot is longer than the movie time in the following image is because it’s a different movie, same format.

VLC playing the ripped MPEG movie

Now that I have defeated the GRT format, how can I copy these movies without the CD drive getting stuck on a bad sector? Well, the answer is simple. Using the following python code, I checked all the sections in the list and checked if the entire size of the GRT movie file is covered by the list in the GRT header. As the code output suggested, it was not all mapped in the list. With this “hole” in the file, the player seeks past it while playing and never tries to read the bad sectors, but a CD cloning program will fail and get stuck reading the CD. As I can now parse the GT header myself and skip the hole, I can copy the movie directly from the CD to a convenient mpg file on my computer.

# https://stackoverflow.com/a/1094933
def sizeof_fmt(num, suffix='B'):
    for unit in ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']:
        if abs(num) < 1024.0:
            return "%3.1f%s%s" % (num, unit, suffix)
        num /= 1024.0
    return "%.1f%s%s" % (num, 'Yi', suffix)

def findHolesInList(clist):
    d = dict(clist)
    s = sorted(d.keys())
    for i in range(len(s)-1):
        if (s[i]+d[s[i]]) != s[i+1]:
            print("Hole found: start={0}, size={1}".format(\
                    s[i]+d[s[i]], sizeof_fmt(s[i+1]-(s[i]+d[s[i]]))))
Hole found: start=977755, size=25.7MiB

Now here comes the kicker, I really don’t have anything to do with these movies. They are at a terrible 352x288 resolution, the actors and acting are far worse than anything you can imagine and the plot (that does not always exist) is terrible and full of doctrine, not exactly the movies I prefer to pirate. But, Why not break the DRM anyway? If anyone finds a GRT file they want to convert, I have a Github repo with a simple and dirty converter written in C (much much faster than the python shown here) GRTConvert repo.