constWORLDant

NOX CTF att3nti0n[REV] 본문

0x07 CTF/[라이트업 보고 푼 것]

NOX CTF att3nti0n[REV]

data type ConS_tanT 2018.09.09 20:37

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:enco .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   ebp
1:  89 e5                   mov    ebp,esp
3:  60                      pusha
4:  31 c0                   xor    eax,eax
6:  be 44 33 22 11          mov    esi,0x11223344
b:  bf dd cc bb aa          mov    edi,0xaabbccdd
10: ac                      lods   al,BYTE PTR ds:[esi]
11: 84 c0                   test   al,al
13: 74 05                   je     0x1a
15: 34 17                   xor    al,0x17
17: aa                      stos   BYTE PTR es:[edi],al
18: eb f6                   jmp    0x10
1a: 61                      popa
1b: 89 ec                   mov    esp,ebp
1d: 5d                      pop    ebp

1e: 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!}


실제 프로그램에서는 인증 되지 않는구나... 




'0x07 CTF > [라이트업 보고 푼 것]' 카테고리의 다른 글

NOX CTF att3nti0n[REV]  (2) 2018.09.09
defcon2015 r0pbaby 풀고 알게 된 것들  (0) 2018.04.08
DEPCAMP r100  (0) 2018.04.02
2 Comments
  • 프로필사진 Rap1er 2018.09.17 12:26 신고 안녕하세요, 혹시 CTF 하실 때 매번 참여한 후 writeup 작성하시는 건가요?
    아니면 지난 문제 구해서 하시는건가요?
    저도 CTF 풀고싶은데 서버가 막히기도 하고 소스코드도 못구해서 문제를 풀고싶은데 못풀고 있거든요..
    혹시 알려주실수 있을까요?
  • 프로필사진 data type ConS_tanT 2018.09.19 15:27 신고 안녕하세요. 해당 CTF 하는 날 풀고 ctf 끝나고 라이트업을 올릴 때도 있고, 상황이 여의치 않을때는 시작할 때 왕창 다운로드 받아놓고 생각날 때 풀 때도 있습니다. 대부분의 바이너리는 ctftime.org에 들어가면 다 있습니다. 풀다가 정 모르겠는것은 다른 사람의 풀이를 보고 제 것으로 만드려고 합니다.
댓글쓰기 폼