How to bypass the stack canary with . (dot) and scanf.

Scenario

Imagine that you have given binary implemented by following code (compiled with default settings):

#include <cstdio>

void sum() {
    double buffer[20];
    int n = 0;
    double s = 0;

    printf("How many numbers do you want to add?\n");
    scanf("%d", &n);

    for(int i=0; i<n; i++) {
        printf("Number[%d]: ", i);
        scanf("%lf", &buffer[i]);
        s += buffer[i];
    }
    printf("Your sum: %lf\n", s);
}

int main() {
    sum();
    return 0;
}

Above code obviously trusts user input and allows to overwrite arbritrary data which can be weaponized with ret2libc and lead to gaining the shell. But there is one big problem - stack canary:

  • overwriting stack canary will cause exception
  • we can’t brute-force the stack cookie (program is not giving us such possibility)
unsigned __int64 sum(void)
{
  int v1; // [rsp+10h] [rbp-C0h] BYREF
  unsigned int i; // [rsp+14h] [rbp-BCh]
  double v3; // [rsp+18h] [rbp-B8h]
  double v4[21]; // [rsp+20h] [rbp-B0h] BYREF
  unsigned __int64 v5; // [rsp+C8h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  v1 = 0;
  v3 = 0.0;
  puts("How many numbers do you want to add?");
  scanf("%d", &v1);
  for ( i = 0; (int)i < v1; ++i )
  {
    printf("Number[%d]: ", i);
    scanf("%lf", &v4[i]);
    v3 = v4[i] + v3;
  }
  printf("Your sum: %lf\n", v3);
  return __readfsqword(0x28u) ^ v5;
}

Game Over? Not exactly ;)

Notice that user input is collected with scanf("%lf", &buffer[i]) and here is the trick: you can pass . (dot) as input, which will skip overwriting the data in referenced variable:

Number[20]: .
Number[21]: .
Your sum: -92773155265697924505313227519998636757689855298695787655081074157174089977436344947792687303154543628009248831613884078618796617025716224.000000
[Inferior 1 (process 29710) exited normally]

scanf under the hood

What

First question which comes to mind after seeing this trick is Why it happens - I will try to answer to this question in the next section - and Is it working only for doubles?.

I created a very simple test program which will help us understand the scope of this trick without reading the source:

#include <cstdio>
int main() {
        double foo = 3.7;
        scanf("%lf", &foo);
        printf("%lf\n", foo);
}

According to my test results:

  • integer numbers (tested on %d)
    • . and .5 don’t overwrite the data
  • floating point numbers (tested on $d, %lf)
    • only . don’t overwrite the data
  • hexadecimal numbers
    • only . don’t overwrite the data
  • strings (%s)
    • data is always overwritten (as expected)

Summarizing our findings - it seems that . affects all numeric string formats1.

Why

This part is partially based on the intuition, so it don’t have to be 100% accurate

Source Code can be found [here]2.

Keeping the long story short:

  • in _IO_vfscanf_internal all characters are checked and parsed one by one
  • legit characters are stored in struct char_buffer charbuf;
  • numeric values divided by . are decomposed into separate values and then converted appropriately into the value of choosen type (ints: only characters before . are parsed; floats: everything before . is interpreted as 0 only if data is available behind the dot).

References