Исследуем игру с аркадного автомата ч. 1

от автора

Уф, вот меня и разбанили на хабре, и у меня есть для вас отличная статья, так что всем привет!

Введение

Никогда не задумывались, что стоит в аркадных автоматах? Какие ОС, какие технологии используются для разработки игр? Как их защищают от копирования и модифицирования? Если да, то добро пожаловать в этот топик.

Внутренности

Итак, что же внутри нашего автомата?

  1. Обычный x86 компьютер с платой PIUIO и JAMMA
  2. Усилитель, кроссовер и эквалайзер
    image
  3. Панель со светодиодами
    image
  4. Панель управления с кнопками «TEST», «SERVICE» и крутилками эквалайзера
    image
  5. LCD или CRT экран

Нас же, конечно, в первую очередь интересует компьютер.

Есть несколько ревизий внутренностей последней версии компьютера:
Материнская плата: Gigabyte GA-945GCM-S2L / Asrock G41M-S3
Графический адаптер: Geforce 8400GS / Geforce 9300GS
Память: DDR2 / DDR3 512 MB
Процессор: Intel Celeron

Сама игра находится на винчестере. В комплекте USB-донгл SafeNet MicroDog.
image

Исследование

С чего же начать? Естественно, со снятия дампа винчестера. Это делается в линуксе одной командой:
dd if=/dev/sdX of=./dump.bin bs=1M
И идем пить чай, т.к. это займет минут 10-15. Винчестеры используются на 160 и 250 гигабайт.
Никогда ничего не делайте на работающем винчестере! Всегда нужно использовать образ!

Образ снят. Давайте запустим cfdisk на него.
image

Что же мы видим? Два раздела с файловой системой ext2, и подозрительно много неразмеченной области в начале диска. Файловая система сразу может навести на мысль, что внутри стоит что-то UNIX-подобное, с большой вероятностью это, конечно, Linux.

Давайте же запустим его в виртуалке. Я люблю qemu, поэтому использую его.

И больше ничего. Дальше либо qemu завершается с ошибкой вроде:
qemu: fatal: Trying to execute code outside RAM or ROM at 0xa5ff00d8
либо просто зависает. Честно говоря, я знал, что у этой игры привязка к винчестеру, и стало очевидно, что алгоритм достаточно простой и не использует проверку целостности данных, а сразу передает управление расшифрованным данным, а в случае с виртуалкой, просто мусору.
Далее я, скорее больше ради интереса, а не в ожидании каких-то открытий, решил посмотреть, что же лежит на тех двух разделах с файловой системой ext2. А вот что:

Раздел 1 ├── [4.0K]  game │   └── [ 25G]  _00000.BIN ├── [4.3M]  i ├── [ 16K]  lost+found ├── [   0]  n ├── [8.5M]  p ├── [7.8M]  u └── [ 22M]  x  game/_00000.BIN: data i:               data n:               empty  p:               data u:               data x:               ELF 32-bit LSB  shared object, Intel 80386, version 1 (SYSV), dynamically linked, stripped  Раздел 2: ├── [ 16K]  lost+found └── [144K]  PIUFESTAEX.INI 

Ну вот, все данные на разделе 1, кроме файла «x», являются обычными данными, либо же шифрованы. Один только «x» как-то выделяется из этой массы — это shared библиотека, которая, вероятно, подгружается игрой (на самом же деле — нет).

Что же дальше? Ну, полезли в сам винчестер.

