Уф, вот меня и разбанили на хабре, и у меня есть для вас отличная статья, так что всем привет!
Введение
Никогда не задумывались, что стоит в аркадных автоматах? Какие ОС, какие технологии используются для разработки игр? Как их защищают от копирования и модифицирования? Если да, то добро пожаловать в этот топик.
Внутренности
Итак, что же внутри нашего автомата?
- Обычный x86 компьютер с платой PIUIO и JAMMA
- Усилитель, кроссовер и эквалайзер
- Панель со светодиодами
- Панель управления с кнопками «TEST», «SERVICE» и крутилками эквалайзера
- LCD или CRT экран
Нас же, конечно, в первую очередь интересует компьютер.
Есть несколько ревизий внутренностей последней версии компьютера:
Материнская плата: Gigabyte GA-945GCM-S2L / Asrock G41M-S3
Графический адаптер: Geforce 8400GS / Geforce 9300GS
Память: DDR2 / DDR3 512 MB
Процессор: Intel Celeron
Сама игра находится на винчестере. В комплекте USB-донгл SafeNet MicroDog.
Исследование
С чего же начать? Естественно, со снятия дампа винчестера. Это делается в линуксе одной командой:
dd if=/dev/sdX of=./dump.bin bs=1M
И идем пить чай, т.к. это займет минут 10-15. Винчестеры используются на 160 и 250 гигабайт.
Никогда ничего не делайте на работающем винчестере! Всегда нужно использовать образ!
Образ снят. Давайте запустим cfdisk на него.
Что же мы видим? Два раздела с файловой системой 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, а текстовые остаются в оригинальном виде. И был прав!
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
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
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-донгла.
Вот вам видео, напоследок.
Пожалуйста, не пишите в комментариях названия игры, чтобы она не гуглилась. Спасибо!
Автор: ValdikSS