InTouch reverse engineering

2022-02-03

Reverse engineering av fönsterfiler för InTouch 9.5

Här är ett litet försök att börja förstå hur WonderWare InTouch 9.5 sparar fönsterdata. För skojs skull har jag valt att endast använda standardverktyg som följer med de flesta Linux-distributioner, som GNU-verktygen diff, stat, grep, printf, dd, tail och head. Och BASH förstås. Samt xxd, som inte tillhör GNU, men som är vitt spridd och följer om inte annat med vim (tror jag).

Icke-fönster-relaterade filer

app.ver och INTOUCH.INI

När man sparar någonting, uppdateras INTOUCH.INI rad 111. Värdet efter ApplicationVersionNo= ökas med 1.

Ex:

ApplicationVersionNo=3284

ändras till:

ApplicationVersionNo=3285

Även app.ver ändras.

d50c innebär 0x0cd5 == 3285.

linkdefs.ini

Om man bara gör någon grafisk ändring ändras inte innehållet i linkdefs.ini men tidsstämpeln uppdateras.

ww_wdws.ndx

Ingen skillnad här, endast tidsstämpel.

Fönster-filer (.win)

Använder fönster “Temp” (win00355.win) som endast innehåller en rektangel (som är gul). Laborerar lite, sparar och kopierar filen till kataloger som jag kallar:

temp1: Fyrkant
temp2: Fyrkant flyttat nedåt en pixel
temp3: Fyrkant flyttat uppåt en pixel (tillbaka till temp1)

Temp1

Diff temp1 och temp3

Dessa borde vara identiska, men byte 116 har ändrats från 26 till 28. I temp2 var värdet 27. Detta är säkert ett versionsnummer som ökar med 1 varje gång fönstret sparas.

