Недавно столкнулся с интересной (по крайней мере мне ) особенностью .NET, решил попробовать оформить в виде этюда, может кто не знает и будет интересно... Очень прошу сильно не пинать/чмырить если тупо получилось или уже обсуждалось (я не нашёл)...
Ситуация тривиальная. Волею судьбы у Вас оказался фрагмент вражеского класса:
using System.Security;
namespace Pentagon
{
public partial class SecretDataStorage
{
public string GetData(SecureString password, bool goodUser)
{
bool goodPass = CheckPass(password);
if (!goodPass != goodUser && goodUser)
{
return ResolveSecretData(); // <= попасть сюды
}
return null;
}
}
}
Пароль, естественно, неизвестен, поэтому следует считать реализацию метода CheckPass() эквивалентной следующей:
bool CheckPass(SecureString _) { return false; }
Вопрос: как, не смотря на отсутствие пароля, всё-таки спереть данные у янки каким-нибудь "грязным хаком" используя средства C#/.NET?
(то есть необходимо заставить нить исполнения зайти в ветвь if'а к вызову ResolveSecretData(), там где отмечено комментарием)
p.s. Код можно было сделать каноничным — для минимального воспроизведения поднимаемой в этом этюде проблемы, но мне захотелось попытаться хотя бы немного запутать задачу... p.p.s. Хотелось бы чтобы гуру CLR дали время подумать неискушённым пользователям.
using System;
using System.Security;
using System.Collections.Specialized;
using System.Runtime.InteropServices;
class Program
{
[StructLayout(LayoutKind.Explicit)]
public struct MyBool
{
[FieldOffset(0)]
public byte Value1;
[FieldOffset(0)]
public bool Value2;
}
static void Main()
{
var ds = new SecretDataStorage();
var mb = new MyBool();
mb.Value1 = 128;
ds.GetData(null, mb.Value2);
}
public partial class SecretDataStorage
{
public string GetData(SecureString password, bool goodUser)
{
bool goodPass = CheckPass(password);
if (!goodPass != goodUser && goodUser)
{
return ResolveSecretData(); // <= попасть сюды
}
return null;
}
private bool CheckPass(SecureString password)
{
return false;
}
private string ResolveSecretData()
{
Console.WriteLine("We are here!");
return"";
}
}
}
var method = new DynamicMethod(string.Empty, typeof(bool), Type.EmptyTypes);
ILGenerator il = method.GetILGenerator();
il.Emit(OpCodes.Ldc_I4_2);
il.Emit(OpCodes.Ret);
bool xbool = (bool) method.Invoke(null, null);
storage.GetData(pass, xbool);
Если кто не понял, то смысл в том, что boolean на стеке MSIL представляется целым числом, при этом компилятор изо всех сил старается ограничить его значениями 1 и 0, но это не всегда возможно, что может иногда породить необычное поведение типа true != true ...
Здравствуйте, Пельмешко, Вы писали:
П>... boolean на стеке MSIL представляется целым числом, при этом компилятор изо всех сил старается ограничить его значениями 1 и 0, но это не всегда возможно, что может иногда породить необычное поведение типа true != true ...
гм... это даже не смешно. Разве не так проверяется истина/ложь? if (a != 0) (а то и вообще if (a))
Что-то пугает меня этот MS Indian Laming.
Re: [как бы этюд] Забраться в if...
От:
Аноним
Дата:
21.07.09 07:45
Оценка:
Здравствуйте, Пельмешко.
Вы наверное посчитаете меня извращенцем, но:
public static void Main()
{
var c = new SecretDataStorage();
var method = c.GetType().GetMethod( "GetData" );
var dynMethod = new DynamicMethod( "Test", typeof(string),
new[]
{
typeof(SecretDataStorage),
typeof(SecureString),
typeof(int)
} );
var generator = dynMethod.GetILGenerator();
generator.Emit( OpCodes.Ldarg_0 );
generator.Emit( OpCodes.Ldarg_1 );
generator.Emit( OpCodes.Ldarg_2 );
generator.EmitCall( OpCodes.Call, method, null );
generator.Emit( OpCodes.Ret );
dynMethod.DefineParameter( 0, ParameterAttributes.In, "s" );
dynMethod.DefineParameter( 1, ParameterAttributes.In, "d" );
var a = (D)dynMethod.CreateDelegate( typeof(D) );
Console.WriteLine( c.GetData( new SecureString(), true ) ?? "null" );
Console.WriteLine( a( c, new SecureString(), 1 ) ?? "null" ); // true
Console.WriteLine( a( c, new SecureString(), 2 ) ?? "null" ); // true > 1 ))
}
Изврат, но работает )).
И спасибо, мне было интерестно ))).
Щас почитаю другие решения.
Re[2]: [как бы этюд] Забраться в if...
От:
Аноним
Дата:
21.07.09 07:51
Оценка:
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Здравствуйте, Пельмешко, Вы писали:
ВВ>Я не слишком рано?
Здравствуйте, matumba, Вы писали: M>гм... это даже не смешно. Разве не так проверяется истина/ложь? if (a != 0) (а то и вообще if (a))
Да какая разница как проверяется
Неужели Вам не понятно что быдлокод пришлось придумать чтобы получить описанный эффект от сравнения двух System.Boolean???
Для "рождённых" сразу в managed сам факт true != true может выглядеть как нарушение мироздания, вот и всё что я хотел показать в этом неудачном этюде.
p.s. Интересно, что R# считает такой код недостижимым, а условие always false:
void func(bool a, bool b)
{
if (a && b && a != b)
{
throw new НеМожетБытьException();
}
}
А вот компилятор C# так не считает и оставляет код после анализа достижимости
Здравствуйте, Пельмешко, Вы писали:
П>Здравствуйте, matumba, Вы писали: M>>гм... это даже не смешно. Разве не так проверяется истина/ложь? if (a != 0) (а то и вообще if (a))
П>Да какая разница как проверяется П>Неужели Вам не понятно что ... бла-бла-понты-бла-бла....
Что зн. какая разница? Если в кишках .NET проверялось бы на "не ноль", то и хаков никаких бы не получилось! Или я опять не догнал идею?
Вот "как бы скомпилированный МСИЛ":
; проверить условие a == b, a и b — boolean
if (a == 0)
return b == 0
else
return b != 0
Строго говоря, это такая дыра, что Балмер должен съесть галстук от стыда.
Здравствуйте, matumba, Вы писали:
M>Здравствуйте, Пельмешко, Вы писали:
П>>Здравствуйте, matumba, Вы писали: M>>>гм... это даже не смешно. Разве не так проверяется истина/ложь? if (a != 0) (а то и вообще if (a))
П>>Да какая разница как проверяется П>>Неужели Вам не понятно что ... бла-бла-понты-бла-бла....
M>Что зн. какая разница? Если в кишках .NET проверялось бы на "не ноль", то и хаков никаких бы не получилось! Или я опять не догнал идею?
Не догнал. Это не проверка на "не ноль" а сравнение двух переменных.
Здравствуйте, gandjustas, Вы писали:
M>>Что зн. какая разница? Если в кишках .NET проверялось бы на "не ноль", то и хаков никаких бы не получилось! Или я опять не догнал идею? G>Не догнал. Это не проверка на "не ноль" а сравнение двух переменных.
Здравствуйте, Пельмешко, Вы писали:
П>p.s. Интересно, что R# считает такой код недостижимым, а условие always false: П>[/c#]А вот компилятор C# так не считает и оставляет код после анализа достижимости
Не стоит путать эвристический анализатор и семантически-строгий компилятор. Ограничение значений типа bool как true и false позволяет поймать много логических ошибок, а кул хацкеры — сами себе Буратины
С уважением, Евгений
JetBrains, Inc. "Develop with pleasure!"
Здравствуйте, xvost, Вы писали: X>Не стоит путать эвристический анализатор и семантически-строгий компилятор.
Так я и не ждал от R# анализа на уровне MSIL как в Pex, просто констатировал факт
И вообще PSI <3
Здравствуйте, matumba, Вы писали: П>>Неужели Вам не понятно что ... бла-бла-понты-бла-бла....
понты Я просто не разглядел мысль за Вашей ненавистью к .NET
M>Что зн. какая разница? Если в кишках .NET проверялось бы на "не ноль", то и хаков никаких бы не получилось!
Дело лишь в том, что в стеке MSIL нету специального представления для boolean'ов и значения сравниваются через опкод equ, сравнивающий числовые значения. Можно было бы ввести опкод с описанной Вами логикой, только ради двух достаточно редких операций bool ==/!= bool, но его нету и не стоит это так яро воспринимать, надо лишь взять за правило в важных местах не выставлять в паблик bool и не сравнивать его потом через ==, вот и всё. А средства, типа DevLabs Pex обнаружат подобные очень маловероятные ситуации во время покрытия тестами (может и чекер CodeContracts найдет, тоже на уровне MSIL работает).
M>Строго говоря, это такая дыра, что Балмер должен съесть галстук от стыда.
Назовите, пожалуйста, язык с возможностью реинтерпретации памяти, в котором данный эффект устраняется компилятором, эмитящим специальный код.
Просто интересно, есть ли такие...
Здравствуйте, Пельмешко, Вы писали:
П>Если кто не понял, то смысл в том, что boolean на стеке MSIL представляется целым числом, при этом компилятор изо всех сил старается ограничить его значениями 1 и 0, но это не всегда возможно, что может иногда породить необычное поведение типа true != true ...
Логичнее не 1 а not 0 то есть 255. Тогда все битовые операции применимы и к булевым.
true==~false; false ==~true; true & false == false; true | false == true
true==!false; false ==!true; true && false == false; true || false == true
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Пельмешко, Вы писали:
П>надо лишь взять за правило в важных местах не выставлять в паблик bool и не сравнивать его потом через ==, вот и всё.
Ты меня извини, но это попахивает маразмом и конспирологией
ИМХО надо разделять консистентность и логичность API с одной стороны, и средства защиты кода с другой стороны (с чем в .NET в целом проблемы). Т.е. я категорически несогласен с прогибанием интерфейсов в угоду защиты кода от хацкеров. На мой взгляд, проблемы такого класса должны решаться через CAS.....
С уважением, Евгений
JetBrains, Inc. "Develop with pleasure!"
Здравствуйте, xvost, Вы писали:
X>Здравствуйте, Пельмешко, Вы писали: П>>надо лишь взять за правило в важных местах не выставлять в паблик bool и не сравнивать его потом через ==, вот и всё. X>Ты меня извини, но это попахивает маразмом и конспирологией
Тем не менее я на вскидку не смог вспомнить, когда мне приходилось сравнивать bool через ==/!=
Здравствуйте, xvost, Вы писали: X>Ты меня извини, но это попахивает маразмом и конспирологией
Ничего, я тут ума-разума набираюсь и рад любому feedback'у, особенно пинкам когда я глупости говорю
Меня в оном обвиняли уже тут когда мне надо было ограничить значение enum'а только определёнными в нём (класс-обёртка был, надо было обеспечить такой инвариант), до сих пор не понимаю почему кому-то казалось это маразмом, это тот же int, а мне надо лишь ограничить принимаемые им значения.
Ещё тут был человек, проверяющий GetType на != null, вот это по-моему действительно маразм, но это возможно
Короче из этого мой вывод в том, что "планка маразма" для разных задач/проектов разная и важно чтобы планка девелопера соответствовала требуемой планке проекта.
Просто если некому участку кода очень важна безопасность такого рода, то Вы наверняка будет обносить её тестами или контрактами и если они подскажут о возможных проблемах такого рода, то почему бы не переформулировать условие на всякий пожарный?
Лично я сам специально такие места никогда искать не буду, насчёт "взять за правило" я погорячился...
X>ИМХО надо разделять консистентность и логичность API с одной стороны...
Не знаю как уж так может пострадать логичность API от переформулировки внутренних условий во избежания == ...
Если Вы поняли мой "призыв" как вообще отказ от bool в пабликах, то это я криво выразился, извините...
X>Т.е. я категорически несогласен с прогибанием интерфейсов в угоду защиты кода от хацкеров.
Вы трактуете это как "хак", а мне кажется, что в бытовых условиях получить хитрый bool достаточно вероятно при интеропе с какими-нибудь не очень честно написанными библиотеками, любящими реинтерпретацию памяти...
Здравствуйте, Воронков Василий, Вы писали: ВВ>Тем не менее я на вскидку не смог вспомнить, когда мне приходилось сравнивать bool через ==/!=
Здравствуйте, xvost, Вы писали:
X>Ты меня извини, но это попахивает маразмом и конспирологией X>ИМХО надо разделять консистентность и логичность API с одной стороны, и средства защиты кода с другой стороны (с чем в .NET в целом проблемы). Т.е. я категорически несогласен с прогибанием интерфейсов в угоду защиты кода от хацкеров.
ОК, а как на счёт рекомендаций относительно того, что бы не делать открытых полей\свойств типа массива (которые можно изменить снаружи. Пример — Path.InvalidPathChars)? Или вот есть рекомендация не делать открытые изменяемые статические поля\свойства (как, например, здесь
Здравствуйте, Пельмешко, Вы писали:
П>Вы трактуете это как "хак", а мне кажется, что в бытовых условиях получить хитрый bool достаточно вероятно при интеропе с какими-нибудь не очень честно написанными библиотеками, любящими реинтерпретацию памяти...
Ещё проще "эффэкт" получить от неправильно описанного в декларации extern-метода параметра. Вот тут отыскать грабли будет не просто — сравнивать сигнатуры, ИМХО, посложнее, чем просто читать код.
[offtop] П>Здравствуйте, Воронков Василий, Вы писали:
Это очень неудобно — в ответе одному человеку обращаться к другому: мужыки тут старались, писали Янусы всякие "Мои Эрэсдээны" что бы упростить людям получение ответов, а ты пользу от этого труда сводишь на нет
[/offtop]
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Тем не менее я на вскидку не смог вспомнить, когда мне приходилось сравнивать bool через ==/!=
По уму дабы исключить булевых сравнений, вместо != нужно ввести булево исключающее или
и солнце б утром не вставало, когда бы не было меня