[как бы этюд] Забраться в if...
От: Пельмешко Россия blog
Дата: 20.07.09 17:52
Оценка: 24 (5) :))
Здравствуйте, уважаемое rsdn community!

Недавно столкнулся с интересной (по крайней мере мне ) особенностью .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 дали время подумать неискушённым пользователям.
Re: [как бы этюд] Забраться в if...
От: Воронков Василий Россия  
Дата: 20.07.09 18:41
Оценка: 25 (4)
Здравствуйте, Пельмешко, Вы писали:

Я не слишком рано?


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 "";
        }
    }
}
Re[2]: [как бы этюд] Забраться в if...
От: Пельмешко Россия blog
Дата: 20.07.09 19:15
Оценка: 4 (1)
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Здравствуйте, Пельмешко, Вы писали:


ВВ>Я не слишком рано?


Эх... Про "реинкарнацию union'а" и интеропщиков я и не подумал...

Я сделал в unsafe:
byte b = 123;
bool xbool = * (bool*) &b;

storage.GetData(pass, xbool);

Можно поизвратнее, с помощью LCG:
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 ...
Re[3]: [как бы этюд] Забраться в if...
От: matumba  
Дата: 21.07.09 07:36
Оценка: +1
Здравствуйте, Пельмешко, Вы писали:

П>... 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
Оценка:
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Здравствуйте, Пельмешко, Вы писали:


ВВ>Я не слишком рано?


Респект за решение ))))
Re: [как бы этюд] Забраться в if...
От: Shvedskiy  
Дата: 21.07.09 10:05
Оценка: 12 (1)
Можно так:

            bool[] b = {false};
            System.Runtime.InteropServices.GCHandle h = System.Runtime.InteropServices.GCHandle.Alloc(b, System.Runtime.InteropServices.GCHandleType.Pinned);
            IntPtr ptr = h.AddrOfPinnedObject();
            System.Runtime.InteropServices.Marshal.WriteByte(ptr, 255);
            h.Free();
            GetData(null, b[0]);
Re[4]: [как бы этюд] Забраться в if...
От: Пельмешко Россия blog
Дата: 21.07.09 12:37
Оценка: 30 (1)
Здравствуйте, 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# так не считает и оставляет код после анализа достижимости
Re[5]: [как бы этюд] Забраться в if...
От: matumba  
Дата: 21.07.09 13:27
Оценка:
Здравствуйте, Пельмешко, Вы писали:

П>Здравствуйте, matumba, Вы писали:

M>>гм... это даже не смешно. Разве не так проверяется истина/ложь? if (a != 0) (а то и вообще if (a))

П>Да какая разница как проверяется

П>Неужели Вам не понятно что ... бла-бла-понты-бла-бла....

Что зн. какая разница? Если в кишках .NET проверялось бы на "не ноль", то и хаков никаких бы не получилось! Или я опять не догнал идею?

Вот "как бы скомпилированный МСИЛ":
; проверить условие a == b, a и b — boolean
if (a == 0)
return b == 0
else
return b != 0

Строго говоря, это такая дыра, что Балмер должен съесть галстук от стыда.
Re[6]: [как бы этюд] Забраться в if...
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 21.07.09 13:34
Оценка:
Здравствуйте, matumba, Вы писали:

M>Здравствуйте, Пельмешко, Вы писали:


П>>Здравствуйте, matumba, Вы писали:

M>>>гм... это даже не смешно. Разве не так проверяется истина/ложь? if (a != 0) (а то и вообще if (a))

П>>Да какая разница как проверяется

П>>Неужели Вам не понятно что ... бла-бла-понты-бла-бла....

M>Что зн. какая разница? Если в кишках .NET проверялось бы на "не ноль", то и хаков никаких бы не получилось! Или я опять не догнал идею?

Не догнал. Это не проверка на "не ноль" а сравнение двух переменных.
Re[7]: [как бы этюд] Забраться в if...
От: matumba  
Дата: 21.07.09 14:01
Оценка:
Здравствуйте, gandjustas, Вы писали:

M>>Что зн. какая разница? Если в кишках .NET проверялось бы на "не ноль", то и хаков никаких бы не получилось! Или я опять не догнал идею?

G>Не догнал. Это не проверка на "не ноль" а сравнение двух переменных.

