Tags: programming python forensic steg
Rating: 2.0
**Writeup for 7/12**
-----
##### Forgive the bad script & if I had missed anything
##### Feel free to Hit me up on discord AmunRha#3245
-----
> A really intresting challenge. Same file as 7/11.
## Analysis
Extracting the remaining files from 7/11 you get around 375 .7z files which when extracted provides with junk.
We know that the first file[7/11] contained the second file we needed[part2] in between the start header chunk and the end header chunk of the 7z file.
> Second file wasnt merely appended.
Reading through the 7z file format specifications we learn that there is a definite structure to the start header and the end header.
> Check this link out to know more about it.
> [https://py7zr.readthedocs.io/en/latest/archive_format.html#file-format](https://py7zr.readthedocs.io/en/latest/archive_format.html#file-format)
The next header[End header location] is at offset `0xc` and the values given are relative to the start header, so the end header offset is at `0x20 + [value of next header]`
> Read this for the detailed info
> [https://www.7-zip.org/recover.html](https://www.7-zip.org/recover.html)
The thing to notice here is that, if we merely change those next header values to what we want, say increase it by 20 bytes, then we could literally put any data in between the original data of the 7z and the start of the end header.
And thats what the authors did to append the part2 file onto the origianl 7/11 file.
Since the hint given also said about "Understanding" the first challenge, we can say the second challenge has something to do with some data embeded in the same way.
Analysis all those 7z files with a hex editor and going below
Used a bash script to check out all files
```bash
echo "[*] Script running..."
for i in {0..375}
do
echo part2_$i.7z
xxd part2_$i.7z
done
```
We can see multiple PNG chunks scattered accross all those files at the end, with no pattern to it, like a particular size, etc [Happy that it was all in order and not at scattered accross random files]
Thus, our assumption was right, now we need to find a way to restore them all and get the original png
I personally tried reading through all the formats of PNG, and 7z files and wasnt able to properly implement them. Since the time left started to become shorter i tried another trick.
-----
## Solve
I wanted to find a pattern, like the PNG chunks start at this place and end at this place.
The end place was easy to find cause it was right before the End Header's start offset.
But the start place was sort of confusing, so i used a workaround for it.
I used the 7z linux cmd `7z t [filename]` to test if everything was okay with the file, and it returned "Everything was OK". I tried chaning a byte using hex editor at a random place in the 7z file and tested again, and it returned "Errors in [filename].7z".
But changing a byte in those PNG chunks returned "Everything was OK". So I wondered if this can be utilised for our solution.
I tried chaning byte by byte in reverse order starting from the Start offset of the End header[End of PNG chunk] and tested it with 7z until i got output as error from the 7z tool.
This placed me at the start of the scattered PNG chunk in that file.
Automate this is in python for 375 files, thus getting the start and end of the scattered PNG chunks, and extract that all to create a new png file gets you the flag.
Or so i expected. This is where things started getting problematic. I was sure that the method I used definetly should have worked out but for some reason the PNG stayed corrupted.
Checking with the pngcheck utility in linux `pngcheck -f -vv [filename].png` we get detailed errors.
```
File: flag.png (9424 bytes)
chunk IHDR at offset 0x0000c, length 13
1300 x 72 image, 32-bit RGB+alpha, non-interlaced
chunk sRGB at offset 0x00025, length 1
rendering intent = perceptual
chunk gAMA at offset 0x00032, length 4: 0.45455
chunk pHYs at offset 0x00042, length 9: 3778x3778 pixels/meter (96 dpi)
chunk IDAT at offset 0x00057, length 9315
zlib: deflated, 32K window, fast compression
row filters (0 none, 1 sub, 2 up, 3 avg, 4 paeth):
1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 0 0 0 0 invalid row-filter type (73)
0
183 0 0 0 0 0 0 0 219 0 0 0 0 0 0 0 182 0 0 0 0 0 0 255 255
255 255 0 0 255 255 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 (72 out of 72)
CRC error in chunk IDAT (computed ea345781, expected 4020dd24)
invalid chunk name "" (00 00 49 45)
chunk at offset 0x024c6, length 1875640320: EOF while reading data
ERRORS DETECTED in flag.png
```
I asked the authors help to decode what these errors are supposed to mean[google didnt help at all], thats when they said that I have extra two bytes in my output file which can be observed from this particular line in that output,
` invalid chunk name "" (00 00 49 45)`
`chunk at offset 0x024c6, length 1875640320: EOF while reading data`
So, onto we proceed to remove two bytes, but how do we know which two bytes those two are supposed to be?
We could either brute the whole procees using a simple script, that is, change two bytes at a time using two for loops and check the output with pngcheck to see if any errors existed, if there is try another two bytes.
I explained this to the author and they were really supportive.
> Brute script attached below.
We find those two bytes not required at offset 0x793(1939) and 0x8a6(2214). Remove those two and you get the final flag.png with no errors in it.
> Solution script attached below.
### Flag - tstlss{Nice!_Did_you_parse_the_properties?}
-----
## Script
Solution script:
```python
import binascii
import subprocess
import re
final_png_hxdmp = ''
for j in range(376):
filename = f'part2_{j}.7z'
with open(filename, 'rb') as f:
content = f.read()
hexdump = binascii.hexlify(content).decode('utf8')
nxt_hdr = ''
for i in range(0,16,2):
if hexdump[24+i:26+i] == '00' and hexdump[26+i:28+i] == '00':
break
nxt_hdr += hexdump[24+i:26+i]
nxt_hdr = re.findall('..',nxt_hdr)
nxt_hdr.reverse()
nxt_hdr = ''.join(nxt_hdr)
nxt_hdr_int = ((int(nxt_hdr, 16)+32)*2) - 2
with open(filename, 'rb') as f:
content = f.read()
hexdump = binascii.hexlify(content).decode('utf8')
start_chnk = end_chnk = int(nxt_hdr_int+2)
i = 0
while True:
hexdump = binascii.hexlify(content).decode('utf8')
hexdump = hexdump[:nxt_hdr_int-i] + '2e' + hexdump[nxt_hdr_int+2-i:]
hexdump = bytes.fromhex(hexdump)
f2 = open("blah2.7z", "wb")
f2.write(hexdump)
f2.close()
process = subprocess.Popen("7z t blah2.7z", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
output = process.stdout.readlines()
if b'Everything is Ok\n' in output:
pass
else:
#print("[*] blah2.7z File Error")
start_chnk = int((nxt_hdr_int+2-i))
break
i+=2
#print(f'[*] Start chunk found - {start_chnk}\n[*] End chunk found - {end_chnk}')
hexdump = binascii.hexlify(content).decode('utf8')
png_chnk = hexdump[start_chnk:end_chnk]
#print(f'[*] PNG chunk values:\n')
print(f'\n\nFile no. {j}: \n')
print(png_chnk)
final_png_hxdmp += png_chnk
#Final file had two byte extra at offset 0x793(1939) and 0x8a6(2214)
#remove that
final_png_hxdmp = final_png_hxdmp[:3880] + "" + final_png_hxdmp[3882:4428] + "" + final_png_hxdmp[4430:]
final_png_hxdmp = bytes.fromhex(final_png_hxdmp)
f3 = open("flag.png", "wb")
f3.write(final_png_hxdmp)
f3.close()
```
Brute to find the wrong two bytes:
```python
import binascii
import subprocess
def write_to(data):
f3 = open("test.png", "wb")
f3.write(data)
f3.close()
def check_error():
process = subprocess.Popen("pngcheck -f -v test.png", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
output = process.stdout.readlines()
return output
def correct_png(hexdump):
for i in range(92, 9425, 2):
new_hx = hexdump[:i] + "" + hexdump[2+i:]
for j in range(i+1, 9425, 2):
new_hx = hexdump[:j] + "" + hexdump[2+j:]
new_hx = bytes.fromhex(new_hx)
write_to(data=new_hx)
output = check_error()
if b'ERRORS DETECTED in test.png\n' in output:
print('[*] Checking for First byte at - ' + str(i) + 'and Second byte at - ' + str(j))
elif b'Segmentation fault\n' in output:
print('[*] Checking for First byte at - ' + str(i) + 'and Second byte at - ' + str(j))
else:
print("[*] Succesfully retrieved the image.")
print('[*] First byte at - ' + str(i) + 'and Second byte at - ' + str(j))
break
with open("flag.png", 'rb') as f:
content = f.read()
hexdump = binascii.hexlify(content).decode('utf8')
correct_png(hexdump)
```
-----