🤖

ångstromCTF 2021 - Float On

2021/04/11に公開

問題概要

I cast my int into a double the other day, well nothing crashed, sometimes life's okay.

We'll all float on, anyway: float_on.c.

Float on over to /problems/2021/float_on on the shell server, or connect with nc shell.actf.co 21399.

float_on.c
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>

#define DO_STAGE(num, cond) do {\
    printf("Stage " #num ": ");\
    scanf("%lu", &converter.uint);\
    x = converter.dbl;\
    if(cond) {\
        puts("Stage " #num " passed!");\
    } else {\
        puts("Stage " #num " failed!");\
        return num;\
    }\
} while(0);

void print_flag() {
    FILE* flagfile = fopen("flag.txt", "r");
    if (flagfile == NULL) {
        puts("Couldn't find a flag file.");
        return;
    }
    char flag[128];
    fgets(flag, 128, flagfile);
    flag[strcspn(flag, "\n")] = '\x00';
    puts(flag);
}

union cast {
    uint64_t uint;
    double dbl;
};

int main(void) {
    union cast converter;
    double x;

    DO_STAGE(1, x == -x);
    DO_STAGE(2, x != x);
    DO_STAGE(3, x + 1 == x && x * 2 == x);
    DO_STAGE(4, x + 1 == x && x * 2 != x);
    DO_STAGE(5, (1 + x) - 1 != 1 + (x - 1));

    print_flag();

    return 0;
}

解説

xuint64で与えて、それをdouble型で解釈したときにDO_STAGEで与えられている式を満たすようにする問題です。

Cにおけるdoubleの処理はIEEE 754で規定されていて、これを見ながらそれぞれの条件に合う数字を探していくことになります。

  1. x == -x: これを満たす代表例として0を考えるのは自然でしょう。
  2. x != x: これはNaNが満たします。
  3. x + 1 == x && x * 2 == x: これはinfinityが満たします。
  4. x + 1 == x && x * 2 != x: これは非常に大きい数(具体的に1を足しても数が変わらないよう、仮数部でカバー出来ない程に大きければOKです。有効数字は大体15桁とかなので、多分10進数における20桁以上の数字なら大丈夫でしょう。僕は100桁でやりました。
  5. (1 + x) - 1 != 1 + (x - 1): これは少し悩んだのですが、NaNはこれを満たします。NaNに関するルールで以下があるためです。
    • NaNと数字の演算はNaNになる。この結果、右辺と左辺はそれぞれNaNになる
    • NaN == NaNfalse

この結果からそれぞれ与えるべきdoubleとしてのxは求まったので、これをuint64型に変換して与える必要があります。まぁ問題で与えられている手法の逆で変換すれば出来るので...

convert.c
#include <stdio.h>
#include <stdint.h>
#include <math.h>
#include <assert.h>

union cast {
  uint64_t uint;
  double dbl;
};

int main() {
  union cast conv;

  // stage 1: x == -x, x = 0
  conv.dbl = 0.0;
  printf("%lu\n", conv.uint);
  // stage 2: x != x, x = NaN
  conv.dbl = NAN;
  printf("%lu\n", conv.uint);
  // stage 3: x + 1 == x, x * 2 == x, x is infinity
  conv.dbl = INFINITY;
  printf("%lu\n", conv.uint);
  // stage 4: x + 1 == x, x * 2 != x, x is very huge number
  conv.dbl = 1e100;
  printf("%lu\n", conv.uint);
  // stage 5: (1 + x) - 1 != 1 + (x - 1), x is NaN
  conv.dbl = NAN;
  double x = conv.dbl;
  if ((1 + x) - 1 != 1 + (x - 1)) {
    printf("%lu\n", conv.uint);
  } else {
    printf("stage 5 failed\n");
  }

  return 0;
}
solve.py
from pwn import *
context.log_level = "debug"

r = remote("shell.actf.co", 21399)

r.recvuntil(b"Stage 1: ")
r.sendline("0")
r.recvuntil(b"Stage 2: ")
r.sendline("9221120237041090560")
r.recvuntil(b"Stage 3: ")
r.sendline("9218868437227405312")
r.recvuntil(b"Stage 4: ")
r.sendline("6103021453049119613")
r.recvuntil(b"Stage 5: ")
r.sendline("9221120237041090560")

r.interactive()

Discussion