Ugrás a lényegre

Könnyű reverse feladat (serial1)

Adott egy futtatható ELF fájl, forráskód nélkül. A program egy kulcsot vár, de a kulcsról nem tudunk semmit. A feladat a helyes kulcs megtalálása.

Megoldás

Objdump-pal megnézzük a forráskódot.

$ objdump -d serial1

0000000000001050 <main>:
1050: 53 push %rbx
1051: 83 ff 02 cmp $0x2,%edi
1054: 75 6a jne 10c0 <main+0x70>
1056: 48 8b 5e 08 mov 0x8(%rsi),%rbx
105a: 48 89 df mov %rbx,%rdi
105d: e8 de ff ff ff call 1040 <strlen@plt>
1062: 48 83 f8 0c cmp $0xc,%rax
1066: 75 45 jne 10ad <main+0x5d>
1068: 80 3b 4b cmpb $0x4b,(%rbx)
106b: 75 40 jne 10ad <main+0x5d>
106d: 80 7b 01 53 cmpb $0x53,0x1(%rbx)
1071: 75 3a jne 10ad <main+0x5d>
1073: 80 7b 02 5a cmpb $0x5a,0x2(%rbx)
1077: 75 34 jne 10ad <main+0x5d>
1079: 80 7b 03 4b cmpb $0x4b,0x3(%rbx)
107d: 75 2e jne 10ad <main+0x5d>
107f: 80 7b 04 2d cmpb $0x2d,0x4(%rbx)
1083: 75 28 jne 10ad <main+0x5d>
1085: 0f b6 43 09 movzbl 0x9(%rbx),%eax
1089: 32 43 0a xor 0xa(%rbx),%al
108c: 32 43 05 xor 0x5(%rbx),%al
108f: 32 43 06 xor 0x6(%rbx),%al
1092: 32 43 07 xor 0x7(%rbx),%al
1095: 32 43 08 xor 0x8(%rbx),%al
1098: 38 43 0b cmp %al,0xb(%rbx)
109b: 75 10 jne 10ad <main+0x5d>
109d: 48 8d 3d 94 0f 00 00 lea 0xf94(%rip),%rdi # 2038 <_IO_stdin_used+0x38>
10a4: e8 87 ff ff ff call 1030 <puts@plt>
10a9: 31 c0 xor %eax,%eax
10ab: 5b pop %rbx
10ac: c3 ret
10ad: 48 8d 3d 67 0f 00 00 lea 0xf67(%rip),%rdi # 201b <_IO_stdin_used+0x1b>
10b4: e8 77 ff ff ff call 1030 <puts@plt>
10b9: b8 01 00 00 00 mov $0x1,%eax
10be: 5b pop %rbx
10bf: c3 ret
10c0: 48 8d 3d 3d 0f 00 00 lea 0xf3d(%rip),%rdi # 2004 <_IO_stdin_used+0x4>
10c7: e8 64 ff ff ff call 1030 <puts@plt>
10cc: eb eb jmp 10b9 <main+0x69>
10ce: 66 90 xchg %ax,%ax

Ez a main függvény kódja. Részről részre:

argc == 2
    1051:	83 ff 02             	cmp    $0x2,%edi
1054: 75 6a jne 10c0 <main+0x70>
strlen(argv[1]) == 0x0c (11)
    1056:	48 8b 5e 08          	mov    0x8(%rsi),%rbx
105a: 48 89 df mov %rbx,%rdi
105d: e8 de ff ff ff call 1040 <strlen@plt>
1062: 48 83 f8 0c cmp $0xc,%rax
1066: 75 45 jne 10ad <main+0x5d>
serial[0:5] == "KSZK-"
    1068:	80 3b 4b             	cmpb   $0x4b,(%rbx)
