NOX CTF 라이트업을 보니까 내가 접근 하지 못한 부분이 존재하였다.
그것은 바로 "VirtualAlloc부터 존재하는 루틴에 대한 미 이해 "
좀 더 꼼꼼히 코드를 봐야겠다.
그럼 그대로 따라하면서 연습해보자.
먼저 main 부분부터 살펴보도록 하자.
프로그램의 인자 값이 키 값이 되는 형태다.
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
__main();
if ( argc == 2 )
{
if ( (unsigned __int8)check(argv[1]) == 1 )
fwrite("Correct! :)\n", 1u, 0xCu, (FILE *)__iob[0]._ptr);
else
fwrite("Not correct password! :(\n", 1u, 0x19u, &__iob[2]);
getchar();
result = 0;
}
else
{
fwrite("Usage: F_ckIt.exe <Decrypted key>\n", 1u, 0x22u, &__iob[2]);
result = 1;
}
return result; }
|
check 함수의 결과가 1이 되어야 정확한 플래그이기 때문에 check 함수를 살펴보아야 한다.
핵심부분은 빨간색 표시를 해두었다.
signed int __cdecl check(_BYTE *a1)
{
__int16 v1; // ax
signed int result; // eax
int v3; // edx
__int16 v4; // ax
unsigned __int8 v5; // [esp+1Fh] [ebp-9h]
_BYTE *v6; // [esp+30h] [ebp+8h]
if ( *a1 )
{
++i;
v6 = a1 + 1;
v5 = check(v6); // v6는 아직 모름
if ( v5 == 2 )
{
result = 2;
}
else if ( v5 && i > 0 )
{
if ( *v6 )
{
v3 = (unsigned __int8)(*v6 ^ key[i % 4]);
v4 = i--;
result = v3 == enc[v4];
}
else
{
result = 1;
}
}
else
{
result = v5;
}
}
else
{
v1 = i--;
if ( v1 == 46 )
result = 1;
else
result = 2;
}
return result; }
|
key는 다음과 같다.

