HTML Diff
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 &lt;&lt; shift) | (value &gt;&gt; (8 - shift)); } const uvec3 m = uvec3(0xff); void main() { uvec3 t = uvec3(texture2D(u_texture, vec2(texCoord0)).rgb * 0xff) &amp; m; uvec3 g = uvec3(texture2D(u_gamma, vec2(texCoord0)).rgb * 0xff) &amp; m; int s = int(mod(func(v_param), 8)); t = rol(t, s); vec3 c = vec3((t ^ g) &amp; 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 &lt;&lt; shift) | (value &gt;&gt; (8 - shift)); } const uvec3 m = uvec3(0xff); void main() { uvec3 t = uvec3(texture2D(u_texture, vec2(texCoord0)).rgb * 0xff) &amp; m; uvec3 g = uvec3(texture2D(u_gamma, vec2(texCoord0)).rgb * 0xff) &amp; m; int s = int(mod(func(v_param), 8)); t = rol(t, s); vec3 c = vec3((t ^ g) &amp; 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 &lt; cbGamma; i++) { pbGamma[i] = rand(); }<p>Видно, что она зависит от текущего времени.</p>
13 unsigned char *pbGamma = malloc(cbGamma); srand(time(0)); for (i = 0; i &lt; 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 &gt;&gt;&gt; import time &gt;&gt;&gt; print hex(int(time.mktime((2014,1,21, 18,37,52, 0,0,0))))<p><strong>0x52de8640</strong></p>
16 &gt;&gt;&gt; import time &gt;&gt;&gt; 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&lt;&lt;i) &amp; 0xFF) | ((v&gt;&gt;(8-i)) &amp; 0xFF)) def ror(v,i): return (((v&gt;&gt;i) &amp; 0xFF) | ((v&lt;&lt;(8-i)) &amp; 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&lt;&lt;i) &amp; 0xFF) | ((v&gt;&gt;(8-i)) &amp; 0xFF)) def ror(v,i): return (((v&gt;&gt;i) &amp; 0xFF) | ((v&lt;&lt;(8-i)) &amp; 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 ==&gt; 48 F8<p>Затем запускаем ShadeIt9000_d.exe derrorim_enc.bmp. И получаем на выходе расшифрованный файл derrorim_enc_enc.bmp, который (за исключением мелких артефактов)<strong>совпадает с тем, который мы расшифровали скриптом на Python</strong>.</p>
32 00015775: 60 F6 ==&gt; 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