1.8 運用C編寫ShellCode代碼

来源:https://www.cnblogs.com/LyShark/archive/2023/07/13/17549459.html
-Advertisement-
Play Games

在筆者前幾篇文章中,我們使用彙編語言並通過自定位的方法實現了一個簡單的`MessageBox`彈窗功能,但由於彙編語言過於繁瑣在編寫效率上不僅要考驗開發者的底層功底,還需要寫出更多的指令集,這對於普通人來說是非常困難的,當然除了通過彙編來實現`ShellCode`的編寫以外,使用C同樣可以實現編寫,... ...


在筆者前幾篇文章中,我們使用彙編語言並通過自定位的方法實現了一個簡單的MessageBox彈窗功能,但由於彙編語言過於繁瑣在編寫效率上不僅要考驗開發者的底層功底,還需要寫出更多的指令集,這對於普通人來說是非常困難的,當然除了通過彙編來實現ShellCode的編寫以外,使用C同樣可以實現編寫,在多數情況下讀者可以直接使用C開發,只有某些環境下對ShellCode條件有極為苛刻的長度限制時才會考慮使用彙編。

相較於彙編語言,使用C編寫Shellcode可以更加方便、高效,特別是對於需要大量計算的操作。在編寫Shellcode時,讀者需要註意以下幾點:

  • 1.使用純C語言進行編寫:在編寫Shellcode時,需要避免使用C++標準庫或其他外部依賴庫,因為這些庫往往會增加代碼的長度和複雜度。
  • 2.關閉編譯器優化:在編寫Shellcode時,需要關閉編譯器的優化功能,因為優化可能會改變代碼的執行順序,導致Shellcode無法正常工作。
  • 3.避免使用全局變數和靜態變數:在Shellcode中,全局變數和靜態變數往往會導致代碼長度過長,並且這些變數的地址也可能與Shellcode中其他代碼的地址產生衝突。
  • 4.使用裸指針和裸記憶體管理:為了減小Shellcode的長度和複雜度,需要使用裸指針和裸記憶體管理,這可以減少代碼中不必要的輔助函數調用。
  • 5.不能使用全局變數,或者用static修飾的變數,在Shellcode中要自定義入口函數,所有的字元串都要用字元串數組的方式代替。

首先讀者應自行新建一個開發項目,並將編譯模式調整為Release模式,這是因為Debug模式下的代碼在轉換成彙編後首先都是一個JMP指令,然後再跳到我們的功能代碼處,但JMP指令是地址相關的 ,所以在轉換成ShellCode時就會出錯。此外在讀者新建項目文件時請最好使用*.c結尾而不要使用*.cpp結尾。

當讀者新建文件以後,接下來請修改配置屬性,將運行庫修改為多線程(MT)並關閉安全檢查機制,如下圖所示;

接著在連接器部分,新增一個EntryMain入口點,預設的Main入口點顯然時不能使用的,如下圖所示;

與前幾章中的內容原理一致,首先我們需要得到kernel32.dll模塊的基址,這段代碼我們依然採用彙編實現,這裡需要註意__declspec(naked)的含義,該聲明是微軟編譯器提供的一個擴展,它用於指示編譯器不要為函數自動生成函數頭和尾,並將函數轉化為裸函數。這種函數不會自動生成函數首碼和尾碼的代碼,也不會創建任何本地變數或保護寄存器。

在使用__declspec(naked)聲明的函數中,開發者需要自己手動管理堆棧和調用函數的傳遞參數,然後在函數體中使用彙編指令實現所需的功能。使用__declspec(naked)聲明的函數可以有效地減小生成的代碼大小,因為不需要在函數前後添加額外的代碼,而且可以精確控制函數內部的代碼。

註意:使用__declspec(naked)聲明的函數需要開發者對彙編語言有一定的瞭解,否則容易出現錯誤。在使用時,需要非常小心,確保在函數內部正確地管理堆棧和傳遞參數,以確保函數能夠正常工作。

// ----------------------------------------------
// 32位獲取模塊基址
// ----------------------------------------------
__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
    }
}

// ----------------------------------------------
// 64位獲取模塊基址
// ----------------------------------------------
/*
.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
*/

當我們能夠拿到kernel32.dll的模塊基址時,則接下來就是通過該基址得到Kernel32的模塊導出表,並獲取該導出表內的GetProcessAddress函數的基址,至於為什麼需要這麼做,在讀者前面的文章中有詳細的分析,這裡就不再重覆敘述。

// ----------------------------------------------
// 32位取函數地址
// ----------------------------------------------
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位取函數地址
// ----------------------------------------------
/*
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;
}
*/

接著我們需要編寫主代碼邏輯,主代碼邏輯中使用GetProcAddressLoadLibraryW來載入user32.dll並調用其中的MessageBoxW函數彈出一個消息框的示例。