char key[4] = "\x13\x37\x73\x31"
enc는 다음과 같다.
.rdata:00405064 unk_405064 db 7Dh ; } ; DATA XREF: .data:enc↑o
.rdata:00405065 db 58h ; X
.rdata:00405066 db 0Bh
.rdata:00405067 db 65h ; e
.rdata:00405068 db 55h ; U
.rdata:00405069 db 4Ch ; L
.rdata:0040506A db 35h ; 5
.rdata:0040506B db 50h ; P
.rdata:0040506C db 78h ; x
.rdata:0040506D db 52h ; R
.rdata:0040506E db 53h ; S
.rdata:0040506F db 41h ; A
.rdata:00405070 db 72h ; r
.rdata:00405071 db 44h ; D
.rdata:00405072 db 0
.rdata:00405073 db 46h ; F
.rdata:00405074 db 7Ch ; |
.rdata:00405075 db 45h ; E
.rdata:00405076 db 17h
.rdata:00405077 db 1Fh
.rdata:00405078 db 3Dh ; =
.rdata:00405079 db 17h
.rdata:0040507A db 35h ; 5
.rdata:0040507B db 58h ; X
.rdata:0040507C db 7Dh ; }
.rdata:0040507D db 53h ; S
.rdata:0040507E db 53h ; S
.rdata:0040507F db 42h ; B
.rdata:00405080 db 7Ch ; |
.rdata:00405081 db 5Ah ; Z
.rdata:00405082 db 16h
.rdata:00405083 db 45h ; E
.rdata:00405084 db 7Bh ; {
.rdata:00405085 db 5Eh ; ^
.rdata:00405086 db 1Dh
.rdata:00405087 db 56h ; V
.rdata:00405088 db 33h ; 3
.rdata:00405089 db 52h ; R
.rdata:0040508A db 1Fh
.rdata:0040508B db 42h ; B
.rdata:0040508C db 76h ; v
.rdata:0040508D db 17h
.rdata:0040508E db 1Ah
.rdata:0040508F db 5Fh ; _
.rdata:00405090 db 60h ; `
.rdata:00405091 db 5Eh ; ^
.rdata:00405092 db 17h
.rdata:00405093 db 54h ; T
.rdata:00405094 db 33h ; 3
.rdata:00405095 db 43h ; C
.rdata:00405096 db 1Bh
.rdata:00405097 db 54h ; T
.rdata:00405098 db 33h ; 3
.rdata:00405099 db 55h ; U
.rdata:0040509A db 1Ah
.rdata:0040509B db 5Fh ; _
.rdata:0040509C db 72h ; r
.rdata:0040509D db 45h ; E
.rdata:0040509E db 0Ah .rdata:0040509F db 4Ch
|
너무 길기 때문에 HxD로 복사해왔다.
7D 58 0B 65 55 4C 35 50 78 52 53 41 72 44 00 46 7C 45 17 1F 3D 17 35 58 7D 53 53 42 7C 5A 16 45 7B 5E 1D 56 33 52 1F 42 76 17 1A 5F 60 5E 17 54 33 43 1B 54 33 55 1A 5F 72 45 0A 4C
key값과 enc를 xor 해보자.
단, key의 조건은 i%4가 있다는 것을 잊지 말자.
#/usr/bin/env python
keys = [0x13,0x37,0x73,0x31]
enc = [0x7D,0x58,0x0B,0x65,0x55,0x4C,0x35,0x50,0x78,0x52,0x53,0x41,0x72,0x44,0x00,0x46,0x7C,0x45,0x17,0x1F,0x3D,0x17,0x35,0x58,0x7D,0x53,0x53,0x42,0x7C,0x5A,0x16,0x45,0x7B,0x5E,0x1D,0x56,0x33,0x52,0x1F,0x42,0x76,0x17,0x1A,0x5F,0x60,0x5E,0x17,0x54,0x33,0x43,0x1B,0x54,0x33,0x55,0x1A,0x5F,0x72,0x45,0x0A,0x4c]
password = ""
for i in xrange(len(enc)):
password += chr(enc[i] ^ keys[i%4])
print password
|
이것은 fake password 였다.
noxTF{Fake password.. Find something else inside the binary}
그럼 분석을 더 진행해야 하는데, main함수에서 연결 된 함수는 이것 뿐이다.
하지만, stripped 된 함수가 하나 더 있는 것을 확인 할 수 있다. (이 부분을 내가 보지 못하였다.)
오늘 얻게 된 팁 : .rdata에 어떤 값이 있을 때 어떤 녀석에서 참조 되는지 (xref)를 잘 보면 흐름을 파악하기 쉽다.
.rdata:004050A1 ; char[]
.rdata:004050A1 db 59h ; Y ; DATA XREF: _c2VjcmV0RnVuY3Rpb24_+231↑o
.rdata:004050A2 db 6Fh ; o
.rdata:004050A3 db 75h ; u
.rdata:004050A4 db 72h ; r
.rdata:004050A5 db 20h
.rdata:004050A6 db 66h ; f
.rdata:004050A7 db 6Ch ; l
.rdata:004050A8 db 61h ; a
.rdata:004050A9 db 67h ; g
.rdata:004050AA db 3Ah ; :
.rdata:004050AB db 20h
.rdata:004050AC db 25h ; %
.rdata:004050AD db 73h ; s
.rdata:004050AE db 0Ah
.rdata:004050AF db 0
저 이상한 함수를 따라가 보면 된다.
단순하게, 변수 선언만 되어 있는 녀석이다. 변수만 선언되어 있는 녀석은 dump code로 인식되어 디컴파일러가 건너 띄게 된다.
signed int c2VjcmV0RnVuY3Rpb24_()
{
int v2; // [esp+17h] [ebp-51h]
int v3; // [esp+1Bh] [ebp-4Dh]
int v4; // [esp+1Fh] [ebp-49h]
int v5; // [esp+23h] [ebp-45h]
int v6; // [esp+27h] [ebp-41h]
int v7; // [esp+2Bh] [ebp-3Dh]
int v8; // [esp+2Fh] [ebp-39h]
__int16 v9; // [esp+33h] [ebp-35h]
char v10; // [esp+35h] [ebp-33h]
char v11; // [esp+36h] [ebp-32h]
char v12; // [esp+37h] [ebp-31h]
char v13; // [esp+38h] [ebp-30h]
char v14; // [esp+39h] [ebp-2Fh]
char v15; // [esp+3Ah] [ebp-2Eh]
char v16; // [esp+3Bh] [ebp-2Dh]
char v17; // [esp+3Ch] [ebp-2Ch]
char v18; // [esp+3Dh] [ebp-2Bh]
char v19; // [esp+3Eh] [ebp-2Ah]
char v20; // [esp+3Fh] [ebp-29h]
char v21; // [esp+40h] [ebp-28h]
char v22; // [esp+41h] [ebp-27h]
char v23; // [esp+42h] [ebp-26h]
char v24; // [esp+43h] [ebp-25h]
char v25; // [esp+44h] [ebp-24h]
char v26; // [esp+45h] [ebp-23h]
char v27; // [esp+46h] [ebp-22h]
char v28; // [esp+47h] [ebp-21h]
char v29; // [esp+48h] [ebp-20h]
char v30; // [esp+49h] [ebp-1Fh]
char v31; // [esp+4Ah] [ebp-1Eh]
char v32; // [esp+4Bh] [ebp-1Dh]
char v33; // [esp+4Ch] [ebp-1Ch]
char v34; // [esp+4Dh] [ebp-1Bh]
char v35; // [esp+4Eh] [ebp-1Ah]
char v36; // [esp+4Fh] [ebp-19h]
char v37; // [esp+50h] [ebp-18h]
char v38; // [esp+51h] [ebp-17h]
char v39; // [esp+52h] [ebp-16h]
char v40; // [esp+53h] [ebp-15h]
char v41; // [esp+54h] [ebp-14h]
char v42; // [esp+55h] [ebp-13h]
char v43; // [esp+56h] [ebp-12h]
char v44; // [esp+57h] [ebp-11h]
LPVOID lpAddress; // [esp+58h] [ebp-10h]
__int16 v46; // [esp+5Eh] [ebp-Ah]
v11 = 121;
v12 = 120;
v13 = 111;
v14 = 67;
v15 = 81;
v16 = 108;
v17 = 95;
v18 = 38;
v19 = 115;
v20 = 115;
v21 = 36;
v22 = 121;
v23 = 72;
v24 = 81;
v25 = 66;
v26 = 89;
v27 = 116;
v28 = 32;
v29 = 38;
v30 = 39;
v31 = 121;
v32 = 34;
v33 = 72;
v34 = 35;
v35 = 101;
v36 = 36;
v37 = 72;
v38 = 84;
v39 = 39;
v40 = 39;
v41 = 38;
v42 = 54;
v43 = 106;
v44 = 0;
v2 = 1368833606;
v3 = 1976432418;
v4 = -1906174688;
v5 = -1681327154;
v6 = 1169404863;
v7 = -1687944426;
v8 = -1206730248;
v9 = 27391;
v10 = -80;
lpAddress = 0;
v46 = 0;
return 70;
}
이럴 때는 어셈블리어로 분석을 해야 한다고 한다.
ebp+var_[32:11] 까지 특정 값을 복사하고, 그 다음 byte단위로 복사를 진행한다.
사실상 주석의 의미라고 보면 된다.
.text:004015EF mov byte ptr [ebp+var_51], 46h
.text:004015F3 mov byte ptr [ebp+var_51+1], 0BEh ; var_50
.text:004015F7 mov byte ptr [ebp+var_51+2], 96h ; var_4F
.text:004015FB mov byte ptr [ebp+var_51+3], 51h ; var_4E
밑의 코드를 보면 비교하는 구문이 존재한다.
.text:004015EF mov byte ptr [ebp+var_51], 46h
.text:004015F3 mov byte ptr [ebp+var_51+1], 0BEh
.text:004015F7 mov byte ptr [ebp+var_51+2], 96h
.text:004015FB mov byte ptr [ebp+var_51+3], 51h
지금 코드에서 ebp+var_51이 0x46인데 비교하는 부분은 값이 다름을 확인하였다.
0X46 ^ 0X55 = 0X13
0X89 ^ 0XBE = 0X37
0X96 ^ 0XE5 = 0X73
0X51 ^ 0X60 = 0X31
=> 이전에 봤던 KEY 값들이 나온다... 와..
.text:00401678 movzx eax, byte ptr [ebp+var_51]
.text:0040167C cmp al, 55h
.text:0040167E jnz fail
.text:00401684 movzx eax, byte ptr [ebp+var_51+1]
.text:00401688 cmp al, 89h
.text:0040168A jnz fail
.text:00401690 movzx eax, byte ptr [ebp+var_51+2]
.text:00401694 cmp al, 0E5h
.text:00401696 jnz fail
.text:0040169C movzx eax, byte ptr [ebp+var_51+3]
.text:004016A0 cmp al, 60h
.text:004016A2 jnz fail
그 후, 평소에 접한 리버싱과 다른 부분을 보았다.
VirtualAlloc 함수를 통해 가상 메모리를 할당한다.
.text:004016A8 mov dword ptr [esp+0Ch], 40h ; flProtect
.text:004016B0 mov dword ptr [esp+8], 3000h ; flAllocationType
.text:004016B8 mov dword ptr [esp+4], 1Fh ; dwSize
.text:004016C0 mov dword ptr [esp], 0 ; lpAddress
.text:004016C7 call _VirtualAlloc@16
.text:004016CC sub esp, 10h
.text:004016CF mov [ebp+lpAddress], eax
.text:004016D2 cmp [ebp+lpAddress], 0
.text:004016D6 jz fail
0x1f : 31bytes
31바이트는 또 어디서 찾아야 하는가?
흠... 이 가상 주소 할당 하는 부분이 존재하는 함수가 단순히 선언 해둔 그곳이니까 그 곳의 값을 덤프로 사용하면 되는거구나..
ebp+var51 부분을 복호화 시켜야 한다.
.text:004015EF mov byte ptr [ebp+var_51], 70
.text:004015F3 mov byte ptr [ebp+var_51+1], 0BEh ; var_50
.text:004015F7 mov byte ptr [ebp+var_51+2], 96h ; var_4F
.text:004015FB mov byte ptr [ebp+var_51+3], 81 ; var_4E
.text:004015FF mov byte ptr [ebp+var_4D], 34
.text:00401603 mov byte ptr [ebp+var_4D+1], 247
.text:00401607 mov byte ptr [ebp+var_4D+2], 205
.text:0040160B mov byte ptr [ebp+var_4D+3], 117
.text:0040160F mov byte ptr [ebp+var_49], 32
.text:00401613 mov byte ptr [ebp+var_49+1], 15h
.text:00401617 mov byte ptr [ebp+var_49+2], 98
.text:0040161B mov byte ptr [ebp+var_49+3], 142
.text:0040161F mov byte ptr [ebp+var_45], 206
.text:00401623 mov byte ptr [ebp+var_45+1], 251
.text:00401627 mov byte ptr [ebp+var_45+2], 200
.text:0040162B mov byte ptr [ebp+var_45+3], 155
.text:0040162F mov byte ptr [ebp+var_41], 191
.text:00401633 mov byte ptr [ebp+var_41+1], 179
.text:00401637 mov byte ptr [ebp+var_41+2], 179
.text:0040163B mov byte ptr [ebp+var_41+3], 69
.text:0040163F mov byte ptr [ebp+var_3D], 22
.text:00401643 mov byte ptr [ebp+var_3D+1], 3
.text:00401647 mov byte ptr [ebp+var_3D+2], 100
.text:0040164B mov byte ptr [ebp+var_3D+3], 155
.text:0040164F mov byte ptr [ebp+var_39], 248
.text:00401653 mov byte ptr [ebp+var_39+1], 193
.text:00401657 mov byte ptr [ebp+var_39+2], 18
.text:0040165B mov byte ptr [ebp+var_39+3], 184
.text:0040165F mov byte ptr [ebp+var_35], 255
.text:00401663 mov byte ptr [ebp+var_35+1], 106
.text:00401667 mov [ebp+var_33], 176
#/usr/bin/env python
array = [70,190,150,81,34,247,205,117,32,21,98,142,206,251,200,155,191,179,179,69,22,3,100,155,248,193,18,184,255,106,176]
key = [0x13,0x37,0x73,0x31]
paasword= ""
for i in xrange(len(array)):
array[i] = '%02x' % (array[i] ^ key[i%4]) print ' '.join(array) |
c0nstant@ubuntu:~/ctf/nox/rev/attention$ python xor_key2.py
55 89 e5 60 31 c0 be 44 33 22 11 bf dd cc bb aa ac 84 c0 74 05 34 17 aa eb f6 61 89 ec 5d c3
disassembly 하면 다음과 같다.
0: 55 push ebp1: 89 e5 mov ebp,esp3: 60 pusha4: 31 c0 xor eax,eax6: be 44 33 22 11 mov esi,0x11223344b: bf dd cc bb aa mov edi,0xaabbccdd10: ac lods al,BYTE PTR ds:[esi]11: 84 c0 test al,al13: 74 05 je 0x1a15: 34 17 xor al,0x1717: aa stos BYTE PTR es:[edi],al18: eb f6 jmp 0x101a: 61 popa1b: 89 ec mov esp,ebp1d: 5d pop ebp1e: c3 ret
xor 0x17을 다시 하면 된다. 이제 이런 문제가 나오면 chr 혹은 디스어셈블리 하는 것을 습관화해두자. !! 오늘 정말 큰 거 하나 배움 ~ 땡큐 nox
flag를 구했다.
c0nstant@ubuntu:~/ctf/nox/rev/attention$ cat flag.py
#/usr/bin/env python
flag =[121,120,111,67,81,108,95,38,115,115,36,121,72,81,66,89,116,32,38,39,121,34,72,35,101,36,72,84,39,39,38,54,106]
for i in xrange(len(flag)):
flag[i] = chr(flag[i] ^ 0x17)
print ''.join(flag)
noxTF{H1dd3n_FUNc710n5_4r3_C001!}
실제 프로그램에서는 인증 되지 않는구나...