00000000  fa 33 c0 8e d0 bc 00 7c  8b f4 50 07 50 1f fb fc  |.3.....|..P.P...| 00000010  bf 00 06 b9 00 01 f3 a5  ea 1d 06 00 00 b6 00 b9  |................| 00000020  02 00 bf 05 00 bb 00 07  b8 01 02 57 cd 13 5f 73  |...........W.._s| 00000030  0c 33 c0 cd 13 4f 75 ed  be 8a 06 eb 3b b9 03 00  |.3...Ou.....;...| 00000040  bf 05 00 bb 00 20 53 07  bb 00 00 b8 20 02 57 cd  |..... S..... .W.| 00000050  13 5f 73 0c 33 c0 cd 13  4f 75 e8 be 8a 06 eb 18  |._s.3...Ou......| 00000060  b9 ff 3f be 00 08 33 ff  ad 83 e6 bf 26 33 05 ab  |..?...3.....&3..| 00000070  49 75 f5 ea 00 00 00 20  ac 3c 00 74 0b 56 bb 07  |Iu..... .<.t.V..| 00000080  00 b4 0e cd 10 5e eb f0  eb fe 44 69 73 6b 20 49  |.....^....Disk I| 00000090  2f 4f 20 45 72 72 6f 72  00 00 00 00 00 00 00 00  |/O Error........| 000000a0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................| * 00000180  28 43 29 32 30 30 34 20  41 4e 44 41 4d 49 52 4f  |(C)2004 XXXXXXXX| 00000190  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................| * 000001b0  00 00 00 00 00 00 00 00  9b f6 31 c9 00 00 00 00  |..........1.....| 000001c0  41 8f 83 fe ff ff cf ce  61 00 b1 a1 a9 03 00 fe  |A.......a.......| 000001d0  ff ff 83 fe ff ff 6f 56  36 04 80 60 1f 00 00 00  |......oV6..`....| 000001e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................| 000001f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 55 aa  |..............U.| 00000200  50 75 6d 70 20 49 74 20  55 70 3a 20 46 69 65 73  |Xxxx Xx Xx: Xxxx| 00000210  74 61 45 78 00 00 00 00  00 00 00 00 00 00 00 00  |xxXx............| 00000220  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................| * 00000300  20 20 20 20 20 20 20 20  20 20 20 20 36 56 4d 51  |            6VMQ| 00000310  57 54 34 37 43 43 34 36  20 20 20 20 53 54 33 31  |WT47CC46    ST31| 00000320  36 30 33 31 38 41 53 20  20 20 20 20 20 20 20 20  |60318AS         | 00000330  20 20 20 20 20 20 20 20  20 20 20 20 20 20 20 20  |                | 00000340  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................| * 00000400  0e a8 36 bd 22 ac ea 0e  a9 36 bb 22 82 66 80 70  |..6."....6.".f.p| 00000410  dc 7a 36 37 c8 5d 18 36  ae fa 83 3a 74 d8 35 29  |.z67.].6...:t.5)| 00000420  11 b9 2d 25 1f c8 7d 10  07 c8 7d 21 81 34 07 ae  |..-%..}...}!.4..| ...

(Вместо «X» были название производителя и название игры. Я их убрал, чтобы этот топик не гуглился по этим данным. Если хотите, узнать, что же там было, HEX-данные я не менял.)

Что это? Объясняю.
По адресу 0-1BE располагается MBR-загрузчик, самый наипростейший, который грузит с первого нашедшего диска stage2-загрузчик, который начинается с адреса 400. Интереснейшая строчка расположена на 300-32F, очень похоже на серийный номер винчестера, версию прошивки и модель. Да что тут гадать, так и есть 😉

Описание Данные
Серийный номер (20 байт) (12 пробелов)6VMQWT47
Версия прошивки (8 байт) CC46(4 пробела)
Название модели (40 байт) ST3160318AS(29 пробелов)

Я пошел по легкому пути: не стал разбирать алгоритм работы stage2-загрузчика, а просто чуть-чуть дописал кода в qemu, чтобы он брал данные винчестера из переменных окружения DRIVE_MODEL, DRIVE_SERIAL и DRIVE_VERSION. Также, qemu позволяет дампить память гостевой машины, что нам будет полезно.

valdikss@valaptop:~/ % DRIVE_SERIAL="            6VMQWT47" DRIVE_VERSION="CC46    " DRIVE_MODEL="ST3160318AS" qemu-system-i386 disk.img -monitor stdio QEMU 1.4.1 monitor - type 'help' for more information (qemu) dump-guest-memory mem.bin (qemu) quit

Определить правильность расшифровки данных можно было чисто визуально: если ядро успешно распаковалось и запустилось, то на момент появится курсор на экране, если же и файловая система распаковалось правильно, то моргнет экран виртуальной машины, это будут пытаться запуститься иксы.
Как линуксоид, я сразу попытался переключиться на другую консоль с помощью комбинаций Alt+FN, и мне это удалось: на второй консоли был вывод иксов.
Образ в qemu успешно запустили, уже хорошо, что же дальше? Нам же нужно как-то получить файловую систему. Наверняка, вместе с ядром грузится initrd или initramfs, в которой либо и лежат все необходимые файлы, либо который расшифровывает и подключает rootfs. Что же делать? Вернемся к нашему дампу памяти и пройдемся по нему замечательной утилитой BinWalk:

DECIMAL         HEX             DESCRIPTION ------------------------------------------------------------------------------------------------------------------- 0               0x0             ELF 32-bit LSB core file Intel 80386, version 1 (SYSV) 141888          0x22A40         Copyright string: " 1999-2003 ANDAMIROang" 1649782         0x192C76        CramFS filesystem, little endian size 279239 CRC 0x42c70000, edition 20, 141723904 blocks, 1589959 files   3848752         0x3ABA30        CramFS filesystem, little endian size 4947968 version #2 sorted_dirs CRC 0x9c99ddde, edition 0, 2433 blocks, 235 files   17648271        0x10D4A8F       mcrypt 2.2 encrypted data, algorithm: blowfish-448, mode: CBC, keymode: 8bit 17752783        0x10EE2CF       mcrypt 2.2 encrypted data, algorithm: blowfish-448, mode: CBC, keymode: 8bit 17773455        0x10F338F       mcrypt 2.2 encrypted data, algorithm: blowfish-448, mode: CBC, keymode: 8bit 17966760        0x11226A8       Copyright string: " (C) 1996-2009 the UPX Team. All Rights Reserved. $l Rights Reserved. $" 18179243        0x11564AB       Copyright string: " (C) 2009 Free Software Foundation, Inc.ion, Inc." 18236316        0x116439C       ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV) 18261064        0x116A448       LZMA compressed data, properties: 0xBD, dictionary size: 16777216 bytes, uncompressed size: 33554432 bytes 18261104        0x116A470       LZMA compressed data, properties: 0xB8, dictionary size: 16777216 bytes, uncompressed size: 33554432 bytes 21033884        0x140F39C       ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV) 21061669        0x1416025       ELF 32-bit LSB no file type, no machine, (GNU/Linux) 21088103        0x141C767       ELF 21152544        0x142C320       LZMA compressed data, properties: 0x6C, dictionary size: 16777216 bytes, uncompressed size: 838860800 bytes 21435292        0x147139C       ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV) 21525404        0x148739C       ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV) 21537692        0x148A39C       ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV) 21554076        0x148E39C       ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV) 21615281        0x149D2B1       Copyright string: " (C) 2006 Free Software Foundation, Inc.ion, Inc." 21672860        0x14AB39C       ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV) 21926836        0x14E93B4       ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV) 110031566       0x68EF2CE       gzip compressed data, was "cursor.pcf", from Unix, last modified: Fri Feb 13 07:15:31 2004 116417852       0x6F0653C       CramFS filesystem, little endian size 4947968 version #2 CRC 0x86f06160, edition 16777216, 18 blocks, 0 files   121710143       0x741263F       LZMA compressed data, properties: 0x87, dictionary size: 1048576 bytes, uncompressed size: 256 bytes 121721756       0x741539C       ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV) 122005647       0x745A88F       LZMA compressed data, properties: 0x87, dictionary size: 1048576 bytes, uncompressed size: 256 bytes 122594740       0x74EA5B4       LZMA compressed data, properties: 0x7E, dictionary size: 16777216 bytes, uncompressed size: 33554432 bytes 122614684       0x74EF39C       CramFS filesystem, little endian size 4947968 version #2 sorted_dirs CRC 0x9c99ddde, edition 0, 2433 blocks, 235 files   129803164       0x7BCA39C       CramFS filesystem, little endian size 4947968 version #2 sorted_dirs CRC 0x9c99ddde, edition 0, 2433 blocks, 235 files

Ох, как же много всего!
На самом деле, все срабатывания LZMA ложные, т.к. у него нет magic number, половина ELFов тоже фейковые. А вот CramFS с 235 файлами очень похож на реальный, и, с большой вероятностью, он используется в качестве initrd.
Вытащить из памяти правильный CramFS не составляет труда. После того, как CramFS лежит у вас на винчестере в виде файла, попытаемся его распаковать. Я использовал cramfs-2.0 из пакета firmware-mod-kit.

valdikss@valaptop:~/ % cramfsck -x root cram1.bin cramfsck: crc error

Ну а что вы ожидали? Думали, все так просто будет?
Если пропатчим проверку контрольной суммы в cramfsck, мы сможем «успешно» распаковать файловую систему:

Дерево файловой системы

├── [4.0K]  bin │   ├── [   7]  ash -> busybox │   ├── [130K]  busybox │   ├── [   7]  cat -> busybox │   ├── [   7]  chmod -> busybox │   ├── [   7]  cp -> busybox │   ├── [   7]  df -> busybox │   ├── [   7]  dnsdomainname -> busybox │   ├── [   7]  echo -> busybox │   ├── [   7]  false -> busybox │   ├── [   7]  hostname -> busybox │   ├── [   7]  kill -> busybox │   ├── [   7]  ln -> busybox │   ├── [   7]  ls -> busybox │   ├── [   7]  mkdir -> busybox │   ├── [   7]  mknod -> busybox │   ├── [   7]  mount -> busybox │   ├── [   7]  mv -> busybox │   ├── [   7]  netstat -> busybox │   ├── [   7]  ping -> busybox │   ├── [   7]  ps -> busybox │   ├── [   7]  pwd -> busybox │   ├── [   7]  rm -> busybox │   ├── [   7]  sh -> busybox │   ├── [   7]  sleep -> busybox │   ├── [   7]  sync -> busybox │   ├── [   7]  true -> busybox │   ├── [   7]  umount -> busybox │   ├── [   7]  uname -> busybox │   └── [   7]  vi -> busybox ├── [4.0K]  dev │   ├── [   0]  console │   ├── [   0]  null │   ├── [   0]  tty1 │   └── [   0]  tty2 ├── [4.0K]  etc │   ├── [4.0K]  init.d │   │   ├── [  90]  mnttab │   │   ├── [ 754]  once │   │   ├── [ 412]  rcS │   │   └── [ 244]  run │   ├── [ 151]  inittab │   └── [4.0K]  X11 │       └── [  23]  xorg.conf -> /usr/lib/xorg/xorg.conf ├── [4.0K]  lib │   ├── [111K]  ld-2.10.1.so │   ├── [  12]  ld-linux.so.2 -> ld-2.10.1.so ... │   ├── [ 78K]  libz.so.1.2.3 │   └── [4.0K]  modules │       └── [ 25K]  atkbd.ko ├── [4.0K]  mnt │   ├── [4.0K]  0 │   │   └── [   0]  invalid │   ├── [4.0K]  1 │   │   └── [   0]  invalid │   └── [4.0K]  hd ├── [1.1M]  piu ├── [4.0K]  proc ├── [4.0K]  sbin │   ├── [  14]  halt -> ../bin/busybox │   ├── [  14]  ifconfig -> ../bin/busybox │   ├── [  14]  init -> ../bin/busybox │   ├── [  14]  insmod -> ../bin/busybox │   ├── [  14]  lsmod -> ../bin/busybox │   ├── [  14]  mdev -> ../bin/busybox │   ├── [  14]  poweroff -> ../bin/busybox │   ├── [  14]  reboot -> ../bin/busybox │   └── [  14]  route -> ../bin/busybox ├── [4.0K]  SETTINGS ├── [4.0K]  sys ├── [   4]  tmp -> /var ├── [4.0K]  usr │   ├── [4.0K]  bin │   │   ├── [ 18K]  amixer │   │   ├── [  17]  du -> ../../bin/busybox │   │   ├── [  17]  env -> ../../bin/busybox │   │   ├── [  17]  free -> ../../bin/busybox │   │   ├── [  17]  less -> ../../bin/busybox │   │   ├── [4.4K]  mountrd │   │   ├── [4.6K]  mount_tab │   │   ├── [  17]  telnet -> ../../bin/busybox │   │   ├── [ 25K]  usbdaemon │   │   ├── [   4]  X -> Xorg │   │   ├── [ 14K]  xinit │   │   └── [1.7M]  Xorg │   ├── [4.0K]  lib │   ├── [4.0K]  sbin │   │   └── [  17]  setlogcons -> ../../bin/busybox │   ├── [4.0K]  share │   │   ├── [4.0K]  alsa │   │   │   ├── [8.8K]  alsa.conf │   │   │   ├── [4.0K]  cards │   │   │   │   ├── [ 669]  AACI.conf │   │   │   │   ├── [ 687]  aliases.alisp │... │   │   │   │   ├── [ 839]  VXPocket.conf │   │   │   │   └── [1.3K]  YMF744.conf │   │   │   ├── [4.0K]  init │   │   │   │   ├── [1.8K]  00main │   │   │   │   ├── [6.9K]  default │   │   │   │   ├── [1.4K]  hda │   │   │   │   ├── [ 391]  help │   │   │   │   ├── [ 932]  info │   │   │   │   └── [ 11K]  test │   │   │   └── [4.0K]  pcm │   │   │       ├── [ 805]  center_lfe.conf ... │   │   │       └── [ 978]  surround71.conf │   │   └── [4.0K]  X11 │   │       └── [4.0K]  xkb │   │           ├── [4.0K]  compiled │   │           │   └── [ 11K]  server.xkm │   │           └── [4.0K]  rules │   │               ├── [ 34K]  base │   │               ├── [ 31K]  evdev │   │               └── [   4]  xorg -> base │   └── [   4]  var -> /var └── [4.0K]  var 

Вот и, вроде бы, все, думаете вы? Тестовые файлы, вроде /etc/init.d/run, вроде-бы, нормальные. Но вот ни один исполняемый файл и ни одна библиотека не запускаются. Сначала я думал, что игра использует либо модифицированное ядро, либо модифицированный libc. Патчил исполняемые файлы, смотрел, как примерно они отличаются от hello world, т.к. все они либо завершались с segmentation fault, либо, что еще хуже, c illegal hardware instruction.
Скомпилировал себе эталонный «hello world», т.к. уже с самого начала, с вызова __libc_start_main, оно улетало куда-то не туда. Затем грешил на релоки, т.к. некоторые были как будто бы побиты, и я предположил, что, возможно, модификация в этом, но нет, после правки релоков игра хоть и стала стараться запускаться, но все не то. Размышлял над этим около 3 дней. Пришел к выводу, что исполняемые файлы каким-то образом бьются в CramFS, а текстовые остаются в оригинальном виде. И был прав!

/etc/init.d/run

echo run export __GL_SYNC_TO_VBLANK=1 export force_s3tc_enable=true export LD_LIBRARY_PATH=/lib:/usr/lib:/mnt/hd/lib cd /mnt/hd/game xinit /piu /mnt/hd/game/ -- -br -quiet -logverbose 0 -verbose 0 -depth 24 -audit 0 -bs -tst -xinerama #/bin/sh

/etc/init.d/once

until /usr/bin/mount_tab /etc/init.d/mnttab; do sleep 1; done /usr/bin/mountrd insmod /usr/lib/modules/nvidia.ko insmod /usr/lib/modules/drm.ko insmod /usr/lib/modules/fb.ko insmod /usr/lib/modules/font.ko insmod /usr/lib/modules/softcursor.ko insmod /usr/lib/modules/bitblit.ko insmod /usr/lib/modules/fbcon.ko insmod /usr/lib/modules/drm_kms_helper.ko insmod /usr/lib/modules/cfbcopyarea.ko insmod /usr/lib/modules/cfbimgblt.ko insmod /usr/lib/modules/cfbfillrect.ko insmod /usr/lib/modules/i915.ko mkdir /dev/dri ln -s /dev/card0 /dev/dri/card0 /usr/bin/usbdaemon amixer set Master 80% unmute amixer set PCM 75% unmute amixer set Front 90% unmute insmod /lib/modules/atkbd.ko

/etc/init.d/run

setlogcons 2 mount -n -t tmpfs -o size=128k var /var mkdir /var/log mkdir /var/run mkdir /var/run/microdog mount -t sysfs sysfs /sys mount -t proc proc /proc mount -t usbfs none /proc/bus/usb mount -t tmpfs mdev /dev mkdir /dev/pts mount -t devpts devpts /dev/pts echo /sbin/mdev > /proc/sys/kernel/hotplug mdev -s mkdir /dev/snd cp -a /dev/controlC* /dev/snd cp -a /dev/pcmC* /dev/snd cp -a /dev/timer /dev/snd

Т.к. на руках у меня был оригинальный CramFS, и, вспомнив, что я могу попасть в консоль ОС в виртуальной машине просто нажав Alt+F2, я решил попробовать заменить оригинальный /etc/init.d/run, заменив вызов xinit на /bin/sh. Но нам же нужно каким-то образом засунуть модифицированный CramFS обратно в образ. Поискав строку «Compressed RamFS» в образе винчестера, я был несколько удивлен, что он лежит незашифрованным! Только, конечно, не просто так, а с «паддингом», весь файл на винчестере разбит на блоки по 32КБ данных и 512 байт пустоты. Ну, это, вроде бы, не проблема!

Так уж получилось, что изменил я не только /etc/init.d/run, но и /etc/init.d/once, закомментировав вызовы mount_tab, mountrd и usbdaemon, и как-то не особо и обратил внимание на цикл в вызове mount_tab.

Итак, отредактировали /etc/init.d/run, собрали CramFS, записали ее в образ, И-и-и!… ничего. Ладно, собрал ФС с оригинальным /etc/init.d/run, и начал искать отличия в файлах. Как оказалось, их было много, я долго пытался понять, в чем же дело, оказывается, cramfs-2.0 из firmware-mod-kit собирает чуточку иначе. Хорошо, скачал обычный cramfs-tools из репозиториев, собрал им, и вдруг увидел, что, помимо контрольной суммы, различается последний байт у файлов. У оригинального файла 0x80, а у собранного мной, конечно же, 0x00. Удивленный таким положением дел, заменил в своем файле последний байт, засунул его в образ винчестера и УРА! Образ запустился с моей модифицированной ФС, и я получил консоль. Никакой сложности простое копирование файлов не создало, запускные файлы теперь действительно запускались на моей linux-системе, и именно на этом шаге я понял, что, действительно, CramFS специальным образом изменяет исполняемые файлы.

Бегло посмотрев на mount_tab, mountrd и usbdaemon в IDA PRO, на тот момент я был уверен, что mount_tab просто монтирует файловую систему в нужные места (те два раздела на винчестере, первый в /mnt/game, а второй в /SETTINGS), mountrd сканирует AGP и PCI шины, расшифровывает, загружает в память и монтирует в /usr/lib один из файлов «p», «i» или «u», лежащих на первом разделе, которые содержат драйвера для видеокарты, а usbdaemon обеспечивает работу с USB-донглом посредством UNIX-сокетов.

Ложная радость

Сижу, исследую запускной файл игры, который вытащил копированием, и у меня начали закрадываться подозрения. Какой-то он не такой, упоминания это версии игры, которую я исследую, нет, и вообще, как-то что-то не то. Понял, что этот файл принадлежит другой версии игры, и выдвинул предположение, что, видимо, после проверки USB-донгла он откуда-то берет и расшифровывает другой файл. Все оказалось куда смешнее.
Помните тот цикл при вызове mount_tab? При первом запуске он действительно просто монтирует файловые системы, но при повторном запуске он читает с винчестера некоторое количество данных со смещения 0x1F80200 или 0x2080200, в зависимости от заголовка по первому смещению, расшифровывает эти данные, и прямо в памяти подменяет запускной файл игры в CramFS на правильный. Вот этот ход мне действительно понравился! Я был в ярости, но, в то же время, горд находчивостью разработчиков.

Заключение

Статья получилась несколько скомканной и состоит скорее из моих мыслей, догадок и наблюдений. Надеюсь, ее было интересно читать. Часть 2 будет чисто техническая, и, возможно, будет содержать разбор файловой системы игры и отвязывание ее от USB-донгла.
Вот вам видео, напоследок.

ссылка на оригинал статьи http://habrahabr.ru/post/179067/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *