'2019/07'에 해당되는 글 4건

  1. 2019.07.01 [CodeEngn] Basic RCE L04 풀이
  2. 2019.07.01 [CodeEngn] Basic RCE L03 풀이
  3. 2019.07.01 [CodeEngn] Basic RCE L02 풀이
  4. 2019.07.01 [CodeEngn] Basic RCE L01 풀이

문제 : 이 프로그램은 디버거 프로그램을 탐지하는 기능을 갖고 있다. 디버거를 탐지하는 함수의 이름은 무엇인가

 

실행파일을 실행해본다.

디버거 모드가 아닌 일반 모드에서 실행시켰기 때문에 저렇게 정상 문자열이 반복적으로 출력된다.

그러면 Olly-Dbg로 열었을 때는?

이렇게 디버깅 당함 문자열이 반복 출력된다.

 

이전 3번 문제와 마찬가지로 함수 이름을 묻는 문제이기 때문에

우클릭 > Search for > All intermodular calls 로 함수 목록을 볼 필요가 있다.

가장 눈에 띄는 함수는 IsDebuggerPresent이다.(지금 현재 Debugger 상태인가?)

위 함수를 더블클릭해서 해당 위치로 이동한다.

 

해당 위치에 BP를 걸고 F9를 눌러서 실행시킨다.

해당 위치까지 실행되었지만, 아무것도 발생하지 않았다.

 

추측에 따르면, 아마 저 함수가 실행되고 난 후에 어떤 반환값을 반환받고,

그 다음 줄에 있는 CMP(Compare) 명령을 실행한다.

비교되는 값에 따라서 "정상" 문자열인지 "디버깅 당함" 이라는 문자열이 출력 될 것이다.

 

F8을 눌러서 Step over를 실행하자.

Step over이후, EAX에 반환값으로 00000001이 입력되었다.

하지만, 예상과 다르게 콘솔 창에 "디버깅 당함"이라는 문자열은 출력되지 않았다.

 

F8로 계속 실행시켜보면,

딱 저 부분에서 "디버깅 당함"이라는 문자열이 출력된다.

문자열 출력 함수로 보이는데, 저기 Arg1을 인자로 받아서 출력하는 것 같다.

문자열을 코드 부분에서 찾으면 의미가 없고, 좌측 하단에 Hex값을 보여주는 부분에서 찾아본다.

 

(참고) Ctrl + G 단축키는 해당 주소로 이동시켜주는 단축키이다.

Ctrl + G 에 00431024를 입력해보면,

00431024 위치에 "디버깅 당함"이라는 문자열이 저장되어 있었다.

그럼 함수 00408190은 단순히 콘솔창에 문자열을 출력시켜주는 함수임을 알 수 있다.

 

F8로 계속 진행하면,

이 부분이 반복 실행된다.

대충 살펴보면

 

1. 1초 쉰다.(1000ms sleep)

2. CALL 00408210 실행

3. IsDebuggerPresent로 디버그 모드인지 판별

4. CALL 00408210 실행

5. "정상" 혹은 "디버깅 당함" 문자열 받아옴

("디버깅 당함" 문자열은 00431024위치에 있다는 것을 확인. "정상" 문자열은 0043101C 위치에 있을 것으로 추정)

6. 00408190 함수 실행 : 문자열을 콘솔 창에 출력해주는 함수

 

여기서 내가 궁금한 부분은, 00408210 위치에 있는 함수가 어떤 일을 하는지였다.

그래서 F7(Step into)로 들어가서 살펴보았는데, 별 중요한 함수는 아닌 것으로 판단하였다.

 

또 다른 궁금점은, 디버깅 모드에서 "정상" 문자열을 출력할 수 있는가였다.

그래서 IsDebuggerPresent가 종료된 후, EAX 값을 0으로 바꿔보았다.

예상대로 "정상" 문자열을 출력하였다.

 

그럼 IsDebuggerPresent에서는 무슨 일을 할까?

위 이미지는 IsDebuggerPresent의 내부이다.

 

딱 세 줄이 있는데,

1. MOV EAX, DWORD PTR FS:[30]

2. MOVZX EAX, BYTE PTR DS:[EAX+2]

3. RETN

이다.

 

(참고)

MOVZX

1. byte나 word를 word나 double-word 목적지에 전송

2. 0비트로 목적지 피연산자의 왼쪽 비트들을 채움.

 

첫번째, FS:[30]을 보면,

FS는 세그먼트 레지스터이다.

FS:[0x00] 4 Win9x and NT 현재 Structured Exception Handling (SEH) 프레임
FS:[0x04] 4 Win9x and NT 스택 베이스 / 스택의 바닥 (높은 주소)
FS:[0x08] 4 Win9x and NT 스택 한계 / 스택의 천장 (낮은 주소)
FS:[0x0C] 4 NT SubSystemTib
FS:[0x10] 4 NT Fiber data
FS:[0x14] 4 Win9x and NT 임의 데이터 슬롯
FS:[0x18] 4 Win9x and NT Thread Environment Block
---- NT subsystem 독립적인 부분 끝 ----
FS:[0x1C] 4 NT Environment Pointer
FS:[0x20] 4 NT 프로세스 ID (몇몇 윈도우 버전에서 이 필드는 'DebugContext'로 쓰인다.)
FS:[0x24] 4 NT 현재 스레드 ID
FS:[0x28] 4 NT 활동중인 RPC 핸들
FS:[0x2C] 4 Win9x and NT thread-local storage 배열의 선형 주소
FS:[0x30] 4 NT Process Environment Block (PEB)의 선형 주소
FS:[0x34] 4 NT 마지막 오류 번호
FS:[0x38] 4 NT Count of owned critical sections
FS:[0x3C] 4 NT CSR 클라이언트 스레드의 주소
FS:[0x40] 4 NT Win32 스레드 정보
FS:[0x44] 124 NT, Wine Win32 클라이언트 정보 (NT), user32 private data (Wine), 0x60 = LastError (Win95), 0x74 = LastError (WinME)
FS:[0xC0] 4 NT Reserved for Wow64. Wow64에서 FastSysCall에 대한 포인터를 포함한다.
FS:[0xC4] 4 NT Current Locale
FS:[0xC8] 4 NT FP Software Status Register
FS:[0xCC] 216 NT, Wine Reserved for OS (NT), kernel32 private data (Wine)

herein: FS:[0x124] 4 NT Pointer to KTHREAD (ETHREAD) structure

FS:[0x1A4] 4 NT 예외 코드
FS:[0x1A8] 18 NT Activation context stack
FS:[0x1BC] 24 NT, Wine Spare bytes (NT), ntdll private data (Wine)
FS:[0x1D4] 40 NT, Wine Reserved for OS (NT), ntdll private data (Wine)
FS:[0x1FC] 1248 NT, Wine GDI TEB Batch (OS), vm86 private data (Wine)
FS:[0x6DC] 4 NT GDI Region
FS:[0x6E0] 4 NT GDI 펜
FS:[0x6E4] 4 NT GDI 브러시
FS:[0x6E8] 4 NT Real Process ID
FS:[0x6EC] 4 NT Real Thread ID
FS:[0x6F0] 4 NT GDI cached process handle
FS:[0x6F4] 4 NT GDI 클라이언트 프로세스 ID (PID)
FS:[0x6F8] 4 NT GDI 클라이언트 스레드 ID (TID)
FS:[0x6FC] 4 NT GDI thread locale information
FS:[0x700] 20 NT Reserved for user application
FS:[0x714] 1248 NT Reserved for GL
FS:[0xBF4] 4 NT 마지막 상태 값
FS:[0xBF8] 532 NT Static UNICODE_STRING buffer
FS:[0xE0C] 4 NT deallocation stack에 대한 포인터
FS:[0xE10] 256 NT TLS 슬롯, 슬롯 당 4 byte
FS:[0xF10] 8 NT TLS 링크 (LIST_ENTRY structure)
FS:[0xF18] 4 NT VDM
FS:[0xF1C] 4 NT Reserved for RPC
FS:[0xF28] 4 NT 스레드 에러 모드(RtlSetThreadErrorMode)

FS 레지스터의 구조이다.

