KeiruaProd

Let’s retrieve an image after its deletion on Slack.

A colleague put a screenshot on Slack then removed it quickly, implying there were sensitive data on it. Can a malicious user on the channel see the image AFTER its removal ?

TL;DR:

It’s not a surprise than when you put something on the internet, it’s not secret anymore. This is especially true with Slack.

Finding the URL of the image using the cache

I did not view the image clearly before it was removed by my colleague (he posted the screenshot to answer a question I had), but I was wondering if I could find it back anyway. Turns out it’s not very difficult.

I googled Slack’s cache directory, hoping the file was still store on the disk directly. A github issue lead me to ~/.config/Slack/Cache.

Then, all I had to do was to look for the files modified in the last 10 minutes using find:

~/.config/Slack$ find ./Cache -cmin -10
.
./Cache
./Cache/index-dir
./Cache/index-dir/the-real-index
./Cache/3d98335959f25344_0
./Cache/5d76eb5c51386236_0
./Cache/ac8118e28bbfdf33_0

Ok, so the cache directory looks promising. Since this is a web app, I tried to convert the files to web image file formats like JPG or PNG, and open it in an image viewer. It lead nowhere :

for f in *; do mv $f $f.jpg; done;
for f in *; do mv $f $f.png; done;

Instead, I could have tried to see what kind of file it was:

$ file 3d98335959f25344_0
3d98335959f25344_0: data

That’s a raw data file, so certainly not a PNG or JPG that would have a known structure.

I tried to read the beginning of the files using xxd, an hexadecimal conversion tool, hoping to find some header or clear text data… on the first file, bingo:

$ xxd 3d98335959f25344_0 | head
00000000: 305c 72a7 1b6d fbfc 0500 0000 6700 0000  0\r..m......g...
00000010: 1bd2 c14c 0000 0000 6874 7470 733a 2f2f  ...L....https://
00000020: 6669 6c65 732e 736c 6163 6b2e 636f 6d2f  files.slack.com/
00000030:       redacted
00000040:       redacted
00000050: 6361 7074 7572 655f 645f 5f5f 655f 5f63  capture_d___e__c
00000060: 7261 6e5f 3230 3231 2d30 352d 3237 5f61  ran_2021-05-27_a
00000070: 5f5f 5f31 362e 3237 2e30 312e 706e 6789  ___16.27.01.png.
00000080: 504e 470d 0a1a 0a00 0000 0d49 4844 5200  PNG........IHDR.
00000090: 000b 3400 0006 ca08 0600 0000 aa11 d1fc  ..4.............

At the beginning of the file, there was the URL of an image. The naming suggested it was a screenshot whose timestamp matched what I was looking for, so I opened it in a browser. It was there. Done.

Turns out when you delete an image on Slack, it’s not deleted from Slack’s servers and someone knowledgable enough can find it after its deletion from the app using the cache.

Extracting the image from the cache file

Ok, but what if Slack deletes the image from its server ? Can we retrieve the entire image out of the cache file ? That’s the purpose of caching files after all, and the cache data is slighty bigger than the image we are looking for. The entire image is maybe stored inside the cache file.

$ ls -alh samples/
.rw-------  517k clem 27 mai   16:27 3d98335959f25344_0
.rw-rw-r--  512k clem 28 mai   09:05 image-from-slack.png

Our image-from-slack.png image is a png file. It starts with the standard [0x89, 0x50, 0x4e, 0x47] bytes, which is part of its magic number (89 50 4e 47 0d 0a 1a 0a).

xxd samples/image-from-slack.png |head -n 1
00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR

So we can write a piece of code in order to search for the location of those 4 bytes inside the file. There might be faster ways (using xxd) but writing C code felt easier:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>

unsigned char *read_file (const char * file_name, unsigned int* s)
{
    struct stat sb;
    stat (file_name, &sb);
    *s = sb.st_size;
    unsigned char *contents = malloc (*s + 1);

    FILE * f = fopen (file_name, "rb");
    fread (contents, sizeof (unsigned char), *s, f);
    fclose (f);
    return contents;
}

int main() {
    unsigned int s;
    unsigned char *contents = read_file("samples/3d98335959f25344_0", &s);
    printf("file size: %d\n", s);

    for(int i = 0; i < s-4; i++) {
        if(contents[i] == 0x89 && contents[i+1] == 'P' && contents[i+2]=='N' && contents[i+3] == 'G') {
            printf("possible offset: %d\n", i);
            break;
        }
    }

    free(contents);
    return 0;
}

Now lets run this program:

gcc search.c -o search
./search 
file size: 516983
possible offset: 127

So now, we know the PNG magic header may start at offset 127.

Our target image is 512232 bytes long. Lets read from the cache file from offset 127, for 512232 bytes, are store this inside a file:

#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE * f = fopen ("samples/3d98335959f25344_0", "rb");
    unsigned int s = 512232;
    unsigned char* contents = (unsigned char*)malloc(s);    
    fseek(f, 127, SEEK_SET);
    fread (contents, sizeof (unsigned char), s, f);
    fclose (f);

    FILE* fp2 = fopen("recovered.png", "wb");
    fwrite (contents, sizeof (unsigned char), s, fp2);
    fclose(fp2);;
}
$ gcc recover.c -o recover
$ ./recover

Awesome ! It works, the recovered.png contains the image. We can recover an image when we know its size. Can we do it without knowing it in advance ? I stopped here, but yes.

The image is stored without encryption, so we could read the memory buffer starting from offset 127 with a library like libpng.

See a typo ? You can suggest a modification on Github.