1 added
1 removed
Original
2026-01-01
Modified
2026-03-10
1
<p>В этой заметке я хочу поделиться с вами решением одного из заданий по<strong>CTF</strong>. Команды, участвующие в соревнованиях, должны были расшифровать изображение под названием 'derrorim_enc.bmp'. Средство, применённое для шифрования изображения, было известно - Shadelt9000.exe, однако дескриптор обнаружить не удалось. Вот это изображение:</p>
1
<p>В этой заметке я хочу поделиться с вами решением одного из заданий по<strong>CTF</strong>. Команды, участвующие в соревнованиях, должны были расшифровать изображение под названием 'derrorim_enc.bmp'. Средство, применённое для шифрования изображения, было известно - Shadelt9000.exe, однако дескриптор обнаружить не удалось. Вот это изображение:</p>
2
<p>При ближайшем рассмотрении файла Shadelt9000.exe становится ясно, что приложение использует OpenGL. Также есть копирайт inflate 1.2.8 Copyright 1995-2013 Mark Adler, указывающий на то, что в программе используется популярная библиотека компрессии zlib.</p>
2
<p>При ближайшем рассмотрении файла Shadelt9000.exe становится ясно, что приложение использует OpenGL. Также есть копирайт inflate 1.2.8 Copyright 1995-2013 Mark Adler, указывающий на то, что в программе используется популярная библиотека компрессии zlib.</p>
3
<p>Если в дизассемблере посмотреть, откуда идут обращения к функциям zlib, можно довольно быстро найти вот такой кусок кода:</p>
3
<p>Если в дизассемблере посмотреть, откуда идут обращения к функциям zlib, можно довольно быстро найти вот такой кусок кода:</p>
4
<p>По адресам 0x47F660 и 0x47F7B8 расположены массивы данных, упакованные zlib. Распакуем их:</p>
4
<p>По адресам 0x47F660 и 0x47F7B8 расположены массивы данных, упакованные zlib. Распакуем их:</p>
5
from zlib import decompress as unZ base = 0x47C000 - 0x7AE00 # data section base ab=open("ShadeIt9000.exe", "rb").read() open("1.txt", "w").write(unZ(ab[0x47F660-base:],-15)) open("2.txt", "w").write(unZ(ab[0x47F7B8-base:],-15))<p>После распаковки файл 1.txt содержит<strong>пиксельный шейдер</strong>:</p>
5
from zlib import decompress as unZ base = 0x47C000 - 0x7AE00 # data section base ab=open("ShadeIt9000.exe", "rb").read() open("1.txt", "w").write(unZ(ab[0x47F660-base:],-15)) open("2.txt", "w").write(unZ(ab[0x47F7B8-base:],-15))<p>После распаковки файл 1.txt содержит<strong>пиксельный шейдер</strong>:</p>
6
#version 330 uniform sampler2D u_texture; uniform sampler2D u_gamma; varying vec4 texCoord0; varying vec3 v_param; uint func(vec3 co){ return uint(fract(sin(dot(co ,vec3(17.1684, 94.3498, 124.9547))) * 68431.4621) * 255.); } uvec3 rol(uvec3 value, int shift) { return (value << shift) | (value >> (8 - shift)); } const uvec3 m = uvec3(0xff); void main() { uvec3 t = uvec3(texture2D(u_texture, vec2(texCoord0)).rgb * 0xff) & m; uvec3 g = uvec3(texture2D(u_gamma, vec2(texCoord0)).rgb * 0xff) & m; int s = int(mod(func(v_param), 8)); t = rol(t, s); vec3 c = vec3((t ^ g) & m) / 0xff; gl_FragColor = vec4(c, 1.); }<p>Файл 2.txt содержит<strong>вершинный шейдер</strong>:</p>
6
#version 330 uniform sampler2D u_texture; uniform sampler2D u_gamma; varying vec4 texCoord0; varying vec3 v_param; uint func(vec3 co){ return uint(fract(sin(dot(co ,vec3(17.1684, 94.3498, 124.9547))) * 68431.4621) * 255.); } uvec3 rol(uvec3 value, int shift) { return (value << shift) | (value >> (8 - shift)); } const uvec3 m = uvec3(0xff); void main() { uvec3 t = uvec3(texture2D(u_texture, vec2(texCoord0)).rgb * 0xff) & m; uvec3 g = uvec3(texture2D(u_gamma, vec2(texCoord0)).rgb * 0xff) & m; int s = int(mod(func(v_param), 8)); t = rol(t, s); vec3 c = vec3((t ^ g) & m) / 0xff; gl_FragColor = vec4(c, 1.); }<p>Файл 2.txt содержит<strong>вершинный шейдер</strong>:</p>
7
attribute vec3 a_param; varying vec4 texCoord0; varying vec3 v_param; void main(void) { gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; texCoord0 = gl_MultiTexCoord0; v_param = a_param; }<p>Главная информация о пиксельном шейдере выделена красным:</p>
7
attribute vec3 a_param; varying vec4 texCoord0; varying vec3 v_param; void main(void) { gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; texCoord0 = gl_MultiTexCoord0; v_param = a_param; }<p>Главная информация о пиксельном шейдере выделена красным:</p>
8
<p>В переменной t оказывается текущий элемент обрабатываемой текстуры (входного файла), а в переменной g - текущий элемент гаммы (сгенерированной псевдослучайным образом).</p>
8
<p>В переменной t оказывается текущий элемент обрабатываемой текстуры (входного файла), а в переменной g - текущий элемент гаммы (сгенерированной псевдослучайным образом).</p>
9
<p>В переменной s мы видим некоторое значение, используемое позже для циклического сдвига s.</p>
9
<p>В переменной s мы видим некоторое значение, используемое позже для циклического сдвига s.</p>
10
<p>Выходное значение фактически вычисляется как</p>
10
<p>Выходное значение фактически вычисляется как</p>
11
-
<p>Причём, если запускать программу несколько раз с одним и тем же входным файлом, то для каждого элемента значение g будет меняться от запуска к запуску, а t и s будут остава��ься одними и теми же.</p>
11
+
<p>Причём, если запускать программу несколько раз с одним и тем же входным файлом, то для каждого элемента значение g будет меняться от запуска к запуску, а t и s будут оставаться одними и теми же.</p>
12
<p>Найдём, как генерируется гамма:</p>
12
<p>Найдём, как генерируется гамма:</p>
13
unsigned char *pbGamma = malloc(cbGamma); srand(time(0)); for (i = 0; i < cbGamma; i++) { pbGamma[i] = rand(); }<p>Видно, что она зависит от текущего времени.</p>
13
unsigned char *pbGamma = malloc(cbGamma); srand(time(0)); for (i = 0; i < cbGamma; i++) { pbGamma[i] = rand(); }<p>Видно, что она зависит от текущего времени.</p>
14
<p>Из исходного архива можно узнать, что файл derrorim_enc.bmp создан 21.01.2014 в 18:37:52.</p>
14
<p>Из исходного архива можно узнать, что файл derrorim_enc.bmp создан 21.01.2014 в 18:37:52.</p>
15
<p>Получаем значение, которое в тот момент вернула бы функция time():</p>
15
<p>Получаем значение, которое в тот момент вернула бы функция time():</p>
16
>>> import time >>> print hex(int(time.mktime((2014,1,21, 18,37,52, 0,0,0))))<p><strong>0x52de8640</strong></p>
16
>>> import time >>> print hex(int(time.mktime((2014,1,21, 18,37,52, 0,0,0))))<p><strong>0x52de8640</strong></p>
17
<p>Теперь копируем файл ShadeIt9000.exe в ShadeIt9000_f.exe и исправляем его.</p>
17
<p>Теперь копируем файл ShadeIt9000.exe в ShadeIt9000_f.exe и исправляем его.</p>
18
<p>По смещению 00015557 надо байты:</p>
18
<p>По смещению 00015557 надо байты:</p>
19
<p>заменить на:</p>
19
<p>заменить на:</p>
20
<p>Это эквивалентно замене:</p>
20
<p>Это эквивалентно замене:</p>
21
call _time на mov eax,52de8640h.<p>Таким образом мы получили версию ShadeIt9000_f, которая будет всегда шифровать с той же гаммой, какая была в момент шифрования интересующего нас файла.</p>
21
call _time на mov eax,52de8640h.<p>Таким образом мы получили версию ShadeIt9000_f, которая будет всегда шифровать с той же гаммой, какая была в момент шифрования интересующего нас файла.</p>
22
<p>Теперь нужно подготовить значения, которые помогут расшифровать изображение:</p>
22
<p>Теперь нужно подготовить значения, которые помогут расшифровать изображение:</p>
23
import os bmp=open("derrorim_enc.bmp", "rb").read() hdr = bmp[:0x36] abData = bytearray(bmp[0x36:]) cbBody = len(bmp) - len(hdr) open("00.bmp", "wb").write(hdr + '\0'*cbBody) open("XX.bmp", "wb").write(hdr + '\2'*cbBody) os.system("ShadeIt9000_f.exe 00.bmp") os.system("ShadeIt9000_f.exe XX.bmp")<p>В файле 00_enc.bmp окажется результат шифрования картинки, состоящий из нулевых байтов. Это и будет гамма в чистом виде.</p>
23
import os bmp=open("derrorim_enc.bmp", "rb").read() hdr = bmp[:0x36] abData = bytearray(bmp[0x36:]) cbBody = len(bmp) - len(hdr) open("00.bmp", "wb").write(hdr + '\0'*cbBody) open("XX.bmp", "wb").write(hdr + '\2'*cbBody) os.system("ShadeIt9000_f.exe 00.bmp") os.system("ShadeIt9000_f.exe XX.bmp")<p>В файле 00_enc.bmp окажется результат шифрования картинки, состоящий из нулевых байтов. Это и будет гамма в чистом виде.</p>
24
<p>В файле XX_enc.bmp окажется результат шифрования картинки, состоящий из байтов со значением 2. Это поможет нам узнать, на сколько битов циклически сдвигался каждый байт.</p>
24
<p>В файле XX_enc.bmp окажется результат шифрования картинки, состоящий из байтов со значением 2. Это поможет нам узнать, на сколько битов циклически сдвигался каждый байт.</p>
25
<p>Наконец, расшифровываем Shadelt9000:</p>
25
<p>Наконец, расшифровываем Shadelt9000:</p>
26
def rol(v,i): return (((v<<i) & 0xFF) | ((v>>(8-i)) & 0xFF)) def ror(v,i): return (((v>>i) & 0xFF) | ((v<<(8-i)) & 0xFF)) dRot = {rol(1,i):i for i in xrange(8)} bmp=open("derrorim_enc.bmp", "rb").read() hdr = bmp[:0x36] abData = bytearray(bmp[0x36:]) abGamma = bytearray(open("00_enc.bmp", "rb").read()[0x36:]) abRot = bytearray(open("XX_enc.bmp", "rb").read()[0x36:]) for i,b in enumerate(abGamma): abRot[i] = dRot[abRot[i] ^ b] for i,b in enumerate(abGamma): abData[i] = ror(abData[i] ^ b, abRot[i]) open("derrorim.bmp", "wb").write(hdr + str(abData))<p>Получаем:</p>
26
def rol(v,i): return (((v<<i) & 0xFF) | ((v>>(8-i)) & 0xFF)) def ror(v,i): return (((v>>i) & 0xFF) | ((v<<(8-i)) & 0xFF)) dRot = {rol(1,i):i for i in xrange(8)} bmp=open("derrorim_enc.bmp", "rb").read() hdr = bmp[:0x36] abData = bytearray(bmp[0x36:]) abGamma = bytearray(open("00_enc.bmp", "rb").read()[0x36:]) abRot = bytearray(open("XX_enc.bmp", "rb").read()[0x36:]) for i,b in enumerate(abGamma): abRot[i] = dRot[abRot[i] ^ b] for i,b in enumerate(abGamma): abData[i] = ror(abData[i] ^ b, abRot[i]) open("derrorim.bmp", "wb").write(hdr + str(abData))<p>Получаем:</p>
27
<h2>И ещё один способ решения</h2>
27
<h2>И ещё один способ решения</h2>
28
<p>Выше был описан верный, но не самый эффективный путь решения задания. Есть способ короче.</p>
28
<p>Выше был описан верный, но не самый эффективный путь решения задания. Есть способ короче.</p>
29
<p>Сразу за вершинным шейдером по адресам 0x47F848 и 0x47F9A0 лежит упакованный zlib-код пиксельного и вершинного шейдера для выполнения обратного преобразования. Возможно, он был случайно забыт разработчиком задания. А может, был оставлен намеренно.</p>
29
<p>Сразу за вершинным шейдером по адресам 0x47F848 и 0x47F9A0 лежит упакованный zlib-код пиксельного и вершинного шейдера для выполнения обратного преобразования. Возможно, он был случайно забыт разработчиком задания. А может, был оставлен намеренно.</p>
30
<p>Коды вершинного шейдера для шифрования и расшифровывания идентичны, так что трогать их не имеет смысла.<strong>А что будет, если подменить пиксельный шейдер?</strong></p>
30
<p>Коды вершинного шейдера для шифрования и расшифровывания идентичны, так что трогать их не имеет смысла.<strong>А что будет, если подменить пиксельный шейдер?</strong></p>
31
<p>Копируем ShadeIt9000_f.exe в ShadeIt9000_d.exe и исправляем его:</p>
31
<p>Копируем ShadeIt9000_f.exe в ShadeIt9000_d.exe и исправляем его:</p>
32
00015775: 60 F6 ==> 48 F8<p>Затем запускаем ShadeIt9000_d.exe derrorim_enc.bmp. И получаем на выходе расшифрованный файл derrorim_enc_enc.bmp, который (за исключением мелких артефактов)<strong>совпадает с тем, который мы расшифровали скриптом на Python</strong>.</p>
32
00015775: 60 F6 ==> 48 F8<p>Затем запускаем ShadeIt9000_d.exe derrorim_enc.bmp. И получаем на выходе расшифрованный файл derrorim_enc_enc.bmp, который (за исключением мелких артефактов)<strong>совпадает с тем, который мы расшифровали скриптом на Python</strong>.</p>
33
<p>На сегодня всё, спасибо!</p>
33
<p>На сегодня всё, спасибо!</p>
34
<p><em>За подготовку материала<a>автор</a>выражает благодарность CTF-сообществу.</em></p>
34
<p><em>За подготовку материала<a>автор</a>выражает благодарность CTF-сообществу.</em></p>
35
35