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
- only
- hexadecimal numbers
- only
.
don’t overwrite the data
- only
- 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 as0
only if data is available behind the dot).