Search code examples
creverse-engineering

How to reverse strings that have been obfuscated using floats and double?


I'm working on a crackme , and having a bit of trouble making sense of the flag I'm supposed to retrieve. I have disassembled the binary using radare2 and ghidra , ghidra gives me back the following pseudo-code:


undefined8 main(void)

{
  long in_FS_OFFSET;
  double dVar1;
  double dVar2;
  int local_38;
  int local_34;
  int local_30;
  int iStack44;
  int local_28;
  undefined2 uStack36;
  ushort uStack34;
  char local_20;
  undefined2 uStack31;
  uint uStack29;
  byte bStack25;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  __printf_chk(1,"Insert flag: ");
  __isoc99_scanf(&DAT_00102012,&local_38);
  uStack34 = uStack34 << 8 | uStack34 >> 8;
  uStack29 = uStack29 & 0xffffff00 | (uint)bStack25;
  bStack25 = (undefined)uStack29;
  if ((((local_38 == 0x41524146) && (local_34 == 0x7b594144)) && (local_30 == 0x62753064)) &&
     (((iStack44 == 0x405f336c && (local_20 == '_')) &&
      ((local_28 == 0x665f646e && (CONCAT22(uStack34,uStack36) == 0x40746f31)))))) {
    dVar1 = (double)CONCAT26(uStack34,CONCAT24(uStack36,0x665f646e));
    dVar2 = (double)CONCAT17((undefined)uStack29,CONCAT43(uStack29,CONCAT21(uStack31,0x5f)));
    __printf_chk(0x405f336c62753064,1,&DAT_00102017);
    __printf_chk(dVar1,1,"y: %.30lf\n");
    __printf_chk(dVar2,1,"z: %.30lf\n");
    dVar1 = dVar1 * 124.8034902710365;
    dVar2 = (dVar1 * dVar1) / dVar2;
    round_double(dVar2,0x1e);
    __printf_chk(1,"%.30lf\n");
    dVar1 = (double)round_double(dVar2,0x1e);
    if (1.192092895507812e-07 <= (double)((ulong)(dVar1 - 4088116.817143337) & 0x7fffffffffffffff))
    {
      puts("Try Again");
    }
    else {
      puts("Well done!");
    }
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

It is easy to see that there's a part of the flag in plain-sight , but the other part is a bit more interesting :

if (1.192092895507812e-07 <= (double)((ulong)(dVar1 - 4088116.817143337) & 0x7fffffffffffffff))

From what I understand , I have to generate the missing part of the flag depending on this condition . The problem is that I absolutely have no idea how to do this .

I can assume this missing part is 8 bytes of size , according to this line :

dVar2=(double)CONCAT17((undefined)uStack29,CONCAT43(uStack29,CONCAT21(uStack31,0x5f)));`

Considering flags are usually ascii , with some special characters , let's say , each byte will have values from 0x21 to 0x7E , that's almost 8^100 combinations , which will clearly take too much time to compute.

Do you guys have an idea on how I should proceed to solve this ?

Edit : Here is the link to the binary : https://filebin.net/dpfr1nocyry3sijk


Solution

  • You can tweak the Ghidra reverse result by edit variable type. Based on scanf const string %32s your local_38 should be char [32].

    Before the first if, there are some char swap.

    And the first if statment give you a long constrain of flag

    At this point, you can confirm part of flag is FARADAY{d0ubl3_@nd_f1o@t, then is ther main part of this challenge.

    It print x, y, z based on the flag, but you'll quickly find x and y is constrain by the if, so you only need to solve z to get the flag, so you think you need to bruteforce all double value limit by printable ascii.

    But there are a limitaion in if statment says byte0 of this double must be _ and a math constrain there, simple math tell dVar2 - 4088116.817143337 <= 1.192092895507813e-07 and it comes dVar2 is very close 4088116.817143337 And byte 3 and byte 7 in this double will swap

    By reverse result: dVar2 = y*y*x*x/z, solve this equation you can say z must near 407.2786840401004 and packed to little endian is `be}uty@. Based on double internal structure format, MSB will affect exponent, so you can make sure last byte is @ and it shows byte0 and byte3 is fixed now by constrain and flag common format with {} pair.

    enter image description here

    So finally, you only need to bureforce 5 bytes of printable ascii to resolve this challenge.

    import string, struct
    from itertools import product
    
    possible = string.ascii_lowercase + string.punctuation + string.digits
    
    for nxt in product(possible, repeat=5):
        n = ''.join(nxt).encode()
        s = b'_' + n[:2] + b'}' + n[2:] + b'@'
        rtn = struct.unpack("<d", s)[0]
        rtn = 1665002837.488342 / rtn
        if abs(rtn - 4088116.817143337) <= 0.0000001192092895507812:
            print(s)
    
    

    And bingo the flag is FARADAY{d0ubl3_@nd_f1o@t_be@uty}