Вызывает переполнение буфера в C, чтобы вызвать истинное условие

Я пытаюсь решить эту проблему, где цель состоит в том, чтобы программа показала "ВЫ ПОБЕДИ!" Чтобы сделать это, я должен найти правильные значения переменных, чтобы сделать оба if состояние и assert оценивать как истинное

Первоначально я думал, что это будет относительно просто. Тем не менее, кажется, что переполнение буфера - это искусство, которое требует продвинутых навыков программирования (в том числе концепций с прямым и обратным порядком байтов), но у меня нет большого опыта в программировании на Си.

Любая помощь или совет будет принята с благодарностью.

#include<assert.h>
int
main(int argc, char*argv[]){
    int size = 0;
    int buf[1024];
    read(0,&size,sizeof(size));
    assert (size <= 1024);
    read(0,buf,size*sizeof(int));
    if (size > 0 && size < 100 && buf[999] == 'B')
        printf("YOU WIN!\n");
    return 0;
}

1 ответ

Забавная проблема.

Вам нужно что-то знать о машине, на которой вы работаете, поэтому я сделал следующие предположения:

  1. 32-битная машина с прямым порядком байтов.

  2. Компилятор делает "классическую" компоновку стека, которая бы size рядом с концом buf,

  3. Данные выровнены до 4 байтов или меньше, поэтому между концом нет заполнения. buf а также size,

Классический компилятор на этой машине размещает ваши переменные в стеке вызовов в порядке их появления в коде, поэтому size толкают сначала, а потом buf, Ваш стек будет выглядеть так:

                   buf                byte
   top of stack   offset             offset
   +-----------+    0 * sizeof(int) =    0  
   | buf[   0] |   
   +-----------+    1 * sizeof(int) =    4
   |           |   
   ...
   |           |
   +-----------+ 1023 * sizeof(int) = 4092
   | buf[1023] |
   +-----------+ 1024 * sizeof(int) = 4096
   |   size    |
   +-----------+ 1025 * sizeof(int) = 4100
  bottom of stack

Запись в buf[1024] переписал бы size, Если бы мы писали необработанные байты, то байты 4096, 4097, 4098 и 4099 соответствовали бы байтам в size,

Мы знаем, что хотим, чтобы размер был отрицательным, чтобы передать assertи мы хотим size * sizeof(int)оценить до 4100. Потому что, если мы прочитаем 4100 байт, последние 4 байта будут перезаписаны size,

size * 4 = 4100.  Solving for size:
size = 1025 (or 0x401)

Теперь мы просто делаем это отрицательным, устанавливая самый значимый бит.

0x80000401

Таким образом, установка размера этого значения пройдет assert (size <= 1024), Потому что все с самым значимым битом установлено отрицательно, а все отрицательные числа меньше 1024.

Когда мы доберемся до буфера чтения, он будет оценен примерно так:

read(0, buf, 0x80000401 * sizeof(int));
read(0, buf, 0x80000401 * 4);
read(0, buf, 0x00001004) == read(0, buf, 4100)

Да, все верно, мы просто сказали, что нужно прочитать 4100 байт из-за 32-битной арифметики по модулю. Или, если это помогает, вы можете думать о умножении на 4 как о смещении влево на 2, так что смещение нашего магического значения влево на 2 избавляет от самого значимого бита.

Чтобы напечатать "ВЫ ВЫИГРЫВАЕТЕ!", Вы должны написать поток байтов, где buf[999] - это байт 3996, а байты 4096, 4097, 4098 и 4099 - байты size, На этой гипотетической машине с прямым порядком байтов мы установили бы эти байты в 0, 0, 0 и 1-99 соответственно.

Другие вопросы по тегам