下麵是代碼的詳細實現流程:

  • 1.定義函數指針類型FN_GetProcAddress,用於存儲GetProcAddress函數的地址,該函數用於在載入的DLL中查找導出函數的地址。
  • 2.通過getProcAddress函數獲取kernel32.dll中的GetProcAddress函數地址,並將其轉換為FN_GetProcAddress類型的函數指針fn_GetProcAddress。
  • 3.定義函數指針類型FN_LoadLibraryW,用於存儲LoadLibraryW函數的地址,該函數用於載入指定的DLL文件。
  • 4.定義名為xyLoadLibraryW的字元數組,存儲字元串"LoadLibraryW"。
  • 5.使用fn_GetProcAddress函數指針獲取kernel32.dll中的LoadLibraryW函數的地址,並將其轉換為FN_LoadLibraryW類型的函數指針fn_LoadLibraryW。
  • 6.定義函數指針類型FN_MessageBoxW,用於存儲MessageBoxW函數的地址,該函數用於彈出消息框。
  • 7.定義名為xy_MessageBoxW的字元數組,存儲字元串"MessageBoxW"。
  • 8.定義名為xy_user32的字元數組,存儲字元串"user32.dll"。
  • 9.使用fn_LoadLibraryW函數指針載入user32.dll,並使用fn_GetProcAddress函數指針獲取其中的MessageBoxW函數地址,並將其轉換為FN_MessageBoxW類型的函數指針fn_MessageBoxW。
  • 10.定義名為MsgBox和Title的wchar_t數組,用於存儲消息框的文本內容和標題。
  • 11.使用fn_MessageBoxW函數指針彈出一個消息框,顯示MsgBox中的文本內容,並使用Title中的文本作為標題。
#include <Windows.h>

FARPROC getProcAddress(HMODULE hModuleBase);
DWORD getKernel32();
// extern "C" PVOID64  getKernel32();

// ----------------------------------------------
// 32位主函數
// ----------------------------------------------
int EntryMain()
{
    // 定義指針,用於存儲GetProcAddress入口地址
    typedef FARPROC(WINAPI* FN_GetProcAddress)(
        _In_ HMODULE hModule,
        _In_ LPCSTR lpProcName
        );

    FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)getProcAddress((HMODULE)getKernel32());

    // 定義指針,用於存儲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);

    // 定義指針,用於存儲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);

    // 此處用於設置MessageBoxW彈窗的文本內容
    wchar_t MsgBox[] = { 'H', 'e', 'l', 'l', 'o', 'L', 'y', 'S', 'h','a','r','k', 0 };
    wchar_t Title[] = { 'T', 'E', 'S', 'T', 0 };
    fn_MessageBoxW(NULL, MsgBox, Title, NULL);

    return 0;
}

// ----------------------------------------------
// 64位主函數
// ----------------------------------------------
/*
int EntryMain()
{
    typedef FARPROC(WINAPI* FN_GetProcAddress)(
        _In_ HMODULE hModule,
        _In_ LPCSTR lpProcName
        );
    FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)getProcAddress((HMODULE)getKernel32());

    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);

    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);

    wchar_t xy_Hello[] = { 'H', 'e', 'l', 'l', 'o', 'L', 'y', 'S', 'h', 'a', 'r', 'k', 0 };
    wchar_t xy_tip[] = { 'T', 'E', 'S', 'T', 0 };
    fn_MessageBoxW(NULL, xy_Hello, xy_tip, NULL);

    return 0;
}
*/

至此讀者需要手動編譯上述代碼,當編譯通過之後,請打開WinHex工具,並定位到ShellCode的開頭位置,如下圖所示則是我們需要提取的指令集;

選中這片區域,並右鍵點擊編輯按鈕,找到複製,C源碼格式,此時讀者即可得到一個完整的源代碼格式;

至此讀者只需要一個註入器用於測試代碼的完善性,此處是簡單實現的一個註入器,代碼中shellcode是我們上圖中提取出來的片段,讀者需要修改targetPid為任意一個32位應用程式,並運行註入即可;

#include <windows.h>
#include <iostream>

using namespace std;

unsigned char shellcode[450] =
{
    0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x5C, 0x53, 0x56, 0x57, 0xE8, 0xD2, 0x00, 0x00, 0x00, 0x8B, 0xC8,
    0xE8, 0xEB, 0x00, 0x00, 0x00, 0x8B, 0xF0, 0xC7, 0x45, 0xD8, 0x4C, 0x6F, 0x61, 0x64, 0x8D, 0x45,
    0xD8, 0xC7, 0x45, 0xDC, 0x4C, 0x69, 0x62, 0x72, 0x50, 0xC7, 0x45, 0xE0, 0x61, 0x72, 0x79, 0x57,
    0xC6, 0x45, 0xE4, 0x00, 0xE8, 0xA7, 0x00, 0x00, 0x00, 0x50, 0xFF, 0xD6, 0x33, 0xC9, 0xC7, 0x45,
    0xC0, 0x75, 0x00, 0x73, 0x00, 0x66, 0x89, 0x4D, 0xD4, 0x8D, 0x4D, 0xE8, 0x51, 0x8D, 0x4D, 0xC0,
    0x5D, 0xC3
};

