自实现函数
文件操作
删除文件
// DeleteFile
BOOL CustomDeleteFile(WCHAR* filePath) {
if (filePath == NULL) {
return FALSE;
}
HANDLE fileHandle = CreateFileW(
filePath,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_DELETE_ON_CLOSE,
NULL
);
if (fileHandle == INVALID_HANDLE_VALUE) {
return FALSE;
}
// 关闭文件句柄以触发删除操作
CloseHandle(fileHandle);
return TRUE;
}
移动文件
#include <iostream>
#include <string>
#include <Windows.h>
#include <winternl.h>
#pragma comment(lib, "ntdll.lib")
NTSTATUS(__stdcall *NtSetInformationFile)(
_In_ HANDLE FileHandle,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_In_ PVOID FileInformation,
_In_ ULONG Length,
_In_ FILE_INFORMATION_CLASS FileInformationClass
);
bool MoveFileNative(const std::wstring& srcPath, const std::wstring& destPath) {
// 打开源文件
HANDLE hFile = CreateFileW(srcPath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hFile == INVALID_HANDLE_VALUE) {
std::wcerr << L"打开源文件失败。" << std::endl;
return false;
}
// 获取NtSetInformationFile函数的地址
NtSetInformationFile = (NTSTATUS(__stdcall*)(HANDLE, PIO_STATUS_BLOCK, PVOID, ULONG, FILE_INFORMATION_CLASS))GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtSetInformationFile");
if (!NtSetInformationFile) {
std::wcerr << L"获取NtSetInformationFile函数失败。" << std::endl;
CloseHandle(hFile);
return false;
}
// 初始化FILE_RENAME_INFORMATION结构体
size_t renameInfoSize = sizeof(FILE_RENAME_INFORMATION) + destPath.size() * sizeof(WCHAR);
FILE_RENAME_INFORMATION* renameInfo = (FILE_RENAME_INFORMATION*)malloc(renameInfoSize);
if (!renameInfo) {
std::wcerr << L"分配内存失败。" << std::endl;
CloseHandle(hFile);
return false;
}
ZeroMemory(renameInfo, renameInfoSize);
renameInfo->ReplaceIfExists = FALSE;
renameInfo->RootDirectory = nullptr;
renameInfo->FileNameLength = static_cast<ULONG>(destPath.size() * sizeof(WCHAR));
memcpy(renameInfo->FileName, destPath.c_str(), destPath.size() * sizeof(WCHAR));
// 调用NtSetInformationFile执行文件移动操作
IO_STATUS_BLOCK ioStatusBlock;
NTSTATUS status = NtSetInformationFile(hFile, &ioStatusBlock, renameInfo, static_cast<ULONG>(renameInfoSize), FileRenameInformation);
// 释放内存并关闭文件句柄
free(renameInfo);
CloseHandle(hFile);
// 检查操作是否成功
if (status != STATUS_SUCCESS) {
std::wcerr << L"移动文件失败,NTSTATUS: 0x" << std::hex << status << std::endl;
return false;
}
return true;
}
CRT函数
strlen
inline int _strlen(const char *ss)
{
if (ss == 0) {
return 0;
}
int i = 0;
while (ss[i])
{
i++;
}
return i;
}
strcpy
inline void _strcpy(char *dst, const char *src)
{
int src_len = _strlen(src);
int i = 0;
for (i = 0; i < src_len; i++)
{
dst[i] = src[i];
}
dst[i] = 0;
}
strncpy
inline char* _strncpy(char* dst, char* src, size_t num)
{
int src_len = _strlen(src);
int i =0;
for (i=0;i<num;i++) {
if (i<src_len) {
dst[i] = src[i];
} else {
dst[i] = 0;
}
}
return dst;
}
strcat
inline void _strcat(char *dst, char *src1, const char *src2)
{
int src1_len = _strlen(src1);
int src2_len = _strlen(src2);
int i = 0;
for (i = 0; i < src1_len + src2_len; i++)
{
if (i < src1_len)
{
dst[i] = src1[i];
}
else
{
dst[i] = src2[i - src1_len];
}
}
dst[i] = 0;
}
strcat1
inline void _strcat1(char *src1, const char *src2)
{
int src1_len = _strlen(src1);
int src2_len = _strlen(src2);
int i = 0;
for (i = 0; i < src1_len + src2_len; i++)
{
if (i < src1_len)
{
src1[i] = src1[i];
}
else
{
src1[i] = src2[i - src1_len];
}
}
src1[i] = 0;
}
strcatc
inline void _strcatc(char *src1, const char c)
{
int src1_len = _strlen(src1);
// _strcpy(dst, s)
src1[src1_len] = c;
src1[src1_len+1] = 0;
}
strtrim
inline void _strtrim(char* src, char* dst) {
int src_len = _strlen(src);
int i=0;
int j=0;
int start_flag = 1;
int end_flag = 0;
for (i=0;i<src_len;i++) {
char current = src[i];
if (current=='\n' || current == '\r' || current == ' ') {
continue;
} else {
break;
}
}
}
memset
inline void * _memset(void * dest, char c, unsigned int len)
{
unsigned int i;
unsigned int fill;
unsigned int chunks = len / sizeof(fill);
char * char_dest = (char *)dest;
unsigned int * uint_dest = (unsigned int *)dest;
fill = (c<<24) + (c<<16) + (c<<8) + c;
for (i = len; i > chunks * sizeof(fill); i--) {
char_dest[i - 1] = c;
}
for (i = chunks; i > 0; i--) {
uint_dest[i - 1] = fill;
}
return dest;
}
memcpy
inline void * __memcpy(void * dest, void * src, unsigned int len)
{
unsigned int i;
char * char_src = (char *)src;
char * char_dest = (char *)dest;
for (i = 0; i < len; i++) {
char_dest[i] = char_src[i];
}
return dest;
}
void* MMcpy(void* dst, const void* src, size_t len)
{
char* ch_dst = (char*)dst;
char* ch_src = (char*)src;
if (NULL == ch_dst || NULL == ch_src) {
return NULL;
}
void* rest = ch_dst;
if (ch_dst <= ch_src || (char*)ch_dst >= (char*)ch_src + len) {
while (len--) {
*(char*)ch_dst = *(char*)ch_src;
ch_dst = (char*)ch_dst + 1;
ch_src = (char*)ch_src + 1;
}
}
else {
ch_src = (char*)ch_src + len - 1;
ch_dst = (char*)ch_dst + len - 1;
while (len--) {
*(char*)ch_dst = *(char*)ch_src;
ch_dst = (char*)ch_dst - 1;
ch_src = (char*)ch_src - 1;
}
}
return rest;
}
XOR
inline void xor_it(void* src, int src_len) {
char XOR_BED[] = "XXX";
// char XOR_BED[] = "WEB";
int XOR_BED_LEN = 40; // TODO
int j = 0;
for (int i = 0; i < src_len; i++) {
j = i % XOR_BED_LEN;
((unsigned char*)src)[i] = ((unsigned char*)src)[i] ^ XOR_BED[j];
}
}
trim_end
// 去除指定字符串尾部的字符
// e.g. trim_end("C:\\Program Files\\*", '*') => "C:\\Program Files\\"
inline void trim_end(char* src, char bad) {
int src_len = _strlen(src);
if (src_len == 0) {
return;
}
int i = 0;
for (i = src_len - 1; i >= 0; i--) {
if (src[i] == bad) {
continue;
}
else {
src[i + 1] = 0;
break;
}
}
}
str_contains
// 判断一个字符串中是否包含另一个字符串
inline bool str_contains(const char* src, const char* sub) {
/*
* 遍历源字符串的每个字符,当发现第一个匹配子串的字符时,记录源字符串的索引值,
* 继续遍历后续字符直到匹配完整个子串或者匹配失败,如果匹配成功,返回 true,否则继续遍历源字符串。
* 当源字符串剩余的字符数小于子串长度时,停止遍历,并返回 false。
*/
int src_len = _strlen(src);
int sub_len = _strlen(sub);
int i = 0;
int j = 0;
int i_origin = 0;
if (sub_len == 0) { return false; }
while(1){
if ( (src[i] == sub[j]) ) {
i_origin = i;
while ((i < src_len) && (j < sub_len) && (src[i] == sub[j])) {
i++;
j++;
}
if (j == sub_len) {
return true;
}
else {
i++;
j = 0;
}
}
else {
i++;
if (i + sub_len > src_len) {
break;
}
}
}
return false;
}
has_ext
// 判断文件名是否以指定的文件扩展名结尾
// e.g. has_ext("abc.dll, "dll") => true
bool has_ext(const char* filename, const char* ext) {
int name_len = _strlen(filename);
int ext_len = _strlen(ext);
if (ext_len < 1) return false;
if (name_len <= (1 + ext_len)) {
return false;
}
int i = 0;
int j = ext_len - 1;
bool res = 0;
for (i = name_len - 1; i > (name_len - 1 - ext_len); i--) {
if (filename[i] == ext[j--]) {
continue;
}
else {
return false;
}
}
return true;
}
s_gets
char* s_gets(char* st, int n)
{
char* ret_val;
char* find;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
find = strchr(st, '\n'); // 查找换行符
if (find) // 如果地址不是 NULL,
*find = '\0'; // 在此处放置一个空字符
else
while (getchar() != '\n')
continue; // 处理输入行的剩余部分
}
return ret_val;
}
PEB
Struct
#include <Windows.h>
#ifndef __NTDLL_H__
#ifndef TO_LOWERCASE
#define TO_LOWERCASE(out, c1) (out = (c1 <= 'Z' && c1 >= 'A') ? c1 = (c1 - 'A') + 'a': c1)
#endif
typedef struct _UNICODE_STRING
{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, * PUNICODE_STRING;
typedef struct _PEB_LDR_DATA
{
ULONG Length;
BOOLEAN Initialized;
HANDLE SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID EntryInProgress;
} PEB_LDR_DATA, * PPEB_LDR_DATA;
typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
void* BaseAddress;
void* EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
SHORT LoadCount;
SHORT TlsIndex;
HANDLE SectionHandle;
ULONG CheckSum;
ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
typedef struct _PEB
{
BOOLEAN InheritedAddressSpace;
BOOLEAN ReadImageFileExecOptions;
BOOLEAN BeingDebugged;
BOOLEAN SpareBool;
HANDLE Mutant;
PVOID ImageBaseAddress;
PPEB_LDR_DATA Ldr;
// [...] this is a fragment, more elements follow here
} PEB, * PPEB;
#endif //__NTDLL_H__
GetModule
inline LPVOID get_module_by_name(WCHAR* module_name)
{
PPEB peb = NULL;
#if defined(_WIN64)
peb = (PPEB)__readgsqword(0x60);
#else
peb = (PPEB)__readfsdword(0x30);
#endif
PPEB_LDR_DATA ldr = peb->Ldr;
LIST_ENTRY list = ldr->InLoadOrderModuleList;
PLDR_DATA_TABLE_ENTRY Flink = *((PLDR_DATA_TABLE_ENTRY*)(&list));
PLDR_DATA_TABLE_ENTRY curr_module = Flink;
while (curr_module != NULL && curr_module->BaseAddress != NULL) {
if (curr_module->BaseDllName.Buffer == NULL) continue;
WCHAR* curr_name = curr_module->BaseDllName.Buffer;
size_t i = 0;
for (i = 0; module_name[i] != 0 && curr_name[i] != 0; i++) {
WCHAR c1, c2;
TO_LOWERCASE(c1, module_name[i]);
TO_LOWERCASE(c2, curr_name[i]);
if (c1 != c2) break;
}
if (module_name[i] == 0 && curr_name[i] == 0) {
return curr_module->BaseAddress;
}
curr_module = (PLDR_DATA_TABLE_ENTRY)curr_module->InLoadOrderModuleList.Flink;
}
return NULL;
}
GetFunc
inline LPVOID get_func_by_name(LPVOID module, char* func_name)
{
IMAGE_DOS_HEADER* idh = (IMAGE_DOS_HEADER*)module;
if (idh->e_magic != IMAGE_DOS_SIGNATURE) {
// "MZ"
return NULL;
}
IMAGE_NT_HEADERS* nt_headers = (IMAGE_NT_HEADERS*)((BYTE*)module + idh->e_lfanew);
IMAGE_DATA_DIRECTORY* exportsDir = &(nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]);
if (exportsDir->VirtualAddress == NULL) {
return NULL;
}
DWORD expAddr = exportsDir->VirtualAddress;
IMAGE_EXPORT_DIRECTORY* exp = (IMAGE_EXPORT_DIRECTORY*)(expAddr + (ULONG_PTR)module);
SIZE_T namesCount = exp->NumberOfNames;
// EAT
DWORD funcsListRVA = exp->AddressOfFunctions;
// ENT
DWORD funcNamesListRVA = exp->AddressOfNames;
// EOT
DWORD namesOrdsListRVA = exp->AddressOfNameOrdinals;
// 名称遍历
for (SIZE_T i = 0; i < namesCount; i++) {
DWORD* nameRVA = (DWORD*)(funcNamesListRVA + (BYTE*)module + i * sizeof(DWORD));
WORD* nameIndex = (WORD*)(namesOrdsListRVA + (BYTE*)module + i * sizeof(WORD));
DWORD* funcRVA = (DWORD*)(funcsListRVA + (BYTE*)module + (*nameIndex) * sizeof(DWORD));
LPSTR curr_name = (LPSTR)(*nameRVA + (BYTE*)module);
size_t k = 0;
for (k = 0; func_name[k] != 0 && curr_name[k] != 0; k++) {
if (func_name[k] != curr_name[k]) break;
}
if (func_name[k] == 0 && curr_name[k] == 0) {
// 找到了
return (BYTE*)module + (*funcRVA);
}
}
return NULL;
}
e.g.
LPVOID kernel32 = get_module_by_name((const LPWSTR)L"kernel32.dll");
LPVOID load_library = get_func_by_name((HMODULE)kernel32, (LPSTR) "LoadLibraryA");
LPVOID get_proc = get_func_by_name((HMODULE)kernel32, (LPSTR) "GetProcAddress");
读取内存
ReadProcessMemory
#include <Windows.h>
#include <stdio.h>
using pRtlFirstEntrySList = DWORD(NTAPI*)(DWORD* pValue);
DWORD ReadMemory()
{
pRtlFirstEntrySList RtlFirstEntrySList = (pRtlFirstEntrySList)GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlFirstEntrySList");
SIZE_T writtenBytes = 0;
WriteProcessMemory(GetCurrentProcess(), (char*)RtlFirstEntrySList + 4, "\x90\x90", 2, &writtenBytes);
printf("RtlFirstEntrySList address at : %p\n", RtlFirstEntrySList);
DWORD dwDataLength = 8;
DWORD returnValue = 0;
for (DWORD i = 0; i < dwDataLength; i++)
{
returnValue = RtlFirstEntrySList((DWORD*)((BYTE*)&RtlFirstEntrySList - 8 + i));
printf("%x", (BYTE)returnValue);
}
return 0;
}
int main()
{
ReadMemory();
}
免杀常用函数
GetKernel32
HMODULE get_kernel32_base() {
_PPEB peb = 0;
#ifdef _WIN64
peb = (_PPEB)__readgsqword(0x60); // peb
#else
peb = (_PPEB)__readfsdword(0x30);
#endif
LIST_ENTRY* entry = peb->pLdr->InMemoryOrderModuleList.Flink;
while (entry) {
PLDR_DATA_TABLE_ENTRY e = (PLDR_DATA_TABLE_ENTRY)entry;
if (calc_hashW2(e->BaseDllName.pBuffer, e->BaseDllName.Length / 2) == Kernel32Lib_Hash) {
return (HMODULE)e->DllBase;
}
entry = entry->Flink;
}
return 0;
};
GetFunc_Hash
void* get_proc_address_from_hash(HMODULE module, uint32_t func_hash, _GetProcAddress get_proc_address) {
PIMAGE_DOS_HEADER dosh = cast(PIMAGE_DOS_HEADER, module);
PIMAGE_NT_HEADERS nth = cast_offset(PIMAGE_NT_HEADERS, module, dosh->e_lfanew);
PIMAGE_DATA_DIRECTORY dataDict = &nth->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
if (dataDict->VirtualAddress == 0 || dataDict->Size == 0) return 0;
PIMAGE_EXPORT_DIRECTORY exportDict = cast_offset(PIMAGE_EXPORT_DIRECTORY, module, dataDict->VirtualAddress);
if (exportDict->NumberOfNames == 0) return 0;
uint32_t* fn = cast_offset(uint32_t*, module, exportDict->AddressOfNames);
uint32_t* fa = cast_offset(uint32_t*, module, exportDict->AddressOfFunctions);
uint16_t* ord = cast_offset(uint16_t*, module, exportDict->AddressOfNameOrdinals);
for (uint32_t i = 0; i < exportDict->NumberOfNames; i++) {
char* name = cast_offset(char*, module, fn[i]);
if (calc_hash(name) != func_hash) continue;
return get_proc_address == 0 ? cast_offset(void*, module, fa[ord[i]]) : get_proc_address(module, name);
}
return 0;
}
Use
// GetKernel32
HMODULE kernel32 = get_kernel32_base();
// GetFunc_Hash kernel32函数
func.GetProcAddress = (_GetProcAddress)get_proc_address_from_hash(kernel32, GetProcAddress_Hash, 0);
func.LoadLibraryA = (_LoadLibraryA)get_proc_address_from_hash(kernel32, LoadLibraryA_Hash, func.GetProcAddress);
func.VirtualAlloc = (_VirtualAlloc)get_proc_address_from_hash(kernel32, VirtualAlloc_Hash, func.GetProcAddress);
func.VirtualFree = (_VirtualFree)get_proc_address_from_hash(kernel32, VirtualFree_Hash, func.GetProcAddress);
// 使用
func.data = (ShellCodeInfo*)((DWORD_PTR)buff_point + i);
func.add1 = (CHAR*)func.VirtualAlloc(0, func.data->addrlen1, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
func._ZeroMemory(func.add1, func.data->addrlen1);
func._MoveMemory(func.add1, (char*)(func.data) + sizeof(ShellCodeInfo), func.data->addrlen1);
// example user32函数 MessageBox
char user32[] = { 'u','s','e','r','3','2','.','d','l','l',0 };
HMODULE user32dll = func.LoadLibraryA(user32);
func.MessageBoxA = (_MessageBoxA)get_proc_address_from_hash(user32dll, MessageBoxA_Hash, func.GetProcAddress);
// GetFunc_Hash ntdll函数
char s[] = { 'n', 't', 'd', 'l', 'l', 0 };
HMODULE ntdll = func.LoadLibraryA(s);
func._ZeroMemory = (_RtlZeroMemory)get_proc_address_from_hash(ntdll, RtlZeroMemory_Hash, func.GetProcAddress);
func._MoveMemory = (_RtlMoveMemory)get_proc_address_from_hash(ntdll, RtlMoveMemory_Hash, func.GetProcAddress);
// GetFunc_Hash Ws2_32函数
char w[] = { 'W','s','2','_','3','2','.','d','l','l',0 };
HMODULE Ws2_32dll = func.LoadLibraryA(w);
func.WSAStartup = (_WSAStartup)get_proc_address_from_hash(Ws2_32dll, WSAStartup_Hash, func.GetProcAddress);
func.socket = (_socket)get_proc_address_from_hash(Ws2_32dll, socket_Hash, func.GetProcAddress);
func.getaddrinfo = (_getaddrinfo)get_proc_address_from_hash(Ws2_32dll, getaddrinfo_Hash, func.GetProcAddress);
func.freeaddrinfo = (_freeaddrinfo)get_proc_address_from_hash(Ws2_32dll, freeaddrinfo_Hash, func.GetProcAddress);
func.htons = (_htons)get_proc_address_from_hash(Ws2_32dll, htons_Hash, func.GetProcAddress);
func.connect = (_connect)get_proc_address_from_hash(Ws2_32dll, connect_Hash, func.GetProcAddress);
func.send = (_send)get_proc_address_from_hash(Ws2_32dll, send_Hash, func.GetProcAddress);
func.recv = (_recv)get_proc_address_from_hash(Ws2_32dll, recv_Hash, func.GetProcAddress);
func.closesocket = (_closesocket)get_proc_address_from_hash(Ws2_32dll, closesocket_Hash, func.GetProcAddress);
func.WSACleanup = (_WSACleanup)get_proc_address_from_hash(Ws2_32dll, WSACleanup_Hash, func.GetProcAddress);
Header.h
#include <windows.h>
#include <stdint.h>
// kernel32
#define GetProcAddress_Hash 0x1AB9B854
typedef void* (__stdcall *_GetProcAddress)(HMODULE, char *);
#define LoadLibraryA_Hash 0x7F201F78
typedef HMODULE(__stdcall *_LoadLibraryA)(LPCSTR lpLibFileName);
#define VirtualAlloc_Hash 0x5E893462
typedef LPVOID(__stdcall *_VirtualAlloc)(LPVOID lpAddress, // region to reserve or commit
SIZE_T dwSize, // size of region
DWORD flAllocationType, // type of allocation
DWORD flProtect // type of access protection
);
#define VirtualFree_Hash 0x6488073
typedef BOOL(__stdcall *_VirtualFree)(LPVOID lpAddress, // address of region
SIZE_T dwSize, // size of region
DWORD dwFreeType // operation type
);
#define lstrcmpiA_Hash 0x705CF2A5
typedef int (__stdcall *_lstrcmpiA)(
_In_ LPCSTR lpString1,
_In_ LPCSTR lpString2
);
// user32
#define MessageBoxA_Hash 0x6DBE321
typedef int(__stdcall *_MessageBoxA)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
// ntdll
#define RtlDecompressBuffer_Hash 0x4B106265
typedef NTSTATUS(__stdcall *_RtlDecompressBuffer)(
USHORT CompressionFormat,
PUCHAR UncompressedBuffer,
ULONG UncompressedBufferSize,
PUCHAR CompressedBuffer,
ULONG CompressedBufferSize,
PULONG FinalUncompressedSize
);
#define RtlGetCompressionWorkSpaceSize_Hash 0x8FC8E20
typedef NTSTATUS(__stdcall *_RtlGetCompressionWorkSpaceSize)(
USHORT CompressionFormatAndEngine,
PULONG CompressBufferWorkSpaceSize,
PULONG CompressFragmentWorkSpaceSize
);
#define RtlZeroMemory_Hash 0xDB579CB
typedef void (__stdcall *_RtlZeroMemory)(IN VOID UNALIGNED *Destination, IN SIZE_T Length
);
#define RtlCopyMemory_Hash 0x20484894
typedef void (__stdcall *_RtlCopyMemory)(IN VOID UNALIGNED *Destination,
IN CONST VOID UNALIGNED *Source, IN SIZE_T Length);
#define RtlMoveMemory_Hash 0x1518E9C0
typedef void(__stdcall *_RtlMoveMemory)(IN VOID UNALIGNED *Destination,
IN CONST VOID UNALIGNED *Source, IN SIZE_T Length);
#define Kernel32Lib_Hash 0x1cca9ce6
文档信息
- 本文作者:Nattevak
- 本文链接:https://HLuKT.github.io/2023/09/25/%E8%87%AA%E5%AE%9E%E7%8E%B0%E5%87%BD%E6%95%B0/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)