Читай ещё раз пост, может поймёшь.
Re[5]: [как бы этюд] Забраться в if...
От: xvost Германия http://www.jetbrains.com/company/people/Pasynkov_Eugene.html
Дата: 21.07.09 14:17
Оценка: +1 :)
Здравствуйте, Пельмешко, Вы писали:

П>p.s. Интересно, что R# считает такой код недостижимым, а условие always false:

П>[/c#]А вот компилятор C# так не считает и оставляет код после анализа достижимости

Не стоит путать эвристический анализатор и семантически-строгий компилятор. Ограничение значений типа bool как true и false позволяет поймать много логических ошибок, а кул хацкеры — сами себе Буратины
С уважением, Евгений
JetBrains, Inc. "Develop with pleasure!"
Re[6]: [как бы этюд] Забраться в if...
От: Пельмешко Россия blog
Дата: 21.07.09 16:15
Оценка:
Здравствуйте, xvost, Вы писали:
X>Не стоит путать эвристический анализатор и семантически-строгий компилятор.

Так я и не ждал от R# анализа на уровне MSIL как в Pex, просто констатировал факт
И вообще PSI <3


Здравствуйте, matumba, Вы писали:
П>>Неужели Вам не понятно что ... бла-бла-понты-бла-бла....

понты Я просто не разглядел мысль за Вашей ненавистью к .NET

M>Что зн. какая разница? Если в кишках .NET проверялось бы на "не ноль", то и хаков никаких бы не получилось!


Дело лишь в том, что в стеке MSIL нету специального представления для boolean'ов и значения сравниваются через опкод equ, сравнивающий числовые значения. Можно было бы ввести опкод с описанной Вами логикой, только ради двух достаточно редких операций bool ==/!= bool, но его нету и не стоит это так яро воспринимать, надо лишь взять за правило в важных местах не выставлять в паблик bool и не сравнивать его потом через ==, вот и всё. А средства, типа DevLabs Pex обнаружат подобные очень маловероятные ситуации во время покрытия тестами (может и чекер CodeContracts найдет, тоже на уровне MSIL работает).

M>Строго говоря, это такая дыра, что Балмер должен съесть галстук от стыда.


Назовите, пожалуйста, язык с возможностью реинтерпретации памяти, в котором данный эффект устраняется компилятором, эмитящим специальный код.
Просто интересно, есть ли такие...
Re[3]: [как бы этюд] Забраться в if...
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 21.07.09 19:46
Оценка:
Здравствуйте, Пельмешко, Вы писали:

П>Если кто не понял, то смысл в том, что 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
и солнце б утром не вставало, когда бы не было меня
Re[7]: [как бы этюд] Забраться в if...
От: xvost Германия http://www.jetbrains.com/company/people/Pasynkov_Eugene.html
Дата: 21.07.09 20:36
Оценка:
Здравствуйте, Пельмешко, Вы писали:

П>надо лишь взять за правило в важных местах не выставлять в паблик bool и не сравнивать его потом через ==, вот и всё.


Ты меня извини, но это попахивает маразмом и конспирологией
ИМХО надо разделять консистентность и логичность API с одной стороны, и средства защиты кода с другой стороны (с чем в .NET в целом проблемы). Т.е. я категорически несогласен с прогибанием интерфейсов в угоду защиты кода от хацкеров. На мой взгляд, проблемы такого класса должны решаться через CAS.....
С уважением, Евгений
JetBrains, Inc. "Develop with pleasure!"
Re[8]: [как бы этюд] Забраться в if...
От: Воронков Василий Россия  
Дата: 21.07.09 22:02
Оценка: +2
Здравствуйте, xvost, Вы писали:

X>Здравствуйте, Пельмешко, Вы писали:

П>>надо лишь взять за правило в важных местах не выставлять в паблик bool и не сравнивать его потом через ==, вот и всё.
X>Ты меня извини, но это попахивает маразмом и конспирологией

Тем не менее я на вскидку не смог вспомнить, когда мне приходилось сравнивать bool через ==/!=
Re[8]: [как бы этюд] Забраться в if...
От: Пельмешко Россия blog
Дата: 22.07.09 05:01
Оценка: +2
Здравствуйте, xvost, Вы писали:
X>Ты меня извини, но это попахивает маразмом и конспирологией

Ничего, я тут ума-разума набираюсь и рад любому feedback'у, особенно пинкам когда я глупости говорю

Меня в оном обвиняли уже тут когда мне надо было ограничить значение enum'а только определёнными в нём (класс-обёртка был, надо было обеспечить такой инвариант), до сих пор не понимаю почему кому-то казалось это маразмом, это тот же int, а мне надо лишь ограничить принимаемые им значения.
Ещё тут был человек, проверяющий GetType на != null, вот это по-моему действительно маразм, но это возможно

Короче из этого мой вывод в том, что "планка маразма" для разных задач/проектов разная и важно чтобы планка девелопера соответствовала требуемой планке проекта.
Просто если некому участку кода очень важна безопасность такого рода, то Вы наверняка будет обносить её тестами или контрактами и если они подскажут о возможных проблемах такого рода, то почему бы не переформулировать условие на всякий пожарный?

Лично я сам специально такие места никогда искать не буду, насчёт "взять за правило" я погорячился...

X>ИМХО надо разделять консистентность и логичность API с одной стороны...


Не знаю как уж так может пострадать логичность API от переформулировки внутренних условий во избежания == ...
Если Вы поняли мой "призыв" как вообще отказ от bool в пабликах, то это я криво выразился, извините...

X>Т.е. я категорически несогласен с прогибанием интерфейсов в угоду защиты кода от хацкеров.


Вы трактуете это как "хак", а мне кажется, что в бытовых условиях получить хитрый bool достаточно вероятно при интеропе с какими-нибудь не очень честно написанными библиотеками, любящими реинтерпретацию памяти...


Здравствуйте, Воронков Василий, Вы писали:
ВВ>Тем не менее я на вскидку не смог вспомнить, когда мне приходилось сравнивать bool через ==/!=

Я тоже
Re[8]: [как бы этюд] Забраться в if...
От: _FRED_ Черногория
Дата: 22.07.09 06:01
Оценка:
Здравствуйте, xvost, Вы писали:

X>Ты меня извини, но это попахивает маразмом и конспирологией

X>ИМХО надо разделять консистентность и логичность API с одной стороны, и средства защиты кода с другой стороны (с чем в .NET в целом проблемы). Т.е. я категорически несогласен с прогибанием интерфейсов в угоду защиты кода от хацкеров.

ОК, а как на счёт рекомендаций относительно того, что бы не делать открытых полей\свойств типа массива (которые можно изменить снаружи. Пример — Path.InvalidPathChars)? Или вот есть рекомендация не делать открытые изменяемые статические поля\свойства (как, например, здесь
Автор: _FRED_
Дата: 27.02.08
— кстати, исправили?). "Попахивают" ли эти рекомендации и чем?

ИМХО, в данном конкретном случае (с попыткой защиты какой-то важной информации — "в важных местах") от показанного эффекта защититься стоит.
Help will always be given at Hogwarts to those who ask for it.
Re[9]: [как бы этюд] Забраться в if...
От: _FRED_ Черногория
Дата: 22.07.09 06:09
Оценка:
Здравствуйте, Пельмешко, Вы писали:

П>Вы трактуете это как "хак", а мне кажется, что в бытовых условиях получить хитрый bool достаточно вероятно при интеропе с какими-нибудь не очень честно написанными библиотеками, любящими реинтерпретацию памяти...


Ещё проще "эффэкт" получить от неправильно описанного в декларации extern-метода параметра. Вот тут отыскать грабли будет не просто — сравнивать сигнатуры, ИМХО, посложнее, чем просто читать код.

[offtop]
П>Здравствуйте, Воронков Василий, Вы писали:

Это очень неудобно — в ответе одному человеку обращаться к другому: мужыки тут старались, писали Янусы всякие "Мои Эрэсдээны" что бы упростить людям получение ответов, а ты пользу от этого труда сводишь на нет
[/offtop]
Help will always be given at Hogwarts to those who ask for it.
Re[9]: [как бы этюд] Забраться в if...
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 22.07.09 18:53
Оценка:
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Тем не менее я на вскидку не смог вспомнить, когда мне приходилось сравнивать bool через ==/!=

По уму дабы исключить булевых сравнений, вместо != нужно ввести булево исключающее или
и солнце б утром не вставало, когда бы не было меня
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.