Всем привет, сам сейф и задача по нему, были в одном ctf.
Заранее кто не знаком с js, особо впечатлительные С-кодеры, не падайте, от того что происходит в коде, в С все по другому.))))(Если что С и Rust > other code lang.)
Мы имеем простой HTML, в нем реализован «JS Safe», который хранит данные в localStorage браузера.
Мы не извлечем от туда данные, так как они храняться локально.
Но этот сейф, открываеться только по пaролю владельца. Как мы видим это тут.
Перевод:
На странице мы видим, анимацию куба и input для пароля.
Наша точка входа, onchange обработчик который при изминение <input>, выполняет func open_safe().
Иследуем функцию open_safe().
Строка блокирует инпут для дальнейшего ввода.
Сразу уберем ее, чтоб могли иследовать, без перезагрузки.
Видим эту строчку:
Переменной password присваеваится результат exec().
Метод exec() выполняет поиск сопоставления регулярного выражения в указанной строке.
Возвращает массив с результатами или null.
В нашем случае он вернул null.
Сейф разблокируется, если пароль соответствует регулярному выражению /^CTF{([0-9a-zA-Z_@!?-]+)}$/ и вызов функции x(password[1]) будет true.
Мы проваливаемся в if и возвращаемся из open_safe не доходя до вызова x(password[1]).
Остальная часть кода просто расшифровывает паролем, зашифрованные данные, которых у нас нет, поскольку они в localStorage владельца сейфа.
При вводе любой строки которая подходит под регулярку к примеру "CTF{byt3r0se}", будет вызов x(password[1])
password[1] это "byt3r0se" из строки "CTF{byt3r0se}"
Если отладка не будет открыта F12 то мы провалимся в другую петлю в функции с(), но об этом далее. Которая преведет к утечке памяти.
Идем дальше функция x(). Начинеться интересное.
В функц х() нас при открытой отладочной панели, будет ждать петля для раздражения, так как она будет запускать дебаг, и чтоб пройти цикл надо проходить его в ручную но мы уберем его вдальнейшем.
Присваиваем пременной a=1000 в консоле или scope и минуем петлю, главное присвоить когда уже сделали шаг a++,
так как если перепишим переменной a, значение которой 0 до того как сделаем инкримент по завершению прохода,
а будет 1001 а это не соответсвует условию выхода из цикла a!=1000 так и будем в лупе.
Далее вызов h() которая с помошь побитовых операций возвращает в переменую x строку длиной 4 символа каждый из которых имеет код символа меньше 256.
Получаеться х это аргумент функции х(), вызывая h() мы х приводим к строке это бесмысленно, входной аргумент x фунции х() строку приводить к строке,
В h() мы видим что аргумент s который мы передали х при вызове вовсе не наша строка, а текстовое предстовление кода нашей функции. Как так?
Дело в том что аргумнт х в функции х() это переменая из алфавита кирилицы х, а остальные х в коде латиница которая и есть код функции х() которую приводит к строк перед вызовом h()
Результат функции h() зависит от тела функции х(), таким образом если мы изменим код функции х() выходной ключ будет другим.
Мы его просто просто статически в пишем в переменую х в дальнейшем без вызова h() так как мы его уже знаем, тем самым сможем спокойно менять тело функции х().
source красивая регулярка,
Переопределили метод объекта source, который возвращает строковое представление объекта.
Зашли в try и при вызове console.log('debug', source); мы передаем source который console.log приводит к строке используя нами переопределенный метод toString который возвращает вызов c(source,x).
В функции с() опять петля так как i != a.length будет true постоянно, a.length == undefined соответственно не будет
равно i которое number. это потому-что c(source,x) мы передали source объкт а его длинна undefined.
Удалим эту анти отладочную карусель.
Далее with - ищет unqualified имя у нас это source, исследуя цепочку областей видимости, связанную
с выполнением скрипта или функции, сожержащих это имя. Оператор 'with' добавляет данный объект в начало цепочки областей видимости в ходе исследования тела его оператора. Если имя используемое в теле соответствует свойству в цепочке областей видимости, тогда имя привязывается к свойству и объекту, содержащему это свойство.
В нашем случае source станет свойство source однойменной переменной source.
Так source.source возврашает нам строку регулярки без слешей.
"Ӈ#7ùª9¨M¤À.áÔ¥6¦¨¹.ÿÓÂ.Ö£JºÓ¹WþÊmãÖÚG¤¢dÈ9&òªћ#³1᧨"
Теперь получатся source = source.source
Далее мы попадаем в eval вызывает код из строки, где будет еще один eval который вызовет c(), она соединяет строки a и b вместе, циклически изменяя b по мере необходимости до длины a, она возврашает строку.
Строку результат с() выполнит второй eval.
Вот что за строку нам вернула с(). "х==c('¢×&Ê´cʯ¬$¶³´}ÍÈ´T©Ð8ͳÍ|Ô÷aÈÐÝ&¨þJ',h(х))//᧢" теперь ее выполняет второй eval
Но как мы узнаем ключ?
Взглянем на строку которую возвращает c(source,x)
"х==c('¢×&Ê´cʯ¬$¶³´}ÍÈ´T©Ð8ͳÍ|Ô÷aÈÐÝ&¨þJ',h(х))//᧢"
Взглянем на переменные x в начале и конце строки их код 1093, кририлица значит тут проверяеться наша переменная обозначенная русской буквой х, которая аргумет функции х(), значение которой "byt3r0se".
И опять вызов с() с новым аргументом и с новым ключом так как в h() передастся аргумент с нашим паролем, а не как в первом случае с телом функции.
И при сравнение с нашим паролем х==c('¢×&Ê´cʯ¬$¶³´}ÍÈ´T©Ð8ͳÍ|Ô÷aÈÐÝ&¨þJ',h(х)) будет false.
Перепиши фунцию х()
Изменения: переменая а та самая
for (a = 0; a != 1000; a++)
debugger ;
Она нам нужна для работы в h() так как мы убрали первый вызов h() где используеться a=1000, после выхода из h() она будет a=2714
Переменая x та самая
x = h(str(x));
h() возвращает ключ из 4 символов, при дифолтном теле функции ключ всегда один и тот же.
Теперь нам надо найти значение при котором х == c('¢×&Ê´cʯ¬$¶³´}ÍÈ´T©Ð8ͳÍ|Ô÷aÈÐÝ&¨þJ', h(х)) будет тру
Взглянем на h() она возвращает ключ в диапозоне 0-0xff длиной 4 байта.
В c(), результат функции зависит от второго аргумента b(который есть h(х)), так как первый нам уже известен. И так, нам нужно найти четыре числа a,b,c,d в диапазоне 0-0xff для нашего ключа b[a,b,c,d].
Мы знаем пароль состоит из буквенно-цифровых символов и знаков препинания из регулярярки /^CTF{( [0-9a-zA-Z_@!?-]+ )}$/
c() xor это значение Unicode для символов a с теми, что в b, но мы знаем, какой индекс b будет использоваться для поиска c получаеться для b[0] будут c[0] c[4] c[8] c[12] и тд
Для с = a^b[i%4] получаеться шаг 4 для каждого индекса b.
Получаеться мы можем сбрутить для каждого индекса b значение c с шагом для с[i+4] которое подойдет под regex.
Теперь напиши скрипт который сбрутит нам возможные пароли:
Получилось 2 результата:
CTF{_BN37!-vR951N!-h5!-ATEI-NXTiabnt-HD3Ukg_}
CTF{_N3x7-v3R51ON-h45-AnTI-4NTi-ant1-D3bUg_}
Думаю понятно какой из них тру пароль.
Теперь мы открыли криптосейф.
На этом все.
Кто хочет оказать поддержку.
bircoin
grin
monero
other
Писать в лс, для получения реквизитов.
Заранее кто не знаком с js, особо впечатлительные С-кодеры, не падайте, от того что происходит в коде, в С все по другому.))))(Если что С и Rust > other code lang.)
Мы имеем простой HTML, в нем реализован «JS Safe», который хранит данные в localStorage браузера.
Мы не извлечем от туда данные, так как они храняться локально.
Но этот сейф, открываеться только по пaролю владельца. Как мы видим это тут.
HTML:
<title>JS safe v2.0 - the leading localStorage based safe solution with high grade JS anti-debug technology</title>
<!--
Advertisement:
Looking for a hand-crafted, browser based virtual safe to store your most
interesting secrets? Look no further, you have found it. You can order your own
by sending a mail to js_safe@example.com. When ordering, please specify the
password you'd like to use to open and close the safe. We'll hand craft a
unique safe just for you, that only works with your password of choice.
-->
Перевод:
HTML:
<title>JS safe v2.0 - ведущее localStorage на основе безопасного решения, с высококлассной JS анти-отладочной технологией</title>
<!--
Реклама:
В поисках созданного вручную, виртуального сейфа на основе браузера для хранения ваших самых
интересных секретов? Не ищите дальше, вы нашли это. Вы можете заказать свой собственный,
отправив письмо по адресу js_safe@example.com. При заказе, пожалуйста, укажите
пароль, который вы хотите использовать, чтобы открыть и закрыть сейф. Мы сделаем вручную
уникальный сейф только для вас, который работает только с вашим выброным паролем.
-->
На странице мы видим, анимацию куба и input для пароля.
Наша точка входа, onchange обработчик который при изминение <input>, выполняет func open_safe().
<input id="keyhole" autofocus onchange="open_safe()" placeholder="?">Иследуем функцию open_safe().
JavaScript:
function open_safe() {
keyhole.disabled = true;
password = /^CTF{([0-9a-zA-Z_@!?-]+)}$/.exec(keyhole.value);
if (!password || !x(password[1])) return document.body.className = 'denied';
document.body.className = 'granted';
password = Array.from(password[1]).map(c => c.charCodeAt());
encrypted = JSON.parse(localStorage.content || '');
content.value = encrypted.map((c,i) => c ^ password[i % password.length]).map(String.fromCharCode).join('')
}
Строка блокирует инпут для дальнейшего ввода.
keyhole.disabled = true;Сразу уберем ее, чтоб могли иследовать, без перезагрузки.
Видим эту строчку:
password = /^CTF{([0-9a-zA-Z_@!?-]+)}$/.exec(keyhole.value);Переменной password присваеваится результат exec().
Метод exec() выполняет поиск сопоставления регулярного выражения в указанной строке.
Возвращает массив с результатами или null.
В нашем случае он вернул null.
Сейф разблокируется, если пароль соответствует регулярному выражению /^CTF{([0-9a-zA-Z_@!?-]+)}$/ и вызов функции x(password[1]) будет true.
if (!password || !x(password[1])) return document.body.className = 'denied';Мы проваливаемся в if и возвращаемся из open_safe не доходя до вызова x(password[1]).
Остальная часть кода просто расшифровывает паролем, зашифрованные данные, которых у нас нет, поскольку они в localStorage владельца сейфа.
При вводе любой строки которая подходит под регулярку к примеру "CTF{byt3r0se}", будет вызов x(password[1])
password[1] это "byt3r0se" из строки "CTF{byt3r0se}"
Если отладка не будет открыта F12 то мы провалимся в другую петлю в функции с(), но об этом далее. Которая преведет к утечке памяти.
Идем дальше функция x(). Начинеться интересное.
JavaScript:
function x(х){
ord = Function.prototype.call.bind(''.charCodeAt);// при вызове ord возвращает числовое значение Юникода.
chr = String.fromCharCode; // при вызове chr вернет строку, созданную из последовательности значений единиц кода UTF-16.
str = String;// конструктор строк
function h(s) {
//функция, которая преобразует произвольную непустую строку в строку длиной 4 символа
//каждый из которых имеет код <256.
for (i = 0; i != s.length; i++) {
a = ((typeof a == 'undefined' ? 1 : a) + ord(str(s[i]))) % 65521;
b = ((typeof b == 'undefined' ? 0 : b) + a) % 65521;
}
return chr(b>>8) + chr(b&0xFF) + chr(a>>8) + chr(a&0xFF);
}
function c(a, b, c){
// xor соединяет строки a и b вместе, циклически изменяя b по мере необходимости до длины a (третий аргумент
//никогда не передается этой функции и представляет собой простой метод запутывания
for (i = 0; i != a.length; i++)
c = (c || '') + chr(ord(str(a[i])) ^ ord(str(b[i % b.length])));
return c
}
for (a=0; a!=1000; a++) debugger; //петля которая будет вызывать дебагер при открытой отладке
x = h(str(x));
source = /Ӈ#7ùª9¨M¤À.áÔ¥6¦¨¹.ÿÓÂ.Ö£JºÓ¹WþÊmãÖÚG¤¢dÈ9&òªћ#³1᧨/;
source.toString = function() {
return c(source,x);
};
try {
console.log('debug', source);
with (source)
return eval('eval(c(source,x))');
} catch (e) {
}
// неявно return undefined (который falsе)
}
В функц х() нас при открытой отладочной панели, будет ждать петля для раздражения, так как она будет запускать дебаг, и чтоб пройти цикл надо проходить его в ручную но мы уберем его вдальнейшем.
for (a=0; a!=1000; a++) debugger; //запустит дебагер браузера для раздражения в петлеПрисваиваем пременной a=1000 в консоле или scope и минуем петлю, главное присвоить когда уже сделали шаг a++,
так как если перепишим переменной a, значение которой 0 до того как сделаем инкримент по завершению прохода,
а будет 1001 а это не соответсвует условию выхода из цикла a!=1000 так и будем в лупе.
Далее вызов h() которая с помошь побитовых операций возвращает в переменую x строку длиной 4 символа каждый из которых имеет код символа меньше 256.
Получаеться х это аргумент функции х(), вызывая h() мы х приводим к строке это бесмысленно, входной аргумент x фунции х() строку приводить к строке,
В h() мы видим что аргумент s который мы передали х при вызове вовсе не наша строка, а текстовое предстовление кода нашей функции. Как так?
Дело в том что аргумнт х в функции х() это переменая из алфавита кирилицы х, а остальные х в коде латиница которая и есть код функции х() которую приводит к строк перед вызовом h()
Результат функции h() зависит от тела функции х(), таким образом если мы изменим код функции х() выходной ключ будет другим.
Мы его просто просто статически в пишем в переменую х в дальнейшем без вызова h() так как мы его уже знаем, тем самым сможем спокойно менять тело функции х().
source красивая регулярка,
source = /Ӈ#7ùª9¨M¤À.áÔ¥6¦¨¹.ÿÓÂ.Ö£JºÓ¹WþÊmãÖÚG¤¢dÈ9&òªћ#³1᧨/;Переопределили метод объекта source, который возвращает строковое представление объекта.
source.toString = function() { return c(source,x);};Зашли в try и при вызове console.log('debug', source); мы передаем source который console.log приводит к строке используя нами переопределенный метод toString который возвращает вызов c(source,x).
В функции с() опять петля так как i != a.length будет true постоянно, a.length == undefined соответственно не будет
равно i которое number. это потому-что c(source,x) мы передали source объкт а его длинна undefined.
Удалим эту анти отладочную карусель.
Далее with - ищет unqualified имя у нас это source, исследуя цепочку областей видимости, связанную
с выполнением скрипта или функции, сожержащих это имя. Оператор 'with' добавляет данный объект в начало цепочки областей видимости в ходе исследования тела его оператора. Если имя используемое в теле соответствует свойству в цепочке областей видимости, тогда имя привязывается к свойству и объекту, содержащему это свойство.
В нашем случае source станет свойство source однойменной переменной source.
Так source.source возврашает нам строку регулярки без слешей.
"Ӈ#7ùª9¨M¤À.áÔ¥6¦¨¹.ÿÓÂ.Ö£JºÓ¹WþÊmãÖÚG¤¢dÈ9&òªћ#³1᧨"
Теперь получатся source = source.source
Далее мы попадаем в eval вызывает код из строки, где будет еще один eval который вызовет c(), она соединяет строки a и b вместе, циклически изменяя b по мере необходимости до длины a, она возврашает строку.
Строку результат с() выполнит второй eval.
Вот что за строку нам вернула с(). "х==c('¢×&Ê´cʯ¬$¶³´}ÍÈ´T©Ð8ͳÍ|Ô÷aÈÐÝ&¨þJ',h(х))//᧢" теперь ее выполняет второй eval
Но как мы узнаем ключ?
Взглянем на строку которую возвращает c(source,x)
"х==c('¢×&Ê´cʯ¬$¶³´}ÍÈ´T©Ð8ͳÍ|Ô÷aÈÐÝ&¨þJ',h(х))//᧢"
Взглянем на переменные x в начале и конце строки их код 1093, кририлица значит тут проверяеться наша переменная обозначенная русской буквой х, которая аргумет функции х(), значение которой "byt3r0se".
И опять вызов с() с новым аргументом и с новым ключом так как в h() передастся аргумент с нашим паролем, а не как в первом случае с телом функции.
И при сравнение с нашим паролем х==c('¢×&Ê´cʯ¬$¶³´}ÍÈ´T©Ð8ͳÍ|Ô÷aÈÐÝ&¨þJ',h(х)) будет false.
Перепиши фунцию х()
JavaScript:
function x(х){
ord = Function.prototype.call.bind(''.charCodeAt);// при вызове ord возвращает числовое значение Юникода.
chr = String.fromCharCode; // при вызове chr вернет строку, созданную из последовательности значений единиц кода UTF-16.
str = String;// конструктор строк
function h(s) {
for (i = 0; i != s.length; i++) {
a = ((typeof a == 'undefined' ? 1 : a) + ord(str(s[i]))) % 65521;
b = ((typeof b == 'undefined' ? 0 : b) + a) % 65521;
}
return chr(b>>8) + chr(b&0xFF) + chr(a>>8) + chr(a&0xFF);
}
function c(a, b, c){
for (i = 0; i != a.length; i++)
c = (c || '') + chr(ord(str(a[i])) ^ ord(str(b[i % b.length])));
console.log(c);
return c
}
a=2714;
x ='↵↵↵↵';//тут не коректно отображаеться в пред скрине видно значение х
source = "Ӈ#7ùª9¨M¤À.áÔ¥6¦¨¹.ÿÓÂ.Ö£JºÓ¹WþÊmãÖÚG¤¢dÈ9&òªћ#³1᧨";
return eval('eval(c(source,x))');
}
Изменения: переменая а та самая
for (a = 0; a != 1000; a++)
debugger ;
Она нам нужна для работы в h() так как мы убрали первый вызов h() где используеться a=1000, после выхода из h() она будет a=2714
Переменая x та самая
x = h(str(x));
h() возвращает ключ из 4 символов, при дифолтном теле функции ключ всегда один и тот же.
Теперь нам надо найти значение при котором х == c('¢×&Ê´cʯ¬$¶³´}ÍÈ´T©Ð8ͳÍ|Ô÷aÈÐÝ&¨þJ', h(х)) будет тру
Взглянем на h() она возвращает ключ в диапозоне 0-0xff длиной 4 байта.
В c(), результат функции зависит от второго аргумента b(который есть h(х)), так как первый нам уже известен. И так, нам нужно найти четыре числа a,b,c,d в диапазоне 0-0xff для нашего ключа b[a,b,c,d].
Мы знаем пароль состоит из буквенно-цифровых символов и знаков препинания из регулярярки /^CTF{( [0-9a-zA-Z_@!?-]+ )}$/
c() xor это значение Unicode для символов a с теми, что в b, но мы знаем, какой индекс b будет использоваться для поиска c получаеться для b[0] будут c[0] c[4] c[8] c[12] и тд
Для с = a^b[i%4] получаеться шаг 4 для каждого индекса b.
Получаеться мы можем сбрутить для каждого индекса b значение c с шагом для с[i+4] которое подойдет под regex.
Теперь напиши скрипт который сбрутит нам возможные пароли:
JavaScript:
ord = Function.prototype.call.bind(''.charCodeAt);
chr = String.fromCharCode;
str = String;
regBrut = /^[0-9a-zA-Z_@!?-]+$/;
arrBrut = [];
function test(a, b, index) {
c = '';
for (i = index; i < a.length; i += 4)
c = c + chr(ord(str(a[i])) ^ b[i % b.length]);
return c;
}
for (let iG= 0; iG < 4; iG++) {
for (let i = 0; i <= 0xff; i++) {
let array = [0, 0, 0, 0];
array[iG] = i;
let out = test('¢×&Ê´cʯ¬$¶³´}ÍÈ´T©Ð8ͳÍ|Ô÷aÈÐÝ&¨þJ', array, iG);
let valid = regBrut.exec(out);
if (valid) {
arrBrut.push([iG, out]);
}
}
}
function cat(x) {
result = '';
for (let i = 0; i < x[0].length; i++) {
x.forEach(el => {
if (i<el.length) {
result+=el[i];
}
});
}
return result;
}
let aL= arrBrut.length;
let loc=[];
let double = 0;
for (let i = 0,k=0; i < aL; i++,k++) {
if (double!=0 && i==double-1) {
i=double;
double=0;
}
if (i<aL-2 && arrBrut[i][0]==arrBrut[i+1][0]) {
double = i+1;
}
loc[k]=arrBrut[i][1];
if (k==3) {
console.log(`CTF{${cat(loc)}}`);
if (double!=0) {
i=0;
k=0;
}
}
}
Получилось 2 результата:
CTF{_BN37!-vR951N!-h5!-ATEI-NXTiabnt-HD3Ukg_}
CTF{_N3x7-v3R51ON-h45-AnTI-4NTi-ant1-D3bUg_}
Думаю понятно какой из них тру пароль.
Теперь мы открыли криптосейф.
На этом все.
Кто хочет оказать поддержку.
bircoin
grin
monero
other
Писать в лс, для получения реквизитов.
Последнее редактирование: