안티 리버싱

리버싱 2022. 10. 19. 09:39

앞서 안티 디버깅에 대한 간단한 예제를 살펴보았다.

2020/08/04 - [SECURITY/REVERSING] - 안티 리버싱 :: 03 - 안티 디버깅 예제

 

 

이제는 안티 디버깅에 대한 이론적인 것들을 간단하게 개념적으로 정리해볼 것이다.

 

대다수 안티 디버깅 기법은 FS:[0x30] 에 접근해 윈도우 API 를 이용하거나, 

디버깅하면 그냥 실행했을 때보다 실행 시간이 길어짐을 이용하여 시간을 검사하는 기법을 자주 사용한다.

 

 

 

 

------------------- 목차 -------------------

 

1. Windows API 

1.1 IsDebuggerPresent
1.2 CheckRemoteDebuggerPresent

1.3 NTQueryInformationProcess

1.4 OutputDebugString

1.5 FindWindow

 

2. structure (구조체) 를 수동으로 검사

2.1 BeingDebugged 플래그

 

3. 디버거로 실행할 때 프로세스에 미치는 변화를 감지

3.1 INT

3.2 Checksums

3.3 시간 체크

 

4. TLS Callback

 

-------------------------------------------- 

 

 

 

 

1. Windows API 

 

 

1.1 IsDebuggerPresent

https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-isdebuggerpresent

 

앞선 예제에서 살펴본, 가장 간단한 디버거 탐지 윈도우 API 함수이다.

이 함수는 PEB 구조에서 IsDebugged 필드를 찾아 디버그가 동작 중인지 판단하여 동작 중이 아니라면 0을, 동작 중이라면 0이 아닌 값을 반환한다.

 

** PEB 는 FS 레지스터를 통해 접근 가능. TEB 의 0x30 이 PEB 를 가리킴

  --> FS:[0x30] 이라 되어있으면 PEB 를 가리키는 것

** TEB, PEB 란?

더보기
접기

* TEB (Therad Environment Block)

: win32 의 자료 구조로서, 현재 실행 중인 스레드에 대한 정보를 저장하고 있다.

다양한 윈도우 DLL 에 대한 컨텍스트 정보를 담고 있다.

이 요소들이 유저 모드에서 구동되므로 유저 모드에서 쓰기가 가능한 구조체가 필요했다.

그래서 이 구조체는 커널 모드에서만 쓰기가 가능한 시스템 주소 공간이 아닌 프로세스 주소 공간에 위치한다. 

 

* PEB (Process Environment Block)

 

: 윈도우 NT 에서의 데이터 구조체.

운영체제 내부에서 사용하는 구조체로, 이미지 로더, 힘 관리자, 윈도우 시스템 DLL 등 유저 모드 상에서 접근할 필요가 있는 정보를 가지고 있다.

 

