zoukankan      html  css  js  c++  java
  • Window中的shellcode编写框架(入门篇)

    Shellcode

    • 定义

      是一段可注入的指令(opcode),可以在被攻击的程序内运行。

    • 特点

      短小精悍,灵活多变,独立存在,无需任何文件格式的包装,因为shellcode直接操作寄存器和函数,所以opcode必须是16进制形式。因此也不能用高级语言编写shellcode。在内存中运行,无需运行在固定的宿主进程上。

    • Shellcode的利用原理

      将shellcode注入缓冲区,然后欺骗目标程序执行它。而将shellcode注入缓冲区最常用的方法是利用目标系统上的缓冲区溢出漏洞。

    Shellcode生成方法

    • 编程语言编写:汇编语言,C语言
    • shellcode生成器

    Shellcode编写原则

    •  杜绝双引号字符串的直接使用

        关闭VS自动优化没有使用到的变量

        自定义函数入口

    •   动态获取函数的地址

        GetProAddress 从dll中获取函数的地址

        参数1:调用dll的句柄,参数2:函数名

        Bug:error C2760: 语法错误: 意外的令牌“标识符”,预期的令牌为“类型说明符”

        打开项目工程-> 属性 -> c/c++ --> 语言 -> 符合模式 修改成否即可 如果这样设置将无法使用c函数。

        这个比较关键,否则使用printf就直接崩溃或者是编译报错

        最佳方案是:修改平台工具集

    • 通过获得Kernel32基址来获取GetProcAddres基址
    • 避免全局变量的使用

        因为vs会将全局变量编译在其他区段中 结果就是一个绝对的地址不能使用static定义变量(变量放到内部函数使用)

    •  确保已加载使用API的动态链接库

    编写shellcode前的准备

    • 修改程序入口点:链接器-高级。作用:去除自动生成的多余的exe代码
    • 关闭缓冲区安全检查,属性->C/C++ ->代码生成->安全检查禁用
    • 设置工程兼容window XP :代码生成 ->运行库 选择 debug MTD  release MT
    • 清除资源:链接器->调试->清单文件
    • 关闭调试功能

    如下:

    属性->常规->平台工具集 选择xp版本

     

    C/C++->代码生成->运行库选择MT

    安全检查禁用

     

    链接器->高级->入口点修改为EntryMain

     

    函数动态链接调用

      在编写shellcode时,所有用到的函数都需要动态调用,通过LoadLibrary函数加载动态链接库,GetProAddress获取动态链接库中函数的地址。所以获取到GetProAddress和LoadLibrary地址时非常重要的。通过GetProAddress和LoadLibrary,可以获取到已加载的动态链接库的地址。

    动态获取Kernel32.dll基址和GetProAddress 地址

      在正常情况下,我们不知道LoadLibraryA的地址,所以不能直接使用该函数。GetProAddress是动态链接库Kernel32.dll中的函数。每个进程内部加载都会加载Kernel32.dll,获取到加载Kernel32.dll地址就可以获取到GetProAddress函数的地址。通过汇编内嵌获取到Kernel32的基址。获取到GetProAddress地址后,就可以获取到所有需要的函数的地址。

    pE文件运行时加载的链接库

     

    代码如下:

    #include <windows.h>
    #include <stdio.h>
    
    //内嵌汇编获取Kernel32的地址
    __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
        }
    }
    
    //通过kernel32基址获取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;
    }
    
    int main()
    {
        //kernel32.dll 基址的动态获取
        HMODULE hLoadLibrary = LoadLibraryA("kernel32.dll");
        //使用内嵌汇编来获取基址
        HMODULE _hLoadLibrary = (HMODULE)getKernel32();
        //效果是一样的
        printf("LoadLibraryA动态获取的地址: 0x%x
    ", hLoadLibrary);
        printf("内嵌汇编获取的地址: 0x%x
    ", _hLoadLibrary);
    
        //声明定义,先转到到原函数定义,然后重新定义
        typedef FARPROC(WINAPI *FN_GetProcAddress)(
                _In_ HMODULE hModule,
                _In_ LPCSTR lpProcName
            );
    
        FN_GetProcAddress fn_GetProcAddress;
        fn_GetProcAddress = (FN_GetProcAddress)_GetProcAddress(_hLoadLibrary);
    
        printf("动态获取GetProcAddress地址: 0x%x
    ",fn_GetProcAddress);
        printf("内置函数获取: 0x%x
    ",GetProcAddress);
    }

    可以看到,动态获取的地址和系统函数获取的是一致的

    Shellcode框架(一)

    引用前面kernel32.dll和GetProcaddress的获取地址。对于每一个要引用的函数,通过查看定义来声明定义函数,通过GetProcaddress动态获取地址。

    具体代码如下:

    原C代码:

    #include <windows.h>
    #include <stdio.h>
    #include <windows.h>
    int EntryMain()
    {
    CreateFileA(“1.txt”, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
    MessageBoxA(NULL, “hello world”, tip, MB_OK);
    return 0;
    }

    Shellcode编写:

    #include <windows.h>
    #include <stdio.h>
    
    FARPROC  getProcAddress(HMODULE hModuleBase);
    DWORD getKernel32();
    
    int EntryMain()
    {
        //声明定义GetProcAddress
        typedef FARPROC(WINAPI *FN_GetProcAddress)(
            _In_ HMODULE hModule,
            _In_ LPCSTR lpProcName
            );
    
        //获取GetProcAddress真实地址
        FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)getProcAddress((HMODULE)getKernel32());
    
    
        //声明定义CreateFileA
        typedef HANDLE(WINAPI *FN_CreateFileA)(
                __in     LPCSTR lpFileName,
                __in     DWORD dwDesiredAccess,
                __in     DWORD dwShareMode,
                __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,
                __in     DWORD dwCreationDisposition,
                __in     DWORD dwFlagsAndAttributes,
                __in_opt HANDLE hTemplateFile
            );
        //将来的替换,地址全部动态获取
        //FN_CreateFileA fn_CreateFileA = (FN_CreateFileA)GetProcAddress(LoadLibrary("kernel32.dll"), "CreateFileA");
        //带引号的字符串打散处理
        char xyCreateFile[] = { 'C','r','e','a','t','e','F','i','l','e','A',0 };
        //动态获取CreateFile的地址
        FN_CreateFileA fn_CreateFileA = (FN_CreateFileA)fn_GetProcAddress((HMODULE)getKernel32(), xyCreateFile);
        char xyNewFile[] = { '1','.','t','x','t',''};
        fn_CreateFileA(xyNewFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
    
    
        //定义LoadLibraryA
        typedef HMODULE(WINAPI *FN_LoadLibraryA)(
                __in LPCSTR lpLibFileName
            );
        char xyLoadLibraryA[] = { 'L','o','a','d','L','i','b','r','a','r','y','A',0};
        //动态获取LoadLibraryA的地址
        FN_LoadLibraryA fn_LoadLibraryA = (FN_LoadLibraryA)fn_GetProcAddress((HMODULE)getKernel32(), xyLoadLibraryA);
    
    
        //定义MessageBoxA
        typedef int (WINAPI *FN_MessageBoxA)(
                __in_opt HWND hWnd,
                __in_opt LPCSTR lpText,
                __in_opt LPCSTR lpCaption,
                __in UINT uType);
    
                //原来的:MessageBoxA(NULL, "Hello world", "tip", MB_OK);
                char xy_user32[] = { 'u','s','e','r','3','2','.','d','l','l',0 };
                char xy_MessageBoxA[] = { 'M','e','s','s','a','g','e','B','o','x','A',0 };
                FN_MessageBoxA fn_MessageBoxA = (FN_MessageBoxA)fn_GetProcAddress(fn_LoadLibraryA(xy_user32), xy_MessageBoxA);
                char xy_Hello[] = { 'H','e','l','l','o',' ','w','o','r','l','d',0 };
                char xy_tip[] = { 't','i','p' };
                fn_MessageBoxA(NULL, xy_Hello, xy_tip, MB_OK);
                return 0;
    }
    
    //内嵌汇编获取Kernel32的地址
    __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
        }
    }
    
    //获取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;
    }

      结果:运行exe文件创建了1.txt文件,并出现弹框。

     

    函数生成的位置规律

    1. 单文件函数生成的位置规律

      规律:单文件函数的生成规律,与函数实现的先后顺序有关,而与函数的定义顺序无关。

      例如:

    #include<windows.h>
    #include<stdio.h>
    
    int FuncA(int a, int b)
    {
        puts("AAAA");
        return a + b;
    }
    
    int FuncB(int a, int b)
    {
        puts("BBB");
        return a + b;
    }
    int main()
    {
        FuncA(1, 2);
        FuncB(2, 3);
        return 0;
    }

      结果:在IDA中看到生成的exe文件中的函数顺序为FuncA,FuncB,main,与函数实现的先后顺序有关。通过函数的位置,可以得到两个函数之间的空间大小。

      2.多文件函数生成的位置规律

        规律:与包含文件的位置无关,与实际调用的顺序有关

      //A.h
    #include<stdio.h>
    void A()
    {
      puts("AAA");
    }
    
    //B.h
    #include<stdio.h>
    void B()
    {
      puts("BBB");
    }
    
    //main
    #include"A.h"
    #include"B.h"
    int main()
    
    { 
      A(); B();
    return 0; }

      结果:

       

      工程下的后缀名为vcxproj文件

       

      修改顺序

       

      可以看到生成的顺序发生了改变

      

    Shellcode框架(二)

      Shellcode代码执行过程

      ShellcodeStart-> ShellcodeEntry-> ShellcodeEnd

       工程文件:

      api.h文件:存放定义的的函数

      header.h文件:存放定义的功能函数

      0.entry.cpp文件:shellcode的入口点

      a.start.cpp文件:shellcode执行(实现逻辑功能)

      b.work.cpp文件:存放具体功能实现

      z.end.cpp文件:shellcode结束

      a.start.cpp文件和b.work.cpp文件分别管理逻辑功能和具体实现,更方便管理。

      生成的文件顺序:

       

       代码:

      api.h

    #pragma once
    
    #include<windows.h>
    
    //声明定义GetProcAddress
    typedef FARPROC(WINAPI *FN_GetProcAddress)(
        _In_ HMODULE hModule,
        _In_ LPCSTR lpProcName
        );
    //定义LoadLibraryA
    typedef HMODULE(WINAPI *FN_LoadLibraryA)(
        __in LPCSTR lpLibFileName
        );
    
    //定义MessageBoxA
    typedef int (WINAPI *FN_MessageBoxA)(
        __in_opt HWND hWnd,
        __in_opt LPCSTR lpText,
        __in_opt LPCSTR lpCaption,
        __in UINT uType);
    
    //定义CreateFileA
    typedef HANDLE(WINAPI *FN_CreateFileA)(
        __in     LPCSTR lpFileName,
        __in     DWORD dwDesiredAccess,
        __in     DWORD dwShareMode,
        __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,
        __in     DWORD dwCreationDisposition,
        __in     DWORD dwFlagsAndAttributes,
        __in_opt HANDLE hTemplateFile
        );
    
    //定义一个指针
    typedef struct _FUNCTIONS
    {
        FN_GetProcAddress fn_GetProcAddress;
        FN_LoadLibraryA fn_LoadLibraryA;
        FN_MessageBoxA fn_MessageBoxA;
        FN_CreateFileA fn_CreateFileA;
    }FUNCTIONS,*PFUNCTIONS;

      header.h

    #pragma once
    #include<windows.h>
    #include<stdio.h>
    #include"api.h"
    void ShellcodeStart();
    void ShellcodeEntry();
    void ShellcodeEnd();
    void CreateShellcode();
    void InitFunctions(PFUNCTIONS pFn);
    void CreateConfigFile(PFUNCTIONS pFn);

      

      0.entry.cpp

      

    #include "header.h"
    int EntryMain()
    {
        CreateShellcode();
    
        return 0;
    }
    
    void CreateShellcode() 
    {
    
        HMODULE hMsvcrt = LoadLibraryA("msvcrt.dll");
        //定义printf
        typedef int (__CRTDECL *FN_printf)(
            _In_z_ _Printf_format_string_ char const* const _Format,
            ...);
        FN_printf fn_printf = (FN_printf)GetProcAddress(hMsvcrt,"printf");
    
        HANDLE hBin = CreateFileA("sh.bin", GENERIC_ALL, 0, NULL, CREATE_ALWAYS, 0, NULL);
    
        if (hBin == INVALID_HANDLE_VALUE)
        {
    
            //这里不能使用printf函数,因为修改了函数入口,找不到printf的地址,所有需要动态调用printf函数
            //printf("create file error:%d
    ", GetLastError());
            fn_printf("create file error:%d
    ", GetLastError());
            return ;
        }
        DWORD dwSize = (DWORD)ShellcodeEnd - (DWORD)ShellcodeStart;
        DWORD dwWrite;
        WriteFile(hBin, ShellcodeStart, dwSize, &dwWrite, NULL);
        CloseHandle(hBin);
    
    }

     a.start.cpp文件

    #include "header.h"
    #include "api.h"
    __declspec(naked) void ShellcodeStart()
    {
        __asm 
        {
            jmp ShellcodeEntry
        }
    }
    //内嵌汇编获取Kernel32的地址
    __declspec(naked) DWORD getKernel32()
    {
        __asm
        {
            mov eax, fs:[30h];
            test eax, eax;
            js finished;
            mov eax, [eax + 0ch];
            mov eax, [eax + 14h];
            mov eax, [eax];
            mov eax, [eax]
            mov eax, [eax + 10h]
        finished:
            ret
        }
    }
    
    //获取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;
    }
    //动态调用地址
    void InitFunctions(PFUNCTIONS pFn) 
    {
        //获取GetProcAddress真实地址
        pFn->fn_GetProcAddress = (FN_GetProcAddress)getProcAddress((HMODULE)getKernel32());
        //动态获取LoadLibraryA的地址
        char xyLoadLibraryA[] = { 'L','o','a','d','L','i','b','r','a','r','y','A',0 };
        pFn->fn_LoadLibraryA = (FN_LoadLibraryA)pFn->fn_GetProcAddress((HMODULE)getKernel32(), xyLoadLibraryA);
        
        //动态获取MessageBoxA的地址
        char xy_user32[] = { 'u','s','e','r','3','2','.','d','l','l',0 };
        char xy_MessageBoxA[] = { 'M','e','s','s','a','g','e','B','o','x','A',0 };
        pFn->fn_MessageBoxA = (FN_MessageBoxA)pFn->fn_GetProcAddress(pFn->fn_LoadLibraryA(xy_user32), xy_MessageBoxA);
    
        //动态获取CreateFile的地址
        char xyCreateFile[] = { 'C','r','e','a','t','F','i','l','e','A',0 };
        pFn->fn_CreateFileA = (FN_CreateFileA)pFn->fn_GetProcAddress((HMODULE)getKernel32(), xyCreateFile);
    
    }
    //入口点
    void ShellcodeEntry()
    {
        char xy_Hello[] = { 'H','e','l','l','o',' ','w','o','r','l','d',0 };
        char xy_tip[] = { 't','i','p' };
        FUNCTIONS fn;
        InitFunctions(&fn);
        CreateConfigFile(&fn);
    }

      b.work.cpp

    #include"api.h"
    #include<stdio.h>
    //存放功能
    void CreateConfigFile(PFUNCTIONS pFn)
    {
        char xyNewFile[] = { '1','.','t','x','t','' };
        pFn->fn_CreateFileA(xyNewFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
    }

      z.end.cpp

    #include "header.h"
    void ShellcodeEnd()
    {
    
    }

    Shellcode加载器

    用来运行提取出来的shellcode代码,保存在sh.bin文件,将sb.bin文件拖入加载器,即可执行。

    #include<stdio.h>
    #include<windows.h>
    int main(int argc,char* argv[])
    {
        //打开文件
        HANDLE hFile = CreateFileA(argv[1], GENERIC_READ, 0, NULL, OPEN_ALWAYS, 0, NULL);
        if (hFile == INVALID_HANDLE_VALUE)
        {
            printf("Oen file error:%d
    ", GetLastError);
            return -1;
        }
        DWORD dwSize;
        dwSize = GetFileSize(hFile, NULL);
    
        LPVOID lpAddress = VirtualAlloc(NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        //内存分配是否成功
        if (lpAddress ==NULL)
        {
            printf("VirtualAlloc error:%d
    ", GetLastError);
            CloseHandle(hFile);
            return -1;
        }
    
        DWORD dwRead;
        ReadFile(hFile,lpAddress,dwSize,&dwRead, 0);
    
        //内嵌汇编
        __asm 
        {
            call lpAddress
        }
        _flushall();
        system("pause");
        return 0;
    }
  • 相关阅读:
    asp.net mvc中ViewData、ViewBag和TempData的详解
    在asp.net WebForms中使用路由Route
    Sql Server批量删除指定表
    MongoDB安装并设置为windows服务以使其开机自启
    NPOI操作excel之写入数据到excel表
    NPOI操作excel之读取excel数据
    SQL Server 定时自动备份数据库
    如何用按钮的click事件去触发a标签的click事件
    C# Asp.net Quartz.NET作业调度之创建、安装、卸载、调试windows服务的简单事例
    c#中浅拷贝和深拷贝的理解
  • 原文地址:https://www.cnblogs.com/thresh/p/12609659.html
Copyright © 2011-2022 走看看