int main(int argc, char *argv[])
{
    DWORD targetPid = 2816;

    HANDLE h_target = NULL;
    LPVOID p_base = NULL;
    HANDLE h_thread = NULL;

    h_target = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPid);
    if (h_target == NULL)
    {
        goto main_end;
    }

    p_base = VirtualAllocEx(h_target, NULL, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if (p_base == NULL)
    {

        goto main_end;
    }

    if (!WriteProcessMemory(h_target, p_base, (LPVOID)shellcode, sizeof(shellcode), NULL)) {
        goto main_end;
    }

    h_thread = CreateRemoteThread(h_target, 0, 0, (LPTHREAD_START_ROUTINE)p_base, NULL, 0, NULL);
    if (h_thread == NULL)
    {
        goto main_end;
    }

main_end:
    if (h_target)
        CloseHandle(h_target);
    if (h_thread)
        CloseHandle(h_thread);
    getchar();
    return 0;
}

如果一切順利,則讀者可看到這段ShellCode已經在特定進程內實現運行了,並輸出瞭如下圖所示的彈窗提示;

原文地址

https://www.lyshark.com/post/9cc4ded5.html

文章作者:lyshark (王瑞)
文章出處:https://www.cnblogs.com/LyShark/p/17549459.html
本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一. 介紹 sleep() : 是Thread類方法,用於使當前線程暫停執行一段時間。它可以被使用在多線程編程中,用於控制線程的執行速度或者創建時間延遲。 參數: long millis : 參數表示線程休眠的毫秒數。 例如,如果調用Thread.sleep(1000),則當前線程會休眠 1000 ...
  • Git開發手冊 git一些不常用的命令記不住,可以查看git開發手冊(https://m.php.cn/manual/view/34957.html) 1、.git/objects/pack 文件過大 今天從git拉取項目進行開發的時候克隆的很慢,還以為是網速的問題。查看了一些git命令框的拉取網路 ...
  • # 1.1 異常是什麼 python使用異常對象來表示異常狀態,併在遇到錯誤時引發異常。異常對象未被處理,程式將終止並顯示一條錯誤信息。 我們可以通過各種方法引發和捕獲錯誤,並採取對應措施。 # 1.2 將“錯誤”變成異常 自主地引發異常 ## 1.2.1 raise語句 我們通過預測異常可能發生的 ...
  • > 太陽🔆未起床,我去上集訓~ > > 坐著車🚗,到門口🚪,迷迷糊糊坐在電腦💻前~ > > 看一看題目,全都不會😭做! > > 摸電線🔋,開電閘,滋滋滋滋到閻王👼面前~ ### 閑聊一陣 emm,今天我被各種東西**虐**慘😭了! Why? 我媽讓我早上 $\texttt {6:00 ...
  • 我從大二上學期的時候學了數據結構,但是當時對數據結構的重要性並不太重視,直到在升大三的暑假,才意識到數據結構對以後學語言和找工作方面的重要性,所以亡羊補牢,為時未晚,嘗試著結合b站上王道考研數據結構課,來記錄自己對知識和代碼的理解。 數據結構學習的內容可以理解為,我們用代碼怎麼去把現實世界的問題高效 ...
  • java JDK安裝及配置 windows11 jdk-8u261-windows-x64.exe 1、點擊安裝,一路預設即可 2、設置系統環境變數 新建JAVA_HOME指明JDK安裝路徑,就是剛纔安裝時所選擇的路徑C:\Program Files\Java\jdk1.8.0_261,此路徑下包括 ...
  • 一、 描述 Spring Cloud Zuul是基於Netflix開源的Zuul項目構建而成,它作為微服務架構中的網關服務,主要用於實現動態路由、負載均衡和請求過濾等功能。 動態路由:Zuul根據預設的路由規則將進來的請求路由到相應的服務實例上。路由規則可以通過配置文件或代碼進行定義,接收到請求後, ...
  • ## 可以徹底告別手寫正則表達式了 這篇文章的目的是讓你能得到完美的正則表達式,而且還不用自己拼。 說到正則表達式,一直是令我頭疼的問題,這家伙一般時候用不到,等用到的時候發現它的規則是一點兒也記不住,`\d`表示一個數字,`\s`表示包括下劃線在內的任意單詞字元,也就是 `[A-Za-z0-9_] ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...