106b: 75 40 jne 10ad <main+0x5d>
106d: 80 7b 01 53 cmpb $0x53,0x1(%rbx)
1071: 75 3a jne 10ad <main+0x5d>
1073: 80 7b 02 5a cmpb $0x5a,0x2(%rbx)
1077: 75 34 jne 10ad <main+0x5d>
1079: 80 7b 03 4b cmpb $0x4b,0x3(%rbx)
107d: 75 2e jne 10ad <main+0x5d>
107f: 80 7b 04 2d cmpb $0x2d,0x4(%rbx)
1083: 75 28 jne 10ad <main+0x5d>
seraial[5:10] XOR önmagával karakterenként
    1085:	0f b6 43 09          	movzbl 0x9(%rbx),%eax
1089: 32 43 0a xor 0xa(%rbx),%al
108c: 32 43 05 xor 0x5(%rbx),%al
108f: 32 43 06 xor 0x6(%rbx),%al
1092: 32 43 07 xor 0x7(%rbx),%al
1095: 32 43 08 xor 0x8(%rbx),%al

Ennél nagyon érdekes sorrendet választott a compiler, nem tudom miért.

XOR eredmény == serial[10]
    1098:	38 43 0b             	cmp    %al,0xb(%rbx)
109b: 75 10 jne 10ad <main+0x5d>
Printf (puts) hívások
    109d:	48 8d 3d 94 0f 00 00 	lea    0xf94(%rip),%rdi        # 2038 <_IO_stdin_used+0x38>
10a4: e8 87 ff ff ff call 1030 <puts@plt>
10a9: 31 c0 xor %eax,%eax
10ab: 5b pop %rbx
10ac: c3 ret
10ad: 48 8d 3d 67 0f 00 00 lea 0xf67(%rip),%rdi # 201b <_IO_stdin_used+0x1b>
10b4: e8 77 ff ff ff call 1030 <puts@plt>
10b9: b8 01 00 00 00 mov $0x1,%eax
10be: 5b pop %rbx
10bf: c3 ret
10c0: 48 8d 3d 3d 0f 00 00 lea 0xf3d(%rip),%rdi # 2004 <_IO_stdin_used+0x4>
10c7: e8 64 ff ff ff call 1030 <puts@plt>
10cc: eb eb jmp 10b9 <main+0x69>
10ce: 66 90 xchg %ax,%ax

Megoldás menete

Végignézzük az utasítások, információt gyűjtünk, hogy milyen serialt fogad el a program. A fent mutatott összes feltételnek teljesülnie kell. A következő lépés egy egyszerű kulcsgenerátor program készítése. Erre készítsünk Python programot.

desired = input("Adj meg 6 karaktert: ")
res = 0
for c in desired:
res ^= ord(c)
print("Kulcsod: KSZK-", desired, chr(res), sep='')

Forráskód

#include <stdio.h>

int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: serial <SERIAL>\n");
return 1;
}

const char *serial = argv[1];
if (strlen(serial) != 12) {
printf("Invalid serial number\n");
return 1;
}
const char prefix[] = "KSZK";
for (int i=0; i < 4; i++) {
if (serial[i] != prefix[i]) {
printf("Invalid serial number\n");
return 1;
}
}

if (serial[4] != '-') {
printf("Invalid serial number\n");
return 1;
}

char res = 0;
for (int i=5; i < 11; i++) {
res ^= serial[i];
}

if (res != serial[11]) {
printf("Invalid serial number\n");
return 1;
}

printf("Serial number is valid\nGreat job!\n");
}

Hagyma reversing

Megszokott módon objdump.

$ objdump -d hagyma Itt nincs main, mert ez a program nem használja a libc-t, ez bare metal. Van _start, itt kezd a program. A program viselkedését GDB-vel fogjuk vizsgálni, mert az objdump kimenetében láttuk, hogy vannak hibás bájtok, és ettő megijedtünk. De előtte,

Headerek vizsgálata

$ objdump -x hagyma

<snip>
Sections:
Idx Name Size VMA LMA File off Algn
0 .note.gnu.property 00000030 0000000000400190 0000000000400190 00000190 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
1 .note.gnu.build-id 00000024 00000000004001c0 00000000004001c0 000001c0 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .text 00000067 0000000000401000 0000000000401000 00001000 2**0
CONTENTS, ALLOC, LOAD, READONLY, CODE
3 code 000000f5 0000000000402000 0000000000402000 00002000 2**0
CONTENTS, ALLOC, LOAD, CODE
<snip>