(출처 : https://ko.wikipedia.org/wiki/Win32_%EC%8A%A4%EB%A0%88%EB%93%9C_%EC%A0%95%EB%B3%B4_%EB%B8%94%EB%A1%9D (위키피디아))

우리가 찾는[30]은 Pointer to PEB라는 것임을 알 수 있는데,

 

Pointer to PEB를 구글에 검색해보면,

프로세스 환경 블록

프로세스 환경 블록 (PEB: Process Environment Block)은 윈도우 NT에서의 데이터 구조체이다. 이것은 운영체제 내부에서 사용하는 opaque data structure이다. 마이크로소프트는 마이크로소프트 개발자 네트워크(MSDN) 라이브러리 문서에서(오직 몇 개의 필드만 문서화한) 아마 다음 버전의 구조체에서는 바뀌게 될 것이라고 언급했다. PEB는 전역 문맥, 시작 파라미터, 프로그램 이미지 로더를 위한 데이터 구조체, 프로그램 이미지 베이스 주소 그리고 동기화 객체들을 포함하는, 프로세스 전체에 적용되는 데이터 구조체를 담고 있다.

PEB 커널 모드 EPROCESS 데이터 구조체 뿐만 아니라 클라이언트/서버 런타임 하위 시스템의 주소 공간에서 관리되는 프로세스 당 데이터 구조체와 밀접하게 관련되어 있다. 그러나 PEB는 자체적으로는 (CSRSS 데이터 구조체처럼) 커널 모드 데이터 구조체가 아니다. 이것은 관련된 프로세스의 사용자 모드 주소 공간에 위치한다. 왜냐하면 NTDLL 같은 운영체제 라이브러리의 사용자 모드 코드에서 사용되기 위해 디자인되었기 때문이다.

WinDbg에서, PEB의 내용을 복사해 오는 명령어는!peb 이다. 결국, 이 정보는 EPROCESS 데이터 구조체(필드 중 하나가 PEB의 주소이다.)의 정보를 보여주는 !process 명령어에 의해서 얻어진다.

 

출처 : https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4_%ED%99%98%EA%B2%BD_%EB%B8%94%EB%A1%9D (위키피디아)

BeingDebugged 프로세스가 디버깅 당하는 중인지 마이크로소프트는 이 필드 대신 공식적인 Win32를 사용할 것을 권장한다. CheckRemoteDebuggerPresent()[2]
Ldr 로드된 모듈에 대한 정보를 제공하는 PEB_LDR_DATA 구조체를 가리키는 포인터 kernel32ntdll의 베이스 주소가 들어 있다.
ProcessParameters 로드된 모듈에 대한 정보를 제공하는 RTL_USER_PROCESS_PARAMETERS 구조체를 가리키는 포인터 RTL_USER_PROCESS_PARAMETERS 구조체는 또한 대부분이 문서화되어 있지 않으며, 여러 버전에서 사용할 수 있다는 보장이 없다.[4]
PostProcessInitRoutine DLL 초기화 이후, 메인 실행 코드가 발동되기 이전에 호출되는 콜백 함수에 대한 포인터 이 콜백 함수는 윈도우 2000에서 사용되지만, 윈도우 NT 이후 버전에서 사용될 수 있다는 보장은 없다.[2]
SessionId 프로세스를 부분으로 갖는 터미널 서비스 세션의 세션 ID NtCreateUserProcess() 시스템 호출은 커널 내부의 MmGetSessionId() 함수를 호출함으로써 초기화한다.[3]

출처 : https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4_%ED%99%98%EA%B2%BD_%EB%B8%94%EB%A1%9D (위키피디아)

 

위 PEB 구조를 통해 안티 디버깅을 할 수 있다.

 

우선적으로 FS:[30]으로 디버깅 모드인지 아닌지 확인한다(BeingDebugged)

- 0 : 디버깅 모드가 아님(False)

- 1 : 디버깅 모드임(True)

 

그리고 MOVZX EAX, BYTE PTR DS:[EAX+2] 로 해당 값을 가져온다.

여기서 EAX = 003BA000, [EAX+2] = [003BA002] = 01

즉, 01값이 리턴된다.(앞에는 0으로 채워서 00000001)

 

그러므로 IsDebuggerPresent 함수의 RETN 값으로 00000001이 되는 것이고,

이는 디버깅 모드임을 알려주는 값이라고 볼 수 있다.

'보안 & 모의해킹 > CodeEngn' 카테고리의 다른 글

[CodeEngn] Basic RCE L03 풀이  (0) 2019.07.01
[CodeEngn] Basic RCE L02 풀이  (0) 2019.07.01
[CodeEngn] Basic RCE L01 풀이  (0) 2019.07.01
Posted by NDC :

문제 : 비주얼베이직에서 스트링 비교함수 이름은?

 

실행파일을 실행해 본다.

여기에 Regcode 부분에 문자열을 아무거나 입력해본다.

이렇게 출력된다.

 

아마 프로그램에 내장된 어떤 문자열과 사용자가 입력한 문자열을 비교해서 인증하는 방식으로 추측할 수 있다.

 

이 프로그램을 STUD_PE로 열어보자.

우선, EP(Entry Point)는 ImageBase + EntryPoint(rva) = 00401168임을 알 수 있다.

UPX 패킹은 되어있지 않고,

MSVBVM50.dll 을 사용하는 것으로 보아 Visual Basic 프로그램임을 알 수 있다.

 

이제 Olly-Dbg로 열어보자.

STUD_PE에서 본 것처럼 EP는 00401168이다.

 

보통 VB 프로그램은 진짜 main 부분이 나오기 전에 쓸모없는 코드(프로그램이 필요로 하는 라이브러리 로딩 등)가 앞에 존재한다고 알고있다.

 

그러므로 우리는 문자열 비교함수의 이름을 알기위해 해당 함수를 찾아보아야 한다.

 

디버거의 Code 부분에서 우클릭 > Search for > All intermodular calls 로 들어가면

프로그램이 사용하는 함수들의 이름을 목록으로 볼 수 있다.

이 프로그램이 사용하는 전체 함수 이름이다.

 

이 목록은 Address 정렬되어있는 상태이므로, 위에서부터 차례대로 실행된다고 볼 수 있다.

 

아래로 한줄한줄 찾다보면 눈에띄는 함수가 하나 보이는데

 

MSVBVM50.__vbaStrCmp

이다.

 

왠지 이 부분에서 문자열(Str, string) 비교(Cmp, Compare)가 이루어 질 것 같다.

 

해당 함수 이름을 더블클릭하면 위치로 이동할 수 있다.

이런 화면을 볼 수 있음.

함수가 위치한 주소는 0040114A이다.

 

이제 위에서 사용자가 입력한 문자열을 입력받고, 프로그램에 적혀있는 정답 문자열을 입력받고

이 두개를 파라미터로 사용하여 0040114A로 넘겨 vbaStrCmp 함수를 실행할 것이다.

 

이 위치에서 스크롤을 올려보면

이렇게 나와있다.

우리는 0040114A 함수를 호출해주는 004028C2에 BP를 걸고 프로그램을 돌려 볼 필요가 있다.

 

BP를 걸고 F9로 프로그램을 실행시켜 보면

프로그램이 실행되고,

이 프로그램에서 Regcode에 아무 문자열이나 입력한 후에 Registreieren을 클릭하자.

난 asdasdads를 입력했다.

 

그러면 우리가 BP를 걸었던 위치까지 프로그램이 실행된다.

 

디버깅을 할때, 혹은 어셈블리를 할때 함수를 실행할려면?

 

그 함수에 들어가는 인자값을 받아야 한다.(물론 필요없는 함수도 있지만 vbaStrCmp같은 함수는 필요함.)

 

그 함수는 보통 Stack에 들어간다.

지금 함수가 실행되기 직전의 Stack을 살펴보면(우측 하단)

맨 위에 어떤 문자열이 있고, 그 다음 줄에 내가 입력한 asdasdads 문자열이 들어가 있다.

 

바로, vbaStrCmp 함수가 함수 실행에 필요한 문자열을 인자로 저장한 과정이다.

 

여기서 우리는 2G83G35Hs2 문자열과 사용자가 입력한 문자열 두개를 비교한다는 것을 알 수 있다.

 

그럼 예상 정답을 알았으니, 디버거를 끄고 프로그램을 일반적인 상태로 실행해보자.

Regcode 부분에 예상 정답을 입력한다.

 

만약 2G83G35Hs2 문자열이 정답이라면?

1. vbaStrCmp 함수가 문자열 비교함수의 이름이 맞음.

2. 위의 함수의 비교 대상이 되는 문자열(비밀번호)은 2G83G35Hs2 이다.

 

이 두개를 알 수 있다.

Registrieren 버튼을 클릭하니, 오류 메시지 대신 정답 메시지가 출력된다.

 

궁금해서 추가로 해본 것은, RCE L02처럼 Hex Editor에서 위의 문자열을 찾아낼 수 있는가였다.

해당 프로그램을 Hex Editor로 열어보면

이렇게 문자열을 찾을 수 있다.

2번 문제와 마찬가지로 정답 시 출력하는 문자열 근처에서 찾을 수 있었다.

(Danke, das Passwort ist richtig!)

 

그리고 위처럼 Hex Editor 에서도 vbaStrCmp 함수의 존재를 찾을 수 있었다.

'보안 & 모의해킹 > CodeEngn' 카테고리의 다른 글

[CodeEngn] Basic RCE L04 풀이  (0) 2019.07.01
[CodeEngn] Basic RCE L02 풀이  (0) 2019.07.01
[CodeEngn] Basic RCE L01 풀이  (0) 2019.07.01
Posted by NDC :

문제 : 패스워드로 인증하는 실행파일이 손상되어 실행이 안되는 문제가 생겼다. 패스워드가 무엇인지 분석하시오

 

실행파일을 실행해보면

라고 뜬다.

이 말은, Olly-Dbg로 하는 동적 분석이 불가능함을 알 수 있다.(당연히 STUD_PE도 안됨)

 

동적 분석이 불가능하면 정적 분석을 해봐야 하는데 가장 대표적인 방법이

Hex Editor로 열어보는 것이다.

 

이렇게 프로그램은 16진수의 코드로 이루어져 있다.

 

이 16진수 코드들을 하나씩 살펴보자.

중간에 내리다 보면 이런 부분이 있다.

이 부분에서는 어떤 것을 찾아낼 수 없을 것 같다.

 

다시 더 내려보자.

이 부분은 왠지 프로그램이 사용한 함수들의 이름이 적혀 있는 것 같다.

1. DialogBoxParamA

2. EndDialog

3. GetDlgItemTextA

4. MessageBoxA

5. SendMessageA

 

6. USER32.dll

7. ExitProcess

8. GetModuleHandleA

9. Kernel32.dll

 

일단 더 내려보자. 위의 부분에서 뭔가를 찾을 수는 없을 것 같다.

더 내리다 보면 위와 같은 부분도 있다.

ADDialog.ArturDents CrackMe#1 : 이 부분은 해당 파일의 Authur 이름과 동일하므로 패스.

Nope, try again! : 패스워드 인증 실패 시 나오는 오류 메시지인 것으로 추정.

Yeah, you did it! : 패스워드 인증 성공 시 나오는 성공 메시지인 것으로 추정.

Crackme #1 : ??? 단순히 프로그램 이름같다.

JK3FJZh : 아마 이부분이 정답 문자열인 것으로 추정.

 

 

왜 JK3FJZh을 정답 문자열로 생각했나?

1. 우선 16진수에서 저렇게 수상하게 ASCII 타입으로 출력되는게 이상함.

2. 다른 문자열과 비교해봤을 때, 가장 패스워드 적인 문자열.

3. 다른 문자열이 다들 의미가 없음.

 

실제로 마지막 부분을 보면

마지막 부분에는 의미없는 문자열로 가득 차 있다.

 

Basic RCE L02는 Hex Editor를 통해 정적 분석을 아주 조금이라도 할 수 있는지 체크하는 문제인 것 같다.

'보안 & 모의해킹 > CodeEngn' 카테고리의 다른 글

[CodeEngn] Basic RCE L04 풀이  (0) 2019.07.01
[CodeEngn] Basic RCE L03 풀이  (0) 2019.07.01
[CodeEngn] Basic RCE L01 풀이  (0) 2019.07.01
Posted by NDC :

문제 : HDD를 CD-Rom으로 인식시키기 위해서는 GetDriveTypeA의 리턴값이 무엇이 되어야 하는가

 

간단하게 C드라이브를 CD-Rom으로 인식시키는 문제이다.

 

문제만 봤을 때는, GetDriveTypeA 함수 실행 시 해당 경로의 드라이브가 C드라이브인 경우의 RETN 값과 CD-Rom  일때의 RETN 값이 다르다는 것을 추측할 수 있다.

 

문제와 함께 제시된 실행 파일을 실행시켜 본다.

여기서 확인을 누르면

CD-Rom 드라이브가 아니라고 출력한다.

 

디버거로 분석하기 전에 프로그램의 기본 정보를 알아보기 위해 STUD_PE 프로그램으로 열어보았다.

32bit 프로그램이고,

UPX 패킹이 되어있지 않음을 알 수 있다.

 

UPX 패킹이 되어있는 프로그램의 경우

저기 CODE, DATA, .idata, .reloc 부분이 전부 UPX0, UPX1로 덮혀져 있다.

UPX로 패킹되어 있는 프로그램의 섹션 예시

이제 이 프로그램을 Olly-Dbg로 열어본다.

전체 프로그램의 어셈블리 코드이다.

 

사용되는 함수는 MessageBoxA와 GetDriveTypeA 이렇게 두 종류가 있다.

 

MessageBoxA : 개발자가 출력하고자 하는 문자열을 보여주는 함수

GetDriveTypeA : 해당 드라이브의 타입에 따라 RETN 값을 보내줌.

 

우리가 봐야할 함수는 GetDriveTypeA이다.

 

대부분의 함수의 리턴 값(RETN)은 EAX 레지스터에 저장된다.

 

그러므로 GetDriveTypeA 까지 실행시키고 그 다음의 EAX 값을 보기 위해

CALL GetDriveTypeA 함수 다음 라인에 BP(BreakPoint, F2)를 걸고 실행(Run, F9)시켜 보았다.

 

현재 CALL 이후 INC 명령어를 실행시키기 직전의 상황.

 

이 상태에서 EAX 값을 본다.

 

EAX = 00000003이다.

 

어셈블리 코드를 쭉 보면, 어디서 이 00000003이 CD-Rom이 아니라고 판별하는지 알 수 있다.

 

GetDriveTypeA 함수가 끝난 후,

 

INC ESI

DEC EAX

JMP 00401021 : 의미없는 코드

INC ESI

INC ESI

DEC EAX

CMP EAX, ESI

JE 0040103D

 

요약하면, ESI를 3번 +1 하고, EAX를 2번 -1 한다.

이후에 ESI와 EAX가 같으면(JE, Jump Equal)

0040103D("Ok, I really think that your HD is a CD-ROM! :P" - 성공 메시지)로 점프

 

즉, 우리가 CD-ROM으로 인식시키기 위해서는 CMP 위치에서 EAX와 ESI을 동일한 값으로 맞춰주면 된다.

 

그럼 다시, CMP 부분에 BP를 걸고 F9를 눌러서 BP 위치까지 실행을 시킨다.

현재 BP(CMP) 대기 중인 상황.

 

여기서 Register 값을 보자.

ESI는 3이고, EAX는 1이다.

하지만 이 값들은 ESI를 +3한 값이고, EAX를 -2한 값이다.

 

EAX와 ESI가 같아야 CD-Rom으로 인식되기 때문에,

ESI와 EAX는 현재 2만큼 값이 차이나고 있으므로 EAX에 +2를 해주어야 CMP 부분에서 3으로 같아지게 된다.

 

앞에서 GetDriveTypeA의 리턴 값이 EAX=3이었기 때문에

GetDriveTypeA의 리턴 값이 EAX=5가 된다면 프로그램은 C드라이브를 CD-Rom으로 인식할 것으로 추측할 수 있다.

저 부분에 BP를 걸고 F8로 step over한 후에,

 

EAX의 값을 5로 수정해 준다.

EAX를 더블클릭 하면 수정할 수 있음.

 

추가적으로, ESI 부분이 지금 00401000으로 잡혀있는데,

이 상태에서는 CMP EAX, ESI 부분이 우리가 원하는 상태로 제대로 실행되지 않는다.

그러므로 ESI도 0으로 바꿔준다.

최종적으로 변경된 레지스터 상태.

 

이후 실행해보면

정상적으로 C드라이브를 CD-Rom으로 인식하게 된다.

'보안 & 모의해킹 > CodeEngn' 카테고리의 다른 글

[CodeEngn] Basic RCE L04 풀이  (0) 2019.07.01
[CodeEngn] Basic RCE L03 풀이  (0) 2019.07.01
[CodeEngn] Basic RCE L02 풀이  (0) 2019.07.01
Posted by NDC :