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
-
- karakter legyen F
- Első és a negyedik karakter legyen ugyan az, és legyen K
- Harmadikból a negyedik karaktert kivonva az eredmény legyen 7
- Az 5., 10., 13., karakterek a
_
karakterrel XORozva adjanak 0-t - A [6,9] karakterek legyenek a "S3CU"
- A 11. karakter legyen
<
- A 14. karakter AND-elve a 15. karakterrel, majd XOR-ozva
0x40
-el adjon 0-t - 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.