Érdekes, hogy van egy nem standard section, a code. Továbbá ez a section írható, olvasható, és futtatható. Ez lehet egy önmódosító program, ezért nézzüg GDB-vel.

GDB TUI

$ gdb hagyma
(gdb) b _start
(gdb) r

Lépjünk át TUI módba a Ctrl + x és Ctrl + a gombokkal. Ezután váltsunk asm layoutra, a layout asm paranccsal. Fent látni az assembly utasításokat. si vel lépjünk egyesével.

Megoldás

A program szintén vár egy paramétert. Futtassuk azzal r asdasd.

  40202a:	8a 47 0f             	mov    0xf(%rdi),%al
40202d: 3c 46 cmp $0x46,%al
40202f: e8 e7 ff ff ff call 40201b <check>

Láthatjuk, hogy a 15. karakterét hasonlítja a megadott paraméternek a 0x46 (F)-el, és check-et hív.

A check függvény:

000000000040201b <check>:
40201b: 0f 85 0a f0 ff ff jne 40102b <fail>
402021: e8 da ff ff ff call 402000 <decode>
402026: 48 31 c0 xor %rax,%rax
402029: c3 ret

Ha az utolsó utasítás nem 0-t adott, akkor fail, amúgy decode.

A decode függvény:

0000000000402000 <decode>:
402000: 49 8d 82 2a 20 40 00 lea 0x40202a(%r10),%rax
402007: 48 8b 18 mov (%rax),%rbx
40200a: 48 31 58 10 xor %rbx,0x10(%rax)
40200e: 48 8b 58 08 mov 0x8(%rax),%rbx
402012: 48 31 58 18 xor %rbx,0x18(%rax)
402016: 49 83 c2 10 add $0x10,%r10
40201a: c3 ret

_l0-tól %r10 bájtra lévő címet veszi alapcímnek. Onnan beolvas 16 bájtot, és az azt követő 16 bájtot XOR-ozza vele, és a 2. 16 bájtot felülírja vele, ezzel lefejtve a hagyma egy rétegét.

Végigmegyünk a rétegeken, és GDB-ben figyeljük, hogy mi kell hozzá.

Rétegek

    1. karakter legyen F
  1. Első és a negyedik karakter legyen ugyan az, és legyen K
  2. Harmadikból a negyedik karaktert kivonva az eredmény legyen 7
  3. Az 5., 10., 13., karakterek a _ karakterrel XORozva adjanak 0-t
  4. A [6,9] karakterek legyenek a "S3CU"
  5. A 11. karakter legyen <
  6. A 14. karakter AND-elve a 15. karakterrel, majd XOR-ozva 0x40-el adjon 0-t
  7. A 12. karakter 2-es komplemensének alsó bájtja legyen 0xcd

Ebből a következő kulcs állítható össze: K##K_S3CU_<#_##F, ahol a # karakterek bizonytalanságot jelentenek.

A 8. szabájt kihasználva írhatunk egy gyors C kódot, ami megmondja a 12. karaktert:

#include <stdio.h>

int main(void) {
for (unsigned char i=0; i<255; i++) {
if((unsigned char)(0-i) == 0xcd)
printf("%02X %c: %02X\n", i, i, (unsigned char)(0-i));
}
}

Ez a 3. K##K_S3CU_<3_##F, még egy karaktert tudunk. Hasonlóan a 7. szabályra:

#include <stdio.h>

int main(void) {
for (unsigned char i=32; i<127; i++)
for (unsigned char j=32; j<127; j++)
if (((i & j) ^ 0x40) == 0)
printf("%c %c\n", i, j);
}

Válasszunk egy valid megoldást, mondjuk a CT karaktereket. K##K_S3CU_<3_CTF, még két karaktert tudunk.

Az maradék 2 karakterről tudjuk, hogy az kódjaik különbsége 7. Válasszuk bármelyik 2 ilyen karaktert, mondjuk az S-t és a Z-t.

KSZK_S3CU_<3_CTF, tudjuk az összes karaktert.