(NT ZW ?? -> https://blog.naver.com/stgavriel/80044878343)

 

 

+ FS 레지스터

: 커널 모드에서는 KPCR(Kernel's Processor Control Region) 구조체를,

유저 모드에서는 TEB 구조체를 가리키고 있다.

 

따라서

FS:[0] 은 TEB 의 시작 위치, 

FS:[0x30] 은 PEB 의 시작 위치를 의미

 

실제로 함수 실행에 들어가보면 FS:[30] 으로 PEB 구조체에서 +2 를 하여 BeingDebugged 를 확인하는 것을 볼 수 있다.

 

 

 

1.2 CheckRemoteDebuggerPresent

https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-checkremotedebuggerpresent

 

PEB 구조의 IsDebugged 필드를 확인하며 IsDebuggerPresent 와 거의 동일한 기능을 한다. 

다만 자신이나 로컬 컴퓨터의 프로세스에 대해서만 검사할 수 있다.

 

파라미터로 프로세스 헨들을 받아 프로세스가 디버거 환경에서 실행 중인지를 판단한다.

 

Use the IsDebuggerPresent function to detect whether the calling process is running under the debugger.

 

 

1.3 NTQueryInformationProcess

https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess

 

첫 번쨰 파라미터로 프로세스 헨들을, 두 번째 파라미터로 얻고자 하는 프로세스 정보의 타입을 요구한다.

두 번째 파라미터인 ProcessInformationClass  ProcessDebugPort(0x7) 을 준다면 해당 프로세스가 디버깅 중인지에 대한 여부를 반환한다.

 

디버깅 중이라면 0, 아니면 디버거 포트 번호를 반환한다.

 

(NT ZW ?? -> https://blog.naver.com/stgavriel/80044878343)

 

 

1.4 OutputDebugString

https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-outputdebugstringw

 

디버거에 출력할 문자열을 전달하는 데 사용되는 함수이므로 디버거의 존재를 탐지하는 데 사용할 수 있다.

 

이 함수를 실행했는데 오류가 발생하지 않는다면 디버거로 실행중이라는 의미이다.

 

 

1.5 FindWindow

https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-findwindowa

 

특정 프로그램(디버그)가 실행되고 있는지 검색할 수 있다.

첫번째 파라미터로 검색할 프로그램(디버그) 이름을 주고, 만일 해당 프로그램이 실행 중이라면 그 헨들을, 실행하고 있지 않다면 NULL 값을 반환한다.

 

ex. FindWindow("OllyDbg", NULL)

 

 

 

2. Structure (구조체) 를 수동으로 검사

 

PEB 구조체에서 디버거가 존재하는지에 대한 정보를 제공한다.

 

운영 체제는 실행 중인 각 프로세스에 대해 윈도우 PEB 구조체를 관리한다.

환경 변수 값, 로드된 모듈 목록, 메모리 주소, 디버거 상태 등의 프로세스 환경 데이터를 포함한다.

 

프로세스 실행 중에 FS:[0x30] 으로 PEB 를 접근할 수 있다.

https://docs.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb

 

2.1 BeingDebugged 플래그

 

이 플래그는 PEB 구조체의 오프셋 2에 있다. (0x2) (향후 윈도우 버전에 따라 변경 가능)

이 플래그 값이 0 이면 디버거가 동작하고 있지 않다는 것이고, 1 이면 디버거가 동작하고 있다는 뜻이다.

 

 

그리고 다른 플래그들이 존재하지만, 향후 윈도우 버전에 의해 구조가 달라질 수 있으므로 해당 문서를 참조하길 바란다.

 

 

3. 디버거로 실행할 때 프로세스에 미치는 변화를 감지

 

3.1 INT 3

 

INT 3 는 소프트웨어 BP 를 설정하는 기본 메커니즘이다.

 

INT 3 의 OPCODE 는 0xCC 로, 이 OPCODE 를 검색하여 본래의 코드가 INT 3 로 변경되었는지 프로세스를 스캔한다.

0xCC 가 발견되면 디버거가 존재한다는 뜻이다.

 

혹은 코드에 INT 3 구문이 있을 때, 디버거로 실행하면 오류가 나지 않지만 그냥 실행하면 해당 구문은 오류를 뱉는다. 오류를 뱉는지에 대한 여부로도 디버그의 존재 여부를 파악할 수 있다.

 

 

이를 우회하기 위해서는 소프트웨어 BP 대신 하드웨어 BP 를 설정하면 된다.

 

 

 

 

3.2 Checksums

 

특정 코드 영역의 CheckSum 값을 구하고, 원본 CheckSum 과 비교하여 BP 설정 혹은 각종 코드 패치 여부를 검사할 수 있다. 

 

그 중 Hash Checking 기법은 함수 코드들의 Hash 값을 미리 구해 둔 다음, 그 값과 실행 중간에 호가인한 Hash 값이 같은지 확인하여 패치 여부를 탐지하는 것이다.

 

 

 

3.3 시간 체크

 

디버거로 프로세스를 실행하면 그냥 프로세스를 실행했을 때보다 속도가 현저히 느려진다.

이런 시간 차이를 탐지하는 몇 가지 기술이 존재한다.

 

 

3.3.1 타임 스탬프

 

특정 동작 수행 전후에 타임 스탬프를 기록하고 비교한다.

 

혹은 예외 발생 전후의 타임 스탬프를 가져와서 비교한다. 디버거가 예외를 처리하는 경우 상당한 지연이 발생하기 때문에 구분할 수 있다. (예외를 무시하거나 지나치게 하는 기능을 제공하는 디버거들도 있지만, 그럼에도 차이가 발생한다.)

 

 

3.3.2 rdtsc (OPCODE 0x0F31)

 

가장 일반적인 시간 검사 방법이다.

 

가장 최근에 시스템이 리부팅한 이후 흐른 시간 값을 저장한 64비트를 반환한다.

이 명령어를 실행한 후 두 값의 차가 특정한 정도를 넘으면 디버거가 실행 중임으로 판단할 수 있다.

 

 

3.3.3 QueryPerformanceCounter & GetTickCount

https://docs.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancecounter

https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-gettickcount

 

 

rdtsc 와 같이 시간차를 분석할 수 있는 윈도우 API 함수이다.

 

 

4. TLS Callback

 

일반적으로 대다수 디버거는 PE 헤더에서 정의한 프로그램 지입점에서 시작한다.

그런데 TLS(Thread Local Storage) Callback 은 진입점 전에 실행되는 코드로, 디버거 몰래 실행할 수 있다.

 

즉, TLS Callback 에 악성 코드 기능을 삽입한다면 디버거로는 감지하지 못한다.

 

일반적인 프로그램은 .tls 섹션을 사용하지 않기 때문에 .tls 섹션이 있는 경우 가장 먼저 안티 디버깅을 의심해야 한다.

 

출처: https://kali-km.tistory.com/entry/Anti-Debugging?category=490391

관련 내용을 더 알고 싶다면 - https://flack3r.tistory.com/entry/TLS-%EC%BD%9C%EB%B0%B1-%EC%95%88%ED%8B%B0%EB%94%94%EB%B2%84%EA%B9%85

 

 

참고 사이트들

https://blog.naver.com/sol9501/70128619541

https://kali-km.tistory.com/entry/Anti-Debugging?category=490391

'리버싱' 카테고리의 다른 글

Lab05-01.dll  (0) 2022.05.02
IAT & EAT 정리  (0) 2022.04.27
RVA to RWA 쉽게 계산하기 [펌]  (0) 2022.04.27
EAT IAT 계산  (0) 2022.04.25
C#은 SecureSting 클래스  (0) 2021.07.09
블로그 이미지

wtdsoul

,

Lab05-01.dll

리버싱 2022. 5. 2. 21:45

1. DllMain 주소

 

2. Import 윈도우를 이용해

Import -> getbyhostname 찾기

 

3. gethostbyname 함수는 몇 개인가? 18개

Import -> gethostbyname -> x

 

4. 0x10001757 에 위치한 gethostbyname 호출을 보면 어떤 DNS 요청이 이뤄지나?

g -> 0x10001757

 

 

 

add eax, 0D 통해 포인터 뒤를 알 수 있다.

 

 

 

6. 0x10001656 서브루틴 IDA Pro 지역변수 음(-) var_ 갯수? 

var 23개

7. 0x10001656 서브루틴 양(+) 오프셋 대응, arg_ ?

 

8. \cmd.exe /c 를 참조하는 코드 영역에서 무슨일?

-> 0x10095B20 주소에 더 가까움

9. 같은 영역 0x100101C8 에서 Dword_1008E5C4는 경로를 지정하는 전역변수로 보이고, 악코는

Dword_1008E5C4를 설정???

 

- 즉, sub_!0003695 함수의 호출 반환값(eax)를 Dword_1008E5C4에 저장한다.

- sub_10003695 함수는 GetVersionExa 함수를 호출하는 부분 윈도우 시스템 정보 확인

- Dword_1008E5C4 상호 참조 이용

 

10. 0x1000FF58에서 서브루틴으로 수백 라인은 문자를 비교 memcmp 비교다.

rotbotwork와 문자열 비교가 성공적으로 이뤄지면 어떤 현상??

 

- 문자열이 robotwork 와 일치하면 push [ebp+8] / 일치하지 않으면 jnz short loc_10010468 실행

 

11. PSLIST 익스포트?

Export > PSLIST

 

 

 

- sub_!00036C3 함수의 기능은 윈도우 버전을 확인, 이 함수 결과에 따라 분기문이 달라짐

- 각 분기문에서 sub_10006518, sub_1000664C를 각각 호출하는데 두 함수 모두 CreateToolhelp32Snapshot 이용

- 결론 프로세스 리스트 반환

 

 

18~19번

81. '18.

'리버싱' 카테고리의 다른 글

안티 리버싱  (0) 2022.10.19
IAT & EAT 정리  (0) 2022.04.27
RVA to RWA 쉽게 계산하기 [펌]  (0) 2022.04.27
EAT IAT 계산  (0) 2022.04.25
C#은 SecureSting 클래스  (0) 2021.07.09
블로그 이미지

wtdsoul

,

IAT & EAT 정리

리버싱 2022. 4. 27. 10:24

https://kw470.tistory.com/25

 

PE File Format(IAT)

 IAT(Import Address Table) 프로그램이 어떤 라이브러리에서 어떤 함수를 사용하고 있는지를 기술한 테이블 DLL(Dynamic Linked Library) - 프로그램에 라이브러리를 포함시키지 말고 별도의 파일(DLL)로 구성

kw470.tistory.com

 

https://yokang90.tistory.com/26

 

- IAT (Import Address Table / PE File 관련)

# 이번 포스팅 내용은 "IAT(Import Address Table)" 입니다. 이전에 "RVA to RAW" 라고, PE 파일이 메모리상에  로딩된 주소인 RVA(프로세스 가상 메모리의 절대주소)를 RAW(File Offset)으로 바꾸는 연습을 했습..

yokang90.tistory.com

 

 

'리버싱' 카테고리의 다른 글

안티 리버싱  (0) 2022.10.19
Lab05-01.dll  (0) 2022.05.02
RVA to RWA 쉽게 계산하기 [펌]  (0) 2022.04.27
EAT IAT 계산  (0) 2022.04.25
C#은 SecureSting 클래스  (0) 2021.07.09
블로그 이미지

wtdsoul

,

 

https://k4keye.tistory.com/28

 

이론[리버싱] RVA to RWA 쉽게 계산하기

이미지 출처: 리버스코어 파일에서는 Offset로 위치를 표현하고 메모리에서는 address(VA) 로 위치를 표현하는데 위의사진에서는 파일과 메모리에서의 위치가 차이가난다. 그래서 메모리에서의 값

k4keye.tistory.com

https://yokang90.tistory.com/21

 

- RVA to RAW (PE File 관련)

# 이번에는 "RVA to RAW"에 대해서 포스팅하도록 하겠습니다. "RVA to RAW"를 간단하게 설명하자면, RVA to RAW  는 PE 파일이 메모리에 로딩되었을 때 각 섹션에서 메모리의 주소(RVA)와 File Offset(RAW)을..

yokang90.tistory.com

https://maple19out.tistory.com/20

 

[리버싱 핵심원리 study] 13장 PE File Format (2)

[리버싱 핵심원리 study] 13장 PE File Format (1) PE(Portable Executable) 파일은 Windows 운영체제에서 사용되는 실행 파일 형식이다. 기존 UNIX의 COEF(Common Object File Format)을 기반으로 Microsoft에서..

maple19out.tistory.com

 

 

https://johyungen.tistory.com/436

 

RVA, VA, RAW (x64dbg, CFF Explorer)

VA Vitual Address로 메모리의 절대 주소를 의미한다. 즉 x96dbg로 프로그램을 올렸을시에 로딩된 메모리 주소를 뜻한다. VA = RVA - Imagebase Easy_Keygen.exe 프로그램을 x64dbg에 올린 후 "Input Name:" 문자..

johyungen.tistory.com

 

 

'리버싱' 카테고리의 다른 글

Lab05-01.dll  (0) 2022.05.02
IAT & EAT 정리  (0) 2022.04.27
EAT IAT 계산  (0) 2022.04.25
C#은 SecureSting 클래스  (0) 2021.07.09
.Net De'obfuscator 분석(진행 중)  (0) 2020.11.08
블로그 이미지

wtdsoul

,

EAT IAT 계산

리버싱 2022. 4. 25. 13:28

 

https://idlecomputer.tistory.com/182

 

[reversing] EAT(Export Address Table) (기타 = 세션 찾기 64bit pe viwer)

EAT (Export Address Table) Windows 운영체제에서 라이브러리(Library) 란 다른 프로그램에서 불러 쓸 수 있도록 관련 함수들을 모아놓은 파일(DLL/SYS)입니다. Win32 API 가 대표적인 Library 이며, 그 중에서..

idlecomputer.tistory.com

 

 

 

'리버싱' 카테고리의 다른 글

IAT & EAT 정리  (0) 2022.04.27
RVA to RWA 쉽게 계산하기 [펌]  (0) 2022.04.27
C#은 SecureSting 클래스  (0) 2021.07.09
.Net De'obfuscator 분석(진행 중)  (0) 2020.11.08
게임 해킹 띠오리(경로)  (0) 2020.11.02
블로그 이미지

wtdsoul

,

https://docs.microsoft.com/ko-kr/dotnet/api/system.security.securestring?view=net-5.0 

 

SecureString 클래스 (System.Security)

더 이상 필요 없게 되면 컴퓨터 메모리에서 삭제되는 텍스트처럼 기밀을 유지해야 하는 텍스트를 나타냅니다.Represents text that should be kept confidential, such as by deleting it from computer memory when no longer n

docs.microsoft.com

 

C#은 SecureSting이라는 클래스를 따로 지원해서 메모리 내에 중요정보 문자열이 안남게 관리한다고 하네요

 

 

 

 

'리버싱' 카테고리의 다른 글

RVA to RWA 쉽게 계산하기 [펌]  (0) 2022.04.27
EAT IAT 계산  (0) 2022.04.25
.Net De'obfuscator 분석(진행 중)  (0) 2020.11.08
게임 해킹 띠오리(경로)  (0) 2020.11.02
함수호출규약(Calling Convention) 간단 정리  (0) 2019.11.21
블로그 이미지

wtdsoul

,

 

two2sh.tistory.com/24

 

[Packer] ConfuserEx 분석 - 4 # by Cihuny

개요 지난 포스팅에서는 ConfuserEx이 사용하는 기법에 대해 간단히 알아보았습니다. 이번 포스팅에서는 풀옵션(Maximum Level + Packer)의 ConfuserEx로 보호된 .Net 프로그램을 언패킹하는 방법에 대해 알

two2sh.tistory.com

www.cnblogs.com/chucklu/p/11396092.html

 

[.NET] ConfuserEx脱壳工具打包 - ChuckLu - 博客园

[.NET] ConfuserEx脱壳工具打包 ConfuserEx 1.0.0脱壳步骤 Written by 今夕何夕[W.B.L.E. TeAm]1.先用UnconfuserEx把主程序Dump出

www.cnblogs.com

mindlocksite.wordpress.com/2017/02/11/easy-way-to-unpack-confuserex-1-0-max-settings/

 

Easy way to unpack Confuserex 1.0 Max Settings

Hi, I decided to write this tutorial because of a request. This time, we won’t use windbg, or any other debugger. I’ll make another tutorial for this approach. So Target : First, we hav…

mindlocksite.wordpress.com

 

www.cnblogs.com/chucklu/p/11396092.html

 

[.NET] ConfuserEx脱壳工具打包 - ChuckLu - 博客园

[.NET] ConfuserEx脱壳工具打包 ConfuserEx 1.0.0脱壳步骤 Written by 今夕何夕[W.B.L.E. TeAm]1.先用UnconfuserEx把主程序Dump出

www.cnblogs.com

 

'리버싱' 카테고리의 다른 글

RVA to RWA 쉽게 계산하기 [펌]  (0) 2022.04.27
EAT IAT 계산  (0) 2022.04.25
C#은 SecureSting 클래스  (0) 2021.07.09
게임 해킹 띠오리(경로)  (0) 2020.11.02
함수호출규약(Calling Convention) 간단 정리  (0) 2019.11.21
블로그 이미지

wtdsoul

,

theori.io/research/korean/game-hacking-1/

 

게임핵의 원리에 대해 알아보자 (1) - Wall Hack 편

FPS 게임에서 자주 발견되는 “Wall Hack” (월핵)은 벽 너머의 적을 보여주어 위치를 알 수 있도록 한다. 월핵을 구현하는 방법은 그래픽 렌더링 라이브러리마다 조금씩 다르다.

theori.io

 

 

'리버싱' 카테고리의 다른 글

RVA to RWA 쉽게 계산하기 [펌]  (0) 2022.04.27
EAT IAT 계산  (0) 2022.04.25
C#은 SecureSting 클래스  (0) 2021.07.09
.Net De'obfuscator 분석(진행 중)  (0) 2020.11.08
함수호출규약(Calling Convention) 간단 정리  (0) 2019.11.21
블로그 이미지

wtdsoul

,

http://blog.naver.com/ljsk139/30143768284

 

함수호출규약(Calling Convention) 간단 정리

__cdecl (default 설정) 인자전달방법 : 스택에 저장하여 전달인자전달순서 : 오른쪽에서 왼쪽으로스택의 ...

blog.naver.com

 

__cdecl (default 설정)

 

  • 인자전달방법 : 스택에 저장하여 전달
  • 인자전달순서 : 오른쪽에서 왼쪽으로
  • 스택의 정리 : Caller에서 정리
  • 장점 : 가변인자를 사용할 수 있다.

 

__stdcall

 

  • 인자전달방법 : 스택에 저장하여 전달
  • 인자전달순서 : 오른쪽에서 왼쪽으로
  • 스택의 정리 : Callee에서 정리
  • 장점 : __cdecl보다 코드의 길이가 짧다. (좀 더 빠르다.)

 

 

__fastcall

 

  • 인자전달방법 : 처음 두개는 ECX, EDX를 이용, 나머지는 스택에 저장하여 전달
  • 인자전달순서 : 오른쪽에서 왼쪽으로
  • 스택의 정리 : Callee에서 정리
  • 장점 : 레지스터에 인자를 저장하기 때문에 속도가 더 빠르다.

 

Calling Convention에서의 중요한 것은 스택의 정리를 Caller에서 하느냐 Callee에서 하느냐이다.

 

 

  왼쪽의 그림의 경우는 Caller에 인자를 저장하는 형태이고 오른쪽 그림의 경우는 Callee에 인자를 저장하는 형태이다.

 

왼쪽의 그림에서 Callee가 스택을 정리한다고 생각을 해보자.

 

Callee는 인자가 자신의 스택영역에 있는 것이 아니기 때문에 인자의 개수가 몇개인지 체크를 해야하고, 그 이후에 정리를 해야한다. 그렇기에 절차가 복잡해 진다. 

 

하지만 Caller가 스택을 정리한다면 Callee를 호출하는 순간 인자의 개수를 이미 알고 있기 때문에, 스택을 정리하는 작업이 복잡하지 않다. 

 

그리고 또한 Caller에서 인자의 개수를 정해서 함수를 호출하는 것이기 때문에 인자의 개수는 변할 수가 있다. 그렇기에 __cdecl에서 가변인자함수를 지원하는 것이다.

 

반대로 오른쪽의 경우는 Caller보다 Callee가 스택을 정리하는 것이 유리하다. 하지만 이 경우는 가변인자함수를 지원할 수 없다. 

 

왜냐하면 스택의 정리 코드를 작성하려면 인자의 총 크기를 미리 알아야 하는데 가변인자함수의 경우는 인자의 개수가 가변적이므로 미리 예측할 수 없기 때문이다.

 

 

 

__cdecl과 __stdcall이 속도차이가 나는 이유는 정리 코드에서의 차이 때문이다.

 

__stdcall의 경우는 Callee가 작업을 마치고 스택을 정리할 때 

ret (인자의 총 크기)

로 정리를 한다.

 

__cdecl의 경우에는 Callee가 ret을 하고 나면 Caller가

add esp, (인자의 총 크기)

로 정리를 한다.

 

 

 

[정리]

 

 

 

 

https://ezbeat.tistory.com/425

'리버싱' 카테고리의 다른 글

RVA to RWA 쉽게 계산하기 [펌]  (0) 2022.04.27
EAT IAT 계산  (0) 2022.04.25
C#은 SecureSting 클래스  (0) 2021.07.09
.Net De'obfuscator 분석(진행 중)  (0) 2020.11.08
게임 해킹 띠오리(경로)  (0) 2020.11.02
블로그 이미지

wtdsoul

,