# Photoshoot Writeup
## Service Description
Simple Flask web service with following routes:```GET /POST /loadPOST /storePOST /upload```
`load` and `store` are used to store and retrieve a flag from database. `upload`accepts parameters `file`, `operations` and, optionally, `year`, which arepassed to `imager` binary.
JS code in `notsuspicious.js` was obfuscated but it was not necessary tounderstand it.
## Imager
```$ file imagerimager: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), <SKIPPED>, not stripped```
### Description
Imager is written in C++, which can be seen from function names. Its purpose itto load image file, apply selected filters on it and save result to disk. Aftera bit of research we can notice that image should be in Netpbm format, supportedmodes (magics) are P1, P5 and P6.
Following filters are supported, with their IDs:
```0: NOP1: Blur2: Downsample3: Lineblur4: Watermark5: Emboss6: TV ```
### Research
Command line looks like `./imager <--config 888> 3 0 1 2 in out`, where configis optional and provides the "year" parameter used in Blur filter. `3` is lengthof filter list, which follows right after. `in` and `out` are input and outputfiles.
After parsing command line, `Task` structure is filled. It is not saved in debuginfo, so you need to reverse engineer its layout:
``` cppstruct Task{  uint64_t filter_ids[3];   // filter IDs array  uint64_t filter_num;      // number of filters to apply  int64_t (*filter_handlers[3])(uint8_t*);  // array of pointers to filter functions  uint8_t *data;            // image data};```
First bug can be found here: `Task` structure clearly can only hold up to 3filters, but the code checks that passed filter list length is no more than 4.It introduces off-by-one error.
After successful command line parsing `handleHeader` function is called. Itparses `in` image and fills the `Task::data` member. Image size should beexactly 200x200. Interesting moment to note here is that `data` is mmapped asRWX. Other than that, nothing wrong occurs here, and AFL did not find anythinginteresting.
Next goes the filter applying stage, `doIt` function. It loops through filterhandlers and applies them to `data`, and it does it in rather peculiar way. Thecode is best explained by itself so here it is:
``` cpp  if ( task->filter_num <= 4uLL )  {    filter_ptr = task->filter_handlers;    successes = 0;    max_iterations = 8;    while ( (unsigned __int64)successes < task->filter_num && max_iterations > 0 )    {      --max_iterations;      res = 1;      if ( *filter_ptr )        res = (*filter_ptr)(task->data);      if ( res )        ++successes;      ++filter_ptr;    }  }```
First of all, we see `filter_num <= 4` comparison again, when it should be `<4`.Next, note the `successes < task->filter_num` comparison. If filter returns 0,`successes` won't be incremented and eventually `filter_ptr` will go past theend of `filter_handlers` array. What goes right after `filter_handlers`? `data`pointer, which is mmapped as RWX!
From this point exploitation path is clear: all we need to do is to get`filter_ptr` to point to `data` field. It can be achieved in two ways: playingwith `filter_num` field on command line parsing stage, or returning 0 from afilter somehow. We will proceed with the latter approach.
### Filters
Only two filters are interesting in some ways, out of seven provided: Blur andWatermark. "Interesting" means that they have sufficiently complex logic inthem. For our purposes, returning 0 from a filter, only Blur is interesting,since Watermark explicitly sets result to 1 before returning.
#### Blur
In Blur we have a structure on the stack. Its name is demangled as`blur(unsigned char *)::{unnamed type#1}::blur` by IDA, let's call it `blur`.Its layout, obtained after reverse engineering the whole Blur function:
``` cppstruct Blur{  int i;  int near_it;  int off;  int X;  int Y;  int offX;  int offY;  int off_px;  int mean;  uint32_t size;  uint8_t *buf;     // from malloc, data is copied to buf  uint8_t near_px[20];  char result;};```
Blur uses the `--config` option value, which defaults to 20. It is used by`getBlurSize` function in following SQL query: `SELECT value FROM config%d WHERE key = 'blursize'`.There are only two such tables, `config20` and `config21`, and the values inthem are `2` and `3` respectively, which can be seen in `db.sql` file.
The size means how much adjacent pixel layers Blur filter should use for anindividual pixel. In other words, 2 means matrix 3x3, and 3 means matrix 5x5.
Blur code was definitely implemented first for blur size 2 only. After that, aweak effort was made to support arbitrary blur sizes, but it was not enough.First of all, it seems, that you can access `buf` at negative offsets, althoughit is not profitable for exploitation. What is really useful for us is the`near_px` array. All adjacent pixel values are copied there, so 9 in case ofsize 2, and 25 in case of size 3. Since array size is only 20, running with blursize 3 will overwrite the `result` field, which is set in the beginning offunction and is not modified afterwards. We need to set it to 1 if we want toreturn 0 from Blur. It can be achieved by getting blur size 3 and setting lastfew picture rows to 0x01.
## Exploitation
The plan is:* Pass 21 to `--config`* Pass `3 0 0 1` or something similar to filter list* Return 0 from Blur* that's all
### Passing 21
We can note that value `21` is explicitly prohibited in `app.py`. We can alsonote that this parameter is parsed by Imager using `strtol(..., 0);` which givesus a lot of ways to bypass the check and get 21 in Imager as result: `025`,`0x21`, `4294967317`, ...
### Return 0 from Blur
As said before, just set last few pixel rows to 0x01.
### RCE!
Well, we jump to `data` now, but it is a bit "corrupted" after Blur, and by "a bit" I mean "a lot". What we need to create is such picture that will producethe desired shellcode at offset 0 after Blur.
First detail to note is that Blur does not process the outermost layer ofpixels, so they have values 0 after Blur. It means that right after calling`data` we jump to a row of 200 zeroes, which thankfully form 100 harmlessinstructions `add BYTE PTR [rax], al` 
Another detail is that blur result is as if it was performed in 3x3 mode, evenif we pass `--config` 21.
### "Blur-resistant" shellcode
After a lot of trial and error we came to two-stage shellcode architecture.
First stage has following properties:* It is easy to find an image that will produce this shellcode after Blur.* All this stage does is writing second stage somewhere and jumping to it.
This shellcode can be split in 3 steps:* Initializing* Writing second stage* Jumping to it
To get images from shellcode (perform reverse-Blur process) we used z3.
To explain a bit more why this task is not trivial: let's look what happensduring Blur.
```a b c d      A B C Dl m n o  ->  L M N Ow x y z      W X Y Z
M = ((a+l+w) + (b+m+x) + (c+n+y)          ) / 9N = (          (b+m+x) + (c+n+y) + (d+o+z)) / 9```
As you can see, N "shares" most of its value with M. N "depends on" M, and somecombinations of N and M are not possible because no initial a,b,c,... valuesexist for them. Another thing to conclude here is that N value should be "near"M, i.e., M=0 N=0x80 situation is not possible, as well as M=0 N=0xFF (nowrapping).
To ease shellcode generation, we jump 3 rows forward in the end of each row. Inour example, it means that a,b,c,d,w,x,y,z will be used only in L,M,N,Oshellcode bytes calculation, and not any other ones that are on other rows.First row with actual shellcode is 1, from it we jump to 4, 7, etc.
Another approach to ease shellcode generation is to find ways to break the chainof dependencies in byte values. It can be done by inserting harmlessinstructions which have bytes which we can set to arbitrary values. For example,since we do not use conditional instructions, `cmp eax, 1234` is a greatcandidate, because it is encoded with one byte 0x3D after which go 4 bytes ofimmediate operand. We can set immediate to any value.
Image generating code from second stage shellcode: "gen_image.py"
### Payload
After getting arbitrary code execution we need to exfiltrate opponents' flags.The vulnerable binary is executed by Python web app, so we don't get anyinteractivity and the way to go is to drop some flags into the output file thatthe web app will later serve to us.
For this purpose, we coded a shellcode that does requests to Postgres DB usingthe functions already present in the binary itself. The flow of the shellcode isas follows:
1. Use return address into `doIt` on the stack to determine binary ASLR base2. Extract output filename from arguments in `main` stack frame3. Call `queryString` function inside the binary with an argument of   `select value from comments where value like 'FAUST_%' order by date desc offset 0 limit 1;`4. Write the result into the output file5. Patch the SQL query to now say `offset 1 limit 1`, execute and write again,    repeat 5 times.6. `exit(0)` to prevent Python throwing an exception
Full shellcode: "sqlshc" files.
### Exploit
Can be found in "photoshoot_vientvos_sploit.php".
[https://bushwhackers.ru/files/faust2020_photoshoot_writeup.7z](https://bushwhackers.ru/files/faust2020_photoshoot_writeup.7z)