$ diff <(xxd temp1/win00355.win) <(xxd temp3/win00355.win)
8c8
< 00000070: 1200 0000 2600 0000 1a00 0000 1300 0000  ....&...........
---
> 00000070: 1200 0000 2800 0000 1a00 0000 1300 0000  ....(...........
 
$ xxd -s 116 -l 1 temp1/win00355.win
00000074: 26                                       &

$ xxd -s 116 -l 1 temp2/win00355.win
00000074: 27                                       '

$ xxd -s 116 -l 1 temp3/win00355.win
00000074: 28                                       (

Diff temp1 och temp2


$ diff <(xxd temp1/win00355.win) <(xxd temp2/win00355.win)
8c8
< 00000070: 1200 0000 2600 0000 1a00 0000 1300 0000  ....&...........
---
> 00000070: 1200 0000 2700 0000 1a00 0000 1300 0000  ....'...........
45,46c45,46
< 000002c0: 0000 3700 0000 8a00 0000 5200 0000 4001  ..7.......R...@.
< 000002d0: 0000 ac00 0000 0c00 0000 2f00 0000 0000  ........../.....
---
> 000002c0: 0000 3700 0000 8a00 0000 5300 0000 4001  ..7.......S...@.
> 000002d0: 0000 ad00 0000 0c00 0000 2f00 0000 0000  ........../.....

Byte 0x74 (116) skiljer sig, men det är förmodligen versionsnummer (se ovan).

Byte 0x2ca och 0x2d2 skiljer sig med 1. Någon av dessa borde vara Y-position.

I WindowMaker nere till höger syns koordinaterna när man markerat ett objekt. För temp1 gäller:

X: 138
Y: 82
W: 182
H: 90

Yay! Byte 0x2ca = 0x52 = 82. Så detta är Y-koordinat för aktuellt objekt.

Byte 0x2d2 = 0xac = 172. 172 - 82 = 90. Så detta är höjden för aktuellt objekt.

Ska prova flytta ned rektangeln 5 steg för att bekräfta. Spara till temp4.

Diff temp1 och temp4


$ diff <(xxd temp1/win00355.win) <(xxd temp4/win00355.win)
8c8
< 00000070: 1200 0000 2600 0000 1a00 0000 1300 0000  ....&...........
---
> 00000070: 1200 0000 2900 0000 1a00 0000 1300 0000  ....)...........
45,46c45,46
< 000002c0: 0000 3700 0000 8a00 0000 5200 0000 4001  ..7.......R...@.
< 000002d0: 0000 ac00 0000 0c00 0000 2f00 0000 0000  ........../.....
---
> 000002c0: 0000 3700 0000 8a00 0000 5700 0000 4001  ..7.......W...@.
> 000002d0: 0000 b100 0000 0c00 0000 2f00 0000 0000  ........../.....

0x52 -> 0x57 är diff på 5.
0xac -> 0xb1 är diff på 5.

Vad händer om jag klonar rektangeln och lägger nånstans?

Flyttade tillbaka originalrektangeln till temp1-position. Kopierade rektangeln och satte in på följande position och sparade som temp5:

X = 245
Y = 319

Temp5

Diff temp1 och temp5


$ diff <(xxd temp1/win00355.win) <(xxd temp5/win00355.win)
8c8
< 00000070: 1200 0000 2600 0000 1a00 0000 1300 0000  ....&...........
---
> 00000070: 1200 0000 2a00 0000 1a00 0000 1300 0000  ....*...........
47,48c47,59
< 000002e0: 0000 0c00 0000 3300 0000 0000 0000 0c00  ......3.........
< 000002f0: 0000 0000 0000 0000 0000                 ..........
---
> 000002e0: 0000 0c00 0000 2a00 0000 0000 0000 1600  ......*.........
> 000002f0: 0000 3f00 0000 0100 0000 0100 0000 3f00  ..?...........?.
> 00000300: 0000 0000 1800 0000 2d00 0000 0100 0000  ........-.......
> 00000310: 0400 0000 2d00 0000 0900 0000 2400 0000  ....-.......$...
> 00000320: 3500 0000 0100 0000 1000 0000 3500 0000  5...........5...
> 00000330: 0000 0000 0100 0000 0100 0000 ff83 ff00  ................
> 00000340: 2000 0000 3600 0000 0100 0000 0c00 0000   ...6...........
> 00000350: 3600 0000 0000 0000 ffff 0000 0400 0000  6...............
> 00000360: 2400 0000 3700 0000 0100 0000 1000 0000  $...7...........
> 00000370: 3700 0000 f500 0000 3f01 0000 ab01 0000  7.......?.......
> 00000380: 9901 0000 0c00 0000 2f00 0000 0000 0000  ......../.......
> 00000390: 0c00 0000 3300 0000 0000 0000 0c00 0000  ....3...........
> 000003a0: 0000 0000 0000 0000                      ........


Här finns en del intressanta saker. Bytesen från 0x2e0 och framåt (0000 0c00 0000 3300…) har flyttat till slutet med start på 0x38e. Dessförinnan har det lags till ett antal rader som måste beskriva min nya rektangel.

$ stat -c %s temp/{1,5}/*.win
762
936

$ expr 936 - 762
174

Närmare bestämt 174 bytes verkar min nya rektangel kosta.

Tiotusenkronorsfrågan lyder: Vad består dessa 174 bytes av?

Vad är en rektangel?

Först, låt oss undersöka mönster.

Vi antar att bytesekvensen 0000 0c00 0000 3300 0000 0000 0000 0c00 0000 0000 0000 0000 0000 talar om att filen är slut (vilket vore lite väl pratigt, så något mer döljer sig förmodligen här, men låt oss strunta i det nu). I outputen från senaste diffen ovan har vi identifierat 174 bytes med start på 0x2e0 som vår nya rektangel. Låt oss söka efter detta mönster och se vid vilken offset vår första rektangel börjar.

$ grep -obUaP "\x00\x00\x0c\x00\x00\x00\x2a\x00" temp5/win00355.win
562:
    *
736:
    *

Här har vi två offsets: 562 (0x232) och 736 (0x2e0). Den sistnämnda stämmer ju väl med vad vi redan vet. Så första rektangeln startar på offset 0x232.

Ett litet tips!

printf är ett ypperligt verktyg för att konvertera mellan hex och dec i terminalen.

$ printf '%x' 562
232

$ printf '%d' 0x232
562

Toppen!

Få se var vi hamnar om vi utgår från offset 0x232 och hoppar fram 174 bytes. Finns det någonting mellan våra två rektanglar måntro?

$ xxd -s 0x232 -l 174 temp5/win00355.win
00000232: 0000 0c00 0000 2a00 0000 0000 0000 1600  ......*.........
00000242: 0000 3f00 0000 0100 0000 0100 0000 3f00  ..?...........?.
00000252: 0000 0000 1800 0000 2d00 0000 0100 0000  ........-.......
00000262: 0400 0000 2d00 0000 0900 0000 2400 0000  ....-.......$...
00000272: 3500 0000 0100 0000 1000 0000 3500 0000  5...........5...
00000282: 0000 0000 0100 0000 0100 0000 ff83 ff00  ................
00000292: 2000 0000 3600 0000 0100 0000 0c00 0000   ...6...........
000002a2: 3600 0000 0000 0000 ffff 0000 0400 0000  6...............
000002b2: 2400 0000 3700 0000 0100 0000 1000 0000  $...7...........
000002c2: 3700 0000 8a00 0000 5200 0000 4001 0000  7.......R...@...
000002d2: ac00 0000 0c00 0000 2f00 0000 0000       ......../.....

Kanske inte helt synligt för ögat sådär direkt, men tittar man noga ser man att en rad utgörs av 16 bytes (genom att räkna varje sifferpar eller genom att räkna ut t ex 0x2d2 - 0x2c2), vår rektangel slutar fyra siffror innan en full sista rad, vilket betyder 14 (=0xe) bytes efter den offset som anges före kolonet längst till vänster i början av raden, vilket i sig innebär att första byten efter vår andra rektangel ligger på…

$ printf '%x'  $((0x2d2 + 0xe))
2e0

Den känner man igen! OK, så för att summera. Först hade vi en rektangel. Den tog 174 bytes och låg i slutet av filen direkt före de 26 bytes som vi tror signalerar att filen är slut. Sen lade vi till en rektangel. Den hamnade då direkt efter sin kompis och 26-bytes-sekvensen fick gå sist i led.

Låt oss lägga till en rektangel! :—D

Nu tycker jag det är hög tid att producera en klon direkt i filen och se om InTouch accepterar den!

Enter temp666.

temp666

Idén är följande:

  1. Vi tar rektangel #2.
  2. Vi klonar den till rektangel #3.
  3. Vi sätter X-koordinaten till hälften av X#2 - X#1 och Y till hälften av Y#2 - Y#1.

Då borde vi få en tjusigt sned lineup av rektanglar. (Spoiler alert: joråsatte.. läs vidare!)

Positionering

Vi vet sedan tidigare att Y-positionen för temp1 (Y#1) lagras på byte 0x2ca. Relativ rektangelposition kan då räknas ut med 0x2ca - 0x232:

$ printf '%x' $((0x2ca - 0x232))
98

Då ska alltså Y#2 ligga vid 0x2e0 + 0x98 = 0x378. Låt oss kontrollera!

$ for offset in {0x2ca,0x378}; do xxd -s $offset -l 1 temp5/win00355.win; done
000002ca: 52                                       R
00000378: 3f                                       ?

Första raden är Y-position för rektangel 1, som vi minns är 82 = 0x52. Check!
Andra raden ska då vara Y-position för rektangel 2. 0x3f = 63.

HUH!!??

Vår andra rektangel var ju längre NED än den första. Låt oss konsultera skärmdumpen ovan lite noggrannare.

temp5 koordinater

Här ser vi att korrekt koordinat är 319 = 0x13f. Och inte 0x3f.

AHA!

Vi kollade endast en byte, men koordinaterna sparas så klart som en int16 dvs 2 bytes.


$ for offset in {0x2ca,0x378}; do xxd -s $offset -l 2 temp5/win00355.win; done
000002ca: 5200                                     R.
00000378: 3f01                                     ?.

Lägst signifikant byte är 0x01 och andra byten 0x3f; 0x013f = 319.

Så. Då har vi koordinat Y#1 ocgh Y#2 och kan därmed bestämma Y#3:

$ printf '%x' $(((0x13f - 0x52) / 2))
76

\[Y_{\#3} = \frac{Y_{\#2} - Y_{\#1}}{2} = \frac{\mathtt{0x13f} - \mathtt{0x52}}{2} = \mathtt{0x76}\]

Men X-koordinaten då?

Ja jast ja. Låt oss leta reda på X#1, som vi med hjälp av skärmdumpen vet är 138 = 0x8a. Om vi startar på första fyrkantens byte 0 (d v s byte 0x232) och begränsar oss inom fyrkantens 174 bytes - och letar efter 0x8a så får vi fram det här.

$ tail -c +$((0x232+1)) temp5/win00355.win | head -c 174 | LANG=C grep -obUaP "\x8a"
148:

Obs! Här använder jag en kombination av tail och head för att plocka ut alla bytes från 0x232 och 174 bytes framåt. tail börjar inte räkna på 0 utan 1, det är därför jag plussar med 1.

Sedan använder jag grep för att söka efter hex-innehållet 8a och returnerar byteoffset. LANG=C behövs, för annars fungerar det inte. :-)

Gissningvis lagras även X-koordinaten i en int16, så låt oss kolla 2 st bytes 148 bytes in (+0x94) på fyrkant #2 (0x2e0) och se om värdet verkar rimligt för en X-koordinat!

$ xxd -s $((0x2e0 + 0x94)) -l 2 temp5/win00355.win
00000374: f500                                     ..

$ printf '%d' 0xf5
245

Stämmer 245 överens med skärmdumpen?

Temp5 inzoooooomad

JAAA!!!

Let’s get busy!

Då har vi alltså en ny Y-koordinat och kan räkna ut X.

\[X_{\#3} = \frac{X_{\#2} - X_{\#1}}{2} = \frac{\mathtt{0xf5} - \mathtt{0x8a}}{2} = \mathtt{0x35}\]

Ergo:

\[X_{\#3} = \mathtt{0x35}\] \[Y_{\#3} = \mathtt{0x76}\]

Nu återstår alltså bara att skapa en ny rektangel.

Offset

Fyrkant 1 börjar på 0x232, fyrkant 2 på 0x232 + 0xae =0x2e0 och vår ny fyrkant 3 ska då börja på 0x2e0 + 0xae =0x38e.

Kloning

Låt oss kopiera fyrkant 2 och klistra in direkt efter.

$ { head -c -26 temp5/win00355.win; tail -c +$((0x2e0+1)) temp5/win00355.win | head -c 174; tail -c 26 temp5/win00355.win ;} > temp666/win00355.win

Detta ser krångligt ut men är ganska enkelt.

Nu återstår att sätta in rätt värden för X och Y.

X-koordinaten ligger på +0x94 alltså på 0x38e + 0x94 = 0x422.

Låt oss ta en kopia av filen så att vi har något att jämföra med när vi vill verifiera att det fungerar. :-)

$ cp temp5/win00355.win temp5/win00355.win.2

Nu skriver vi!

Sådär, dags att ändra X-koordinat.

$ printf '\x35\x0' | dd of=temp666/win00355.win bs=1 seek=$((0x422)) count=2 conv=notrunc
2+0 records in
2+0 records out
2 bytes copied, 0,00277203 s, 0,7 kB/s

Det ovanstående gör är sonika att ersätta byte 0x422 med värdet 0x35 och 0x423 med värde 0x0.

Låt oss kontrollera att det fungerade som tänkt…


$ diff <(xxd temp666/win00355.win.2) <(xxd temp666/win00355.win)
67c67
< 00000420: 0000 f500 0000 3f01 0000 ab01 0000 9901  ......?.........
---
> 00000420: 0000 3500 0000 3f01 0000 ab01 0000 9901  ..5...?.........

Wohoo!

Då blir det samma procedur med Y-koordinaten.

$ printf '\x76\x0' | dd of=temp666/win00355.win bs=1 seek=$((0x426)) count=2 conv=notrunc
2+0 records in
2+0 records out
2 bytes copied, 7,6225e-05 s, 26,2 kB/s

Och kontroll…


$ diff <(xxd temp666/win00355.win.2) <(xxd temp666/win00355.win)
67c67
< 00000420: 0000 3500 0000 7600 0000 ab01 0000 9901  ..5...v.........
---
> 00000420: 0000 f500 0000 3f01 0000 ab01 0000 9901  ......?.........

Utmärkt!

Slutgiltigt test

Hade InTouch gjort den här ändringen hade versionnummer både i .win-filen men även i några andra filer ändrats. Låt oss vara lite wild and crazy och strunta i det och se vad som händer! :-D

Kopierar filen och öppnar i WindowMaker…

Resultat

OK. Så här bidde det hela.

WindowMaker klagade inte över att vi pillat med hens fil. Inte heller över att vi struntat i densammes allehanda versionnuffror. Däremot visade InTouch sitt missnöje på ett lite retsamt vis: genom att rendera ett monster!

En mink bland hermelinerna

Fick vi en till rektangel? Ja.
Fick vi till rätt koordinater? Faktiskt - ja. Se själv i skärmdumpen. X=53=0x35, Y=118=0x76.

Så, varför är vår fyrkant så groteskt stor?

Tillbaks till ruta ett

Bläddrar vi upp ända till rubriken Diff temp1 och temp3 ser vi klart och tydligt att det var två värden som ändrades när jag knuffade första fyrkanten en pixel på Y-axeln. Inte ett värde. Det ena värdet listade vi ju snabbt ut att det var Y-koordinaten. Men det andra värdet, som vi fullständigt gav fan i, verkar onekligen vara höjden. Vi måste nog alltså öka/minska lika mycket på höjden som på Y och förmodligen samma med bredd och X.

Höjd och bredd

Genom att studera Diff temp1 och temp5 och skärmdumpen direkt ovanför diffen, kan vi lista ut saker och ting. I skärmdumpen hittar vi X=245, Y=319, W=182, H=90. I och med att höjden ändras med samma värde som Y-positionen när vi knuffar runt på vår fyrkant, är det rimligt att anta att det som sparas inte är höjden per se utan ytterligare en koordinat: höjden fås genom att ta denna koordinat minus Y. Få se huruvida detta antagande bär frukt.

Med Y=319 och H=90 ska alltså värdet 319 + 90 = 409 = 0x199 återfinnas nånstans. Fast som vi sett tidigare swappas bytesen, så vi letar efter 0x9901. Rimligtfinns ligger den nånstans i närheten av Y = 319 = 0x13f = 0x3f01.


$ diff <(xxd temp1/win00355.win) <(xxd temp5/win00355.win)
8c8
< 00000070: 1200 0000 2600 0000 1a00 0000 1300 0000  ....&...........
---
> 00000070: 1200 0000 2a00 0000 1a00 0000 1300 0000  ....*...........
47,48c47,59
< 000002e0: 0000 0c00 0000 3300 0000 0000 0000 0c00  ......3.........
< 000002f0: 0000 0000 0000 0000 0000                 ..........
---
> 000002e0: 0000 0c00 0000 2a00 0000 0000 0000 1600  ......*.........
> 000002f0: 0000 3f00 0000 0100 0000 0100 0000 3f00  ..?...........?.
> 00000300: 0000 0000 1800 0000 2d00 0000 0100 0000  ........-.......
> 00000310: 0400 0000 2d00 0000 0900 0000 2400 0000  ....-.......$...
> 00000320: 3500 0000 0100 0000 1000 0000 3500 0000  5...........5...
> 00000330: 0000 0000 0100 0000 0100 0000 ff83 ff00  ................
> 00000340: 2000 0000 3600 0000 0100 0000 0c00 0000   ...6...........
> 00000350: 3600 0000 0000 0000 ffff 0000 0400 0000  6...............
> 00000360: 2400 0000 3700 0000 0100 0000 1000 0000  $...7...........
> 00000370: 3700 0000 f500 0000 3f01 0000 ab01 0000  7.......?.......
> 00000380: 9901 0000 0c00 0000 2f00 0000 0000 0000  ......../.......
> 00000390: 0c00 0000 3300 0000 0000 0000 0c00 0000  ....3...........
> 000003a0: 0000 0000 0000 0000                      ........


Mycket riktigt. Höjdinformation lagras på fjärde int16 efter Y-koordinaten. På samma sätt lagras breddinformationen (X + W = 245 + 182 = 0x1ab = 0xab01) fyra int16 efter X-koordinaten (0xf5). Omräknat till relative offsets kan vi göra följande sammanställning.

X: +0x94
Y: +0x98
W: +0x9c
H: +0xa0

Så vi har alltså att göra med fyra int16 som ligger direkt efter varandra i ordningen X-koordinat, Y-koordinat, Bredd, Höjd.

Låt oss få fason på vår groteska fyrkant!

För fyrkant 3 som startar på offset 0x38e gäller således:

X: 0x38e + 0x94 = 0x422
Y: 0x38e + 0x98 = 0x426
W: 0x38e + 0x9c = 0x42a
H: 0x38e + 0xa0 = 0x42e

Eftersom vi vill behålla samma bredd och höjd som moderrektangeln, som ju var 182 respektive 90, är det bara att sätta igång och plussa.

W = X + 182 = 0x35 + 0xb6 = 0xeb

H = Y + 90 = 0x76 + 0x5a = 0xd0

Låt oss skriva:

$ printf '\xeb\x0' | dd of=temp666/win00355.win bs=1 seek=$((0x42a)) count=2 conv=notrunc
2+0 records in
2+0 records out
2 bytes copied, 9,1291e-05 s, 21,9 kB/s

$ printf '\xd0\x0' | dd of=temp666/win00355.win bs=1 seek=$((0x42e)) count=2 conv=notrunc
2+0 records in
2+0 records out
2 bytes copied, 0,000121587 s, 16,4 kB/s

Och kopiera filen till InTouch-katalogen, starta om InTouch och…….

…och…

…och…

En tjusigt sned lineup

En fyrkant inte alls grotesk, utan lika näpen som sina syskon; ej längre en mink bland hermeliner, men kanske än odresserad hermelin bland… dresserade hermeliner? Hur som helst har fyrkanten exakt de dimensioner vi önskade, men den är felplacerad.

Och den är inte felplacerad på grund av att InTouch är ett värdelöst skitprogram, utan för att VI (inte jag) är en värdelös skitmatematiker.

Vi glömde nämligen plussa på X#1 och Y#1 när vi räknade ut X#3 och Y#3.

Nytt försök

\[X_{\#3} = X_{\#1} + \frac{X_{\#2} - X_{\#1}}{2} = \mathtt{0x8a} + \frac{\mathtt{0xf5} - \mathtt{0x8a}}{2} = \mathtt{0xbf}\]

\[Y_{\#3} = Y_{\#1} + \frac{Y_{\#2} - Y_{\#1}}{2} = \mathtt{0x52} + \frac{\mathtt{0x13f} - \mathtt{0x52}}{2} = \mathtt{0xc8}\]

\[W_{\#3} = X_{\#3} + 182 = \mathtt{0xbf} + \mathtt{0xb6} = \mathtt{0x175} = \mathtt{0x7501}\]

\[H_{\#3} = Y_{\#3} + 90 = \mathtt{0xc8} + \mathtt{0x5a} = \mathtt{0x122} = \mathtt{0x2201}\]

Skriva lite…

$ printf '\xbf\x0' | dd of=temp666/win00355.win bs=1 seek=$((0x422)) count=2 conv=notrunc
2+0 records in
2+0 records out
2 bytes copied, 9,147e-05 s, 21,9 kB/s

$ printf '\xc8\x0' | dd of=temp666/win00355.win bs=1 seek=$((0x426)) count=2 conv=notrunc
2+0 records in
2+0 records out
2 bytes copied, 4,7636e-05 s, 42,0 kB/s

$ printf '\x75\x01' | dd of=temp666/win00355.win bs=1 seek=$((0x42a)) count=2 conv=notrunc
2+0 records in
2+0 records out
2 bytes copied, 9,8921e-05 s, 20,2 kB/s

$ printf '\x22\x01' | dd of=temp666/win00355.win bs=1 seek=$((0x42e)) count=2 conv=notrunc
2+0 records in
2+0 records out
2 bytes copied, 0,000204353 s, 9,8 kB/s

Kopiera lite…

Ooooch….

WOHOO!