This is real case: I exported config data from some old TVT CCTV camera, and the file looked like this:
kjof{v^`yj'>+<+2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 27%7 pai p{y {c||u |c|}pL{h3wpc7%72 2 2 2 2
2 2 2 2 2 2 2 2 2 2 2 2 2^M
!0ebq/kjo|t`s2?>3??/xa~`yfsh -H[["%-"1
3~`sith=yx}nfra -,!--=wpcs| -u{i^?' 2xjx3fml3lrb2yx},??1
!{d^?x|#
!nqnobTaK`q{|hx1
!jszp1S@! xahb#
3xahb#A^32jszp1
32nqnobTaK`q{|hx1
3r|~@^?exli\inizn1
!jszp1|m|ayzp32jszp1
!jszp1rmw|i`qjs32jszp1
32`nlRmwj~{N{|{h|#
32{d^?x|#
3njs|r}={d^?x2?ct|i-=lrzs{ -,-#
!fijp1
!fy/ivmj -hfs{.=?1,32fy1
!|xan`oA|bx/ivmj -n{ofsh?/pneCxa -,>?1!.FLYNINFR@1! njs|r}Snpj#
3nxt{~g={d^?x2?mr`qj|a?1{nq|x32|jfilu1
!yrcinzj={d^?x2?nqnobTaK`q{|hx-#AR32yrcinzj#
3|c|}pGrcy[tbx/ivmj -hfs{.=?1/?! |c|}pGrcy[tbx1
...
Clearly, this has some XML form. And I suspect the key has 2-byte length (see the first line).
Let's try all 2-byte XOR keys, and print only (decrypted) strings where all characters are printable:
#!/usr/bin/env python3
from __future__ import print_function
import sys, struct
KEYLEN=2
def xor_strings(s,t):
# https://en.wikipedia.org/wiki/XOR_cipher#Example_implementation
"""xor two strings together"""
return b"".join(bytes([a^b]) for a,b in zip(s,t))
def read_file(fname):
file=open(fname, mode='rb')
content=file.read()
file.close()
return content
def chunks(l, n):
"""divide input buffer by n-len chunks"""
n = max(1, n)
return [l[i:i + n] for i in range(0, len(l), n)]
#cipher_file=read_file(sys.argv[1])
cipher_file=b"!|xan`oA|bx/ivmj -n{ofsh?/pneCxa -,>?1!.FLYNINFR@1! njs|r}Snpj#"
def is_printable(t):
for c in t:
if c<0x20:
return False
if c>=0x80:
return False
return True
for i in range(256):
for j in range(256):
possible_key=struct.pack("BB", i, j)
tmp=chunks(cipher_file, KEYLEN)
plain=b"".join(map(lambda x: xor_strings(x, possible_key), tmp))
if is_printable(plain):
print (i,j,plain)
The resulting list has 1024 possible keys:
0 0 b'!|xan`oA|bx/ivmj -n{ofsh?/pneCxa -,>?1!.FLYNINFR@1! njs|r}Snpj#'
0 1 b'!}x`nao@|cx.iwmk ,nzogsi?.poeBx` ,,??0!/FMYOIOFS@0!!nks}r|Sopk#'
0 2 b'!~xcnboC|`x-itmh /nyodsj?-pleAxc /,<?3!,FNYLILFP@3!"nhs~r\x7fSlph#'
0 3 b'!\x7fxbncoB|ax,iumi .nxoesk?,pme@xb .,=?2!-FOYMIMFQ@2!#nis\x7fr~Smpi#'
0 4 b'!xxendoE|fx+irmn )n\x7fobsl?+pjeGxe ),:?5!*FHYJIJFV@5!$nnsxrySjpn#'
...
29 12 b'<pemslrMane#tzpf=!swrjnd"#mbxOem=!12"=<"[@DBTB[^]=<,sfnpoqNbmf>'
29 13 b'<qelsmrLaoe"t{pg= svrkne""mcxNel= 13"<<#[ADCTC[_]<<-sgnqopNcmg>'
29 14 b'<reosnrOale!txpd=#surhnf"!m`xMeo=#10"?< [BD@T@[\\]?<.sdnrosN`md>'
29 15 b'<sensorName type="string" maxLen="11"><![CDATA[]]></sensorName>'
...
It can be browsed manually and checked for meaningful data. Now we got decryption key: 29 15. But during further inspection I've found that some bytes are not encrypted: 0x09 (tab), 0x0A (newline) -- and this is why encrypted file preserves some visual structure.
Let's decrypt:
#!/usr/bin/env python3
from __future__ import print_function
import sys, struct
KEYLEN=2
def xor_strings(s,t):
# https://en.wikipedia.org/wiki/XOR_cipher#Example_implementation
"""xor two strings together"""
return b"".join(bytes([a^b]) for a,b in zip(s,t))
def read_file(fname):
file=open(fname, mode='rb')
content=file.read()
file.close()
return content
def chunks(l, n):
"""divide input buffer by n-len chunks"""
n = max(1, n)
return [l[i:i + n] for i in range(0, len(l), n)]
cipher_file=read_file(sys.argv[1])
k=29 # current state of k
for c in cipher_file:
if c>=0x20:
sys.stdout.write (chr(c^k))
else:
sys.stdout.write (chr(c)) # skip unencrypted parts
if k==29:
k=15
elif k==15:
k=29
The output:
verifyCode:1636===================================***/mnt/mtd/flash/alarmCfg.xml***===================================
<?xml version="1.0" encoding="UTF-8"?>
<config version="1.0" xmlns="http://www.ipc.com/ver10">
<types>
<alarmInVoltage>
<enum>NO</enum>
<enum>NC</enum>
</alarmInVoltage>
<oscObjectStatus>
<enum>abandum</enum>
<enum>objstolen</enum>
</oscObjectStatus>
</types>
<sensor type="list" count="1">
<item>
<id type="uint32">1</id>
<sensorName type="string" maxLen="11"><![CDATA[]]></sensorName>
<switch type="boolean">false</switch>
<voltage type="alarmInVoltage">NO</voltage>
<alarmHoldTime type="uint32">20</alarmHoldTime>
<triggerAlarmOut type="list" count="1">
<itemType type="boolean"/>
<item id="1">false</item>
...
A practicing reverse engineering should solve such tasks without noticable effort.
