逆向工具之移动导出表

简介: 逆向工具之移动导出表

0x01 移动表目的


1、PE结构里许多表是编译器自动生成的,里面存储很多非常重要的信息,比如这次要移动的导出表记录了函数的地址,序号,函数名称,函数数量等,通俗来说,导出表相当于一张函数使用说明书,提供给函数使用。


2、在程序启动的时候,系统会根据这些表来做初始化的工作:比如将用到的DLL中的函数地址存储到IAT表中,修复IAT表。


3、很多时候,为了保护我们的程序,可以对程序的二进制代码进行加密操作,但存在的问题是:各种表的信息与客户字节的代码和数据都混在一起,如果全部进行加密,那系统在初始化程序的时候会出问题,无法加载这个程序,那加密失去意义。


0x02 导出表结构


601ae909c7cb4d4e34c9d53bef7d6c0f_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


上图中的导出表中的AddressOfFunctions、AddressOfNames、AddressOfNameOrdinals的值都是内存相对偏移

RVA(内存相对偏移),FOA(文件相对偏移)


0x03 移动导出表


bcd879d1369a00a9d477502df1ae8e01_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


1、将原来的dll加载进内存中,堆区新开辟一块内存,大小为原来dll大小与新增节的大小之和(新增节大小直接给了0x1000,可以通过计算来判断大小),将原来dll的数据全部拷贝到申请的内存中去,再新增一个节表,并返回新开辟内存的首地址。


2、复制AddressOfFunctions指向的函数地址表,需要将内存相对偏移转化为文件相对偏移,长度:4*NumberOfFunctions。


3、复制AddressOfNameOrdinals指向的函数序号表,需要将内存相对偏移转化为文件相对偏移,长度:2*AddressOfNames。


4、复制AddressOfNames指向的函数名称地址表,需要将内存相对偏移转化为文件相对偏移,长度:4*AddressOfNames。


5、复制所有的函数名,长度需要通过strlen()函数进行计算,复制时直接修复函数名称地址表中的地址。


6、复制IMAGEEXPORTDIRECTORY结构(导出表)。


7、修复IMAGEEXPORTDIRECTORY结构中的AddressOfFunctions、AddressOfNames、AddressOfNameOrdinals,需要将FOA转化为RVA,并且指向IMAGEEXPORTDIRECTORY结构的指针要进行更新,否则修改的还是原来位置的导出表。


8.修复可选PE头中的导出表目录的地址值(RVA),指向新的IMAGEEXPORTDIRECTORY。


0x04 实现函数


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#define SECTIONLENGTH 0x1000
#define EVERYSECTIONTABLELENGTH 0x28
#define SECTIONNAME ".export"
#define INPUTFILENAME "D:/dynamicdll.dll"
#define INPUTMODE "rb"
#define OUTPUTFILENAME "D:/test.dll"
#define OUTPUTMODE "wb"
/*************************************************

函数功能:读取一个文件,将文件内容写入到内存中;

函数参数:无

函数返回值:pFileBuffer(LPVOID)

**************************************************/
LPVOID FileBuffer() {
    FILE* pFile = NULL; //文件指针
    LPVOID pFileBuffer = NULL; //存放数据内存的首地址
    DWORD filesize = 0;//记录文件大小
    size_t result = 0;//记录写入的返回结果
    //打开一个文件
    pFile = fopen(INPUTFILENAME, INPUTMODE);
    if (pFile == NULL) {
        printf("打开文件失败!\n");
        return NULL;
    }
    //统计文件的大小
    fseek(pFile, 0, SEEK_END);
    filesize = ftell(pFile);
    fseek(pFile, 0, SEEK_SET);
    //申请一块内存
    pFileBuffer = malloc(filesize);
    if (pFileBuffer == NULL) {
        printf("申请内存失败!\n");
        fclose(pFile);
        return NULL;
    }
    //将文件数据写入内存
    result = fread(pFileBuffer, 1, filesize, pFile);
    if (result != filesize) {
        printf("文件写入内存失败!\n");
        fclose(pFile);
        free(pFileBuffer);
        return NULL;
    }
    fclose(pFile);
    return pFileBuffer;
}
/*************************************************


函数功能:计算一个文件大小;

函数参数:filename(文件的绝对路径,LPSTR),mode(读取文件的形式,LPSTR)

函数返回值:filesize(int)

**************************************************/
DWORD CountFileSize(LPSTR filename, LPSTR mode) {
    FILE* pFile = NULL;
    LPSTR pFileBuffer = NULL;
    DWORD filesize = 0;
    //打开文件
    pFile = fopen(filename, mode);
    if (pFile == NULL) {
        printf("打开文件失败!\n");
        return NULL;
    }
    //统计文件大小
    fseek(pFile, 0, SEEK_END);
    filesize = ftell(pFile);
    fseek(pFile, 0, SEEK_SET);
    fclose(pFile);
    return filesize;
}
/*************************************************

函数功能:计算一个filebuffer的大小;

函数参数:pFileBuffer(LPVOID)

函数返回值:filesize(int)

**************************************************/
DWORD CountFileBufferSize(LPVOID pFileBuffer) {
    PIMAGE_DOS_HEADER pDosHeader = NULL;
    PIMAGE_NT_HEADERS pNTHeader = NULL;
    PIMAGE_FILE_HEADER pPEHeader = NULL;
    PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
    PIMAGE_SECTION_HEADER pSectionHeader = NULL;
    DWORD filesize = 0;//记录文件大小
    if (pFileBuffer == NULL) {
        printf("文件写入内存失败!\n");
        return NULL;
    }
    //判读是否具有MZ标志                                                                                            
    if (*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE) {
        printf("不具有MZ标志!\n");
        free(pFileBuffer);
        return 0;
    }
    pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
    //判断是否具有PE标志                                                                                            
    if (*(PDWORD)((DWORD)pFileBuffer + pDosHeader->e_lfanew) != IMAGE_NT_SIGNATURE) {
        printf("不具有PE标志\n");
        free(pFileBuffer);
        return 0;
    }
    pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
    pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4);
    pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + 20);
    pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
    //最后一个节头地址 + 最后一节大小
    filesize = (DWORD)((pSectionHeader + pPEHeader->NumberOfSections - 1)->PointerToRawData + (pSectionHeader + pPEHeader->NumberOfSections - 1)->SizeOfRawData);
    return filesize;
}
/*************************************************

函数功能:PE文件尾部新增一个节;

函数参数:pFileBuffer(LPVOID)

函数返回值:pNewFileBuffer(LPVOID)

**************************************************/
LPVOID AddLastSection(LPVOID pFileBuffer) {
    LPVOID pNewFileBuffer = NULL;
    PIMAGE_DOS_HEADER pDosHeader = NULL;
    PIMAGE_NT_HEADERS pNTHeader = NULL;
    PIMAGE_FILE_HEADER pPEHeader = NULL;
    PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
    PIMAGE_SECTION_HEADER pSectionHeader = NULL;
    if (pFileBuffer == NULL) {
        printf("文件写入内存失败!\n");
        return NULL;
    }
    pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
    pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
    pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4);
    pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + 20);
    pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
    //判断节表空间是否足够,节表后需要预留一个节表空间的位置
    if (pSectionHeader->PointerToRawData - (DWORD)(pSectionHeader + pPEHeader->NumberOfSections) < 0x50) {
        printf("节表空间不够!\n");
        free(pFileBuffer);
        return NULL;
    }
    //开辟新的内存
    DWORD filesize = CountFileBufferSize(pFileBuffer);
    pNewFileBuffer = malloc(filesize + SECTIONLENGTH);
    if (pNewFileBuffer == NULL) {
        printf("申请内存失败!\n");
        free(pFileBuffer);
        return NULL;
    }
    //新内存初始化为0
    memset(pNewFileBuffer, 0, filesize + SECTIONLENGTH);
    //拷贝原来的文件内容到新内存并释放旧文件内存
    memcpy(pNewFileBuffer, pFileBuffer, filesize);
    free(pFileBuffer);
    //结构体指针再次初始化
    pDosHeader = (PIMAGE_DOS_HEADER)pNewFileBuffer;
    pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pNewFileBuffer + pDosHeader->e_lfanew);
    pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4);
    pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + 20);
    pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
    //修改sizeofimage
    pOptionHeader->SizeOfImage += SECTIONLENGTH;
    //copy第一个节表内容
    memcpy(pSectionHeader + pPEHeader->NumberOfSections, pSectionHeader, EVERYSECTIONTABLELENGTH);
    //修改新增的节表的项目
    PIMAGE_SECTION_HEADER changeSection1 = pSectionHeader + pPEHeader->NumberOfSections;//新增节表首地址
    PIMAGE_SECTION_HEADER changeSection2 = pSectionHeader + pPEHeader->NumberOfSections - 1;//新增节表的前一个节表首地址
    changeSection1->Misc.VirtualSize = SECTIONLENGTH;//修改内存中的尺寸
    changeSection1->SizeOfRawData = SECTIONLENGTH;//修改文件中的尺寸
    memcpy(changeSection1, SECTIONNAME, 0x8);//修改名字
    changeSection1->PointerToRawData = changeSection2->PointerToRawData + changeSection2->SizeOfRawData;
    if (changeSection2->SizeOfRawData > changeSection2->Misc.VirtualSize){
        changeSection1->VirtualAddress = changeSection2->VirtualAddress + changeSection2->SizeOfRawData;
    }
    else{
        changeSection1->VirtualAddress = changeSection2->VirtualAddress + changeSection2->Misc.VirtualSize;
    }
    //修改NumberOfSections
    pPEHeader->NumberOfSections += 1;
    return pNewFileBuffer;
}
/*************************************************

函数功能:将RVA的值转换成FOA;

函数参数:pFileBuffer(LPVOID),virtualAddress(LPSTR)

函数返回值:fileAddress(LPVOID)

**************************************************/
LPVOID RvaToFoa(LPVOID pFileBuffer, LPSTR virtualAddress) {
    LPSTR sectionAddress = NULL;//记录距离节头的距离
    LPSTR fileAddress = NULL;//记录文件中的偏移
    PIMAGE_DOS_HEADER pDosHeader = NULL;
    PIMAGE_NT_HEADERS pNTHeader = NULL;
    PIMAGE_FILE_HEADER pPEHeader = NULL;
    PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
    PIMAGE_SECTION_HEADER pSectionHeader = NULL;
    if (pFileBuffer == NULL) {
        printf("文件写入内存失败!\n");
        return NULL;
    }
    pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
    pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
    pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4);
    pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + 20);
    pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
    if ((DWORD)virtualAddress <= pOptionHeader->SizeOfHeaders){
        return virtualAddress;
    }
    for (DWORD i = 1; i <= pPEHeader->NumberOfSections; i++) {
        if ((DWORD)virtualAddress < pSectionHeader->VirtualAddress) {
            pSectionHeader--;
            break;
        }
        else if (i == pPEHeader->NumberOfSections){
            break;
        }
        else{
            pSectionHeader++;
        }
    }
    //距离该节头的距离
    sectionAddress = virtualAddress - pSectionHeader->VirtualAddress;
    fileAddress = pSectionHeader->PointerToRawData + sectionAddress;
    return (LPVOID)fileAddress;
}
/*************************************************

函数功能:将FOA的值转换成RVA;

函数参数:pFileBuffer(LPVOID),virtualAddress(LPSTR)

函数返回值:fileAddress(LPVOID)

**************************************************/
LPVOID FoaToRva(LPVOID pFileBuffer, LPSTR fileaddress) {
    LPSTR sectionAddress = NULL;//记录距离节头的距离
    LPSTR virtualaddress = NULL;//记录内存中的偏移
    PIMAGE_DOS_HEADER pDosHeader = NULL;
    PIMAGE_NT_HEADERS pNTHeader = NULL;
    PIMAGE_FILE_HEADER pPEHeader = NULL;
    PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
    PIMAGE_SECTION_HEADER pSectionHeader = NULL;
    if (pFileBuffer == NULL) {
        printf("文件写入内存失败!\n");
        return NULL;
    }
    pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
    pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
    pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4);
    pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + 20);
    pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
    if ((DWORD)fileaddress <= pOptionHeader->SizeOfHeaders){
        return fileaddress;
    }
    for (DWORD i = 1; i <= pPEHeader->NumberOfSections; i++) {
        if ((DWORD)fileaddress < pSectionHeader->PointerToRawData) {
            pSectionHeader--;
            break;
        }
        else if (i == pPEHeader->NumberOfSections){
            break;
        }
        else{
            pSectionHeader++;
        }
    }
    //距离该节头的距离
    sectionAddress = fileaddress - pSectionHeader->PointerToRawData;
    virtualaddress = pSectionHeader->VirtualAddress + sectionAddress;
    return (LPVOID)virtualaddress;
}
/*************************************************

函数功能:移动导出表;

函数参数:pFileBuffer(LPVOID)

函数返回值:无


**************************************************/
void MoveExportTable(LPVOID pFileBuffer) {
    PIMAGE_DOS_HEADER pDosHeader = NULL;
    PIMAGE_NT_HEADERS pNTHeader = NULL;
    PIMAGE_FILE_HEADER pPEHeader = NULL;
    PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
    PIMAGE_SECTION_HEADER pSectionHeader = NULL;
    PIMAGE_DATA_DIRECTORY pDataDirectory = NULL;//定位目录
    PIMAGE_EXPORT_DIRECTORY exportTableAddress = NULL;//定位导出表的真正位置
    PDWORD addressOfFunction = NULL;//定位函数地址表的真正位置
    PDWORD addressOfName = NULL;//定位函数名称地址表的真正位置
    PWORD addressNameOrdinals = NULL;//定位函数序号表的真正位置
    LPVOID returnAddress = NULL;//记录RVAtoFOA的返回值
    if (pFileBuffer == NULL) {
        printf("文件写入内存失败!\n");
        return;
    }
    //新增一个节
    LPVOID pNewFileBuffer = AddLastSection(pFileBuffer);
    if (pNewFileBuffer == NULL) {
        printf("新增节失败!\n");
        return;
    }
    pDosHeader = (PIMAGE_DOS_HEADER)pNewFileBuffer;
    pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pNewFileBuffer + pDosHeader->e_lfanew);
    pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4);
    pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + 20);
    pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
    pDataDirectory = (PIMAGE_DATA_DIRECTORY)pOptionHeader->DataDirectory;
    //定位导出表位置
    returnAddress = RvaToFoa(pNewFileBuffer, (LPSTR)(pDataDirectory->VirtualAddress));
    exportTableAddress = (PIMAGE_EXPORT_DIRECTORY)((DWORD)returnAddress + (DWORD)pNewFileBuffer);
    //定位函数地址表
    returnAddress = RvaToFoa(pNewFileBuffer, (LPSTR)(exportTableAddress->AddressOfFunctions));
    addressOfFunction = (PDWORD)((DWORD)(returnAddress)+(DWORD)pNewFileBuffer);
    PDWORD pNewSection = (PDWORD)((pSectionHeader + pPEHeader->NumberOfSections - 1)->PointerToRawData + (DWORD)pNewFileBuffer);
    //复制AddressOfFunctions
    PDWORD repairAddressOfFunction = (PDWORD)((DWORD)pNewSection - (DWORD)pNewFileBuffer);
    memcpy(pNewSection, addressOfFunction, (exportTableAddress->NumberOfFunctions) * 4);
    //定位函数序号表
    returnAddress = RvaToFoa(pNewFileBuffer, (LPSTR)(exportTableAddress->AddressOfNameOrdinals));
    addressNameOrdinals = (PWORD)((DWORD)(returnAddress)+(DWORD)pNewFileBuffer);
    pNewSection = (PDWORD)((DWORD)pNewSection + ((exportTableAddress->NumberOfFunctions) * 4));
    //复制AddressOfNameOrdinals
    PWORD repairAddressNameOrdinals = (PWORD)((DWORD)pNewSection - (DWORD)pNewFileBuffer);
    memcpy(pNewSection, addressNameOrdinals, (exportTableAddress->NumberOfNames) * 2);
    //定位函数名称地址表
    returnAddress = RvaToFoa(pNewFileBuffer, (LPSTR)(exportTableAddress->AddressOfNames));
    addressOfName = (PDWORD)((DWORD)(returnAddress)+(DWORD)pNewFileBuffer);
    pNewSection = (PDWORD)((DWORD)pNewSection + ((exportTableAddress->NumberOfNames) * 2));
    //复制AddressOfNames
    PDWORD repairAddressOfName = (PDWORD)((DWORD)pNewSection - (DWORD)pNewFileBuffer);
    memcpy(pNewSection, addressOfName, (exportTableAddress->NumberOfNames) * 4);
    //循环复制函数名并修复函数名地址
    DWORD singleFunctionNamelen = 0;//记录函数名称长度
    DWORD allFuntionNamelen = 0; //记录函数名称总长度
    PDWORD repairAddressName = (PDWORD)pNewSection;//用于修复函数名地址
    PDWORD functionNamePtr = (PDWORD)((DWORD)pNewSection + (exportTableAddress->NumberOfNames) * 4);//用于拷贝函数名称进行偏移
    PDWORD namePtr = NULL;//存储函数名的FOA
    for (DWORD i = 1; i <= exportTableAddress->NumberOfNames; i++) {
        //将函数名称地址表的RVA转化成FOA
        returnAddress = (PBYTE)RvaToFoa(pNewFileBuffer, LPSTR(*repairAddressName));
        namePtr = (PDWORD)((DWORD)returnAddress + (DWORD)pNewFileBuffer);
        //尾部的0
        singleFunctionNamelen = strlen((const char*)namePtr) + 1;
        memcpy(functionNamePtr, (const void*)namePtr, singleFunctionNamelen);
        allFuntionNamelen += singleFunctionNamelen;
        *repairAddressName = (DWORD)(FoaToRva(pNewFileBuffer, (LPSTR)((DWORD)functionNamePtr - (DWORD)pNewFileBuffer)));
        functionNamePtr = (PDWORD)((DWORD)functionNamePtr + singleFunctionNamelen);
        repairAddressName++;
    }
    pNewSection = (PDWORD)((DWORD)pNewSection + (exportTableAddress->NumberOfNames) * 4 + (DWORD)allFuntionNamelen);
    PBYTE repairExportTable = (PBYTE)((DWORD)pNewSection - (DWORD)pNewFileBuffer);//用于修复目录表
    //复制IMAGE_EXPORT_DIRECTORY结构
    memcpy(pNewSection, exportTableAddress, 0x28);
    //修复IMAGE_EXPORT_DIRECTORY结构中的AddressOfFunctions、AddressOfNameOrdinals、AddressOfNames FOA→RVA
    exportTableAddress = (PIMAGE_EXPORT_DIRECTORY)pNewSection;
    exportTableAddress->AddressOfFunctions = (DWORD)FoaToRva(pNewFileBuffer, (LPSTR)repairAddressOfFunction);
    exportTableAddress->AddressOfNameOrdinals = (DWORD)FoaToRva(pNewFileBuffer, (LPSTR)repairAddressNameOrdinals);
    exportTableAddress->AddressOfNames = (DWORD)FoaToRva(pNewFileBuffer, (LPSTR)repairAddressOfName);
    //修复目录项中的值,指向新的IMAGE_EXPORT_DIRECTORY
    pDataDirectory->VirtualAddress = (DWORD)FoaToRva(pNewFileBuffer, (LPSTR)repairExportTable);
    //存盘
    FILE* pFile = NULL;
    DWORD newfilesize = 0;
    size_t newresult = 0;
    pFile = fopen(OUTPUTFILENAME, OUTPUTMODE);
    if (pFile == NULL) {
        printf("打开文件失败!\n");
        free(pNewFileBuffer);
        return;
    }
    newfilesize = CountFileSize(INPUTFILENAME, INPUTMODE) + SECTIONLENGTH;
    newresult = fwrite(pNewFileBuffer, 1, newfilesize, pFile);
    if (newresult != newfilesize) {
        printf("写入文件失败!\n");
        fclose(pFile);
        free(pNewFileBuffer);
        return;
    }
    printf("存盘成功!\n");
    fclose(pFile);
    free(pNewFileBuffer);
}
int main(){
    LPVOID pFileBuffer = FileBuffer();
    MoveExportTable(pFileBuffer);
    return 0;
}


0x05 结果展示


原来的dll


a3e19d7376400721622b08e0bea3b5c8_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


移动后的dll


c62683647332e83a4c74d369f91ba495_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


相关文章
|
6月前
|
算法 PHP 数据安全/隐私保护
【实战】PHP代码逆向工具,轻松还原goto加密语句的神器!
`goto解密工具`是一款针对PHP的在线神器,能有效解密和还原goto加密代码,提升代码可读性和可维护性。支持单文件及50M压缩包一键解密,提供全效解决方案。通过实际案例展示了解密报错和理解复杂代码的能力,是PHP开发者解决goto难题的得力助手。立即体验:[在线PHP解密大师](https://copy.kaidala.com/dala/goto/index.html)。
112 1
SPSS数据文件的合并
SPSS数据文件的合并
347 0
|
存储 索引
导入表解析,IAT表解析【滴水逆向三期53笔记】
导入表解析,IAT表解析【滴水逆向三期53笔记】
|
6月前
|
NoSQL easyexcel Java
easyexcel导入导出百万条数据思路分析
easyexcel导入导出百万条数据思路分析
204 0
|
存储 C语言
PE导出表,C语言打印导出表信息【滴水逆向三期49笔记+作业】(上)
PE导出表,C语言打印导出表信息【滴水逆向三期49笔记+作业】
|
存储 C语言
PE导出表,C语言打印导出表信息【滴水逆向三期49笔记+作业】(下)
PE导出表,C语言打印导出表信息【滴水逆向三期49笔记+作业】
|
SQL 关系型数据库 MySQL
MySql基础-笔记12 -重复数据处理、SQL注入、导入导出数据
MySql基础-笔记12 -重复数据处理、SQL注入、导入导出数据
187 0
MySql基础-笔记12 -重复数据处理、SQL注入、导入导出数据
|
SQL 关系型数据库 MySQL
一键导出PostgreSQL数据库表设计为word文档
项目开始时,数据库表设计是从概要设计到详细设计,再到数据库中的表结构,有一套完整的文档
1210 0
一键导出PostgreSQL数据库表设计为word文档
|
数据库 索引 程序员
excel导入功能中去掉重复数据—解决思路
excel导入功能中去掉重复数据—解决思路 今天客户提出一个问题,能否在产品中2个子系统中的实现所有的excel导入功能过滤掉已有的数据。 我思考了一番,想到了3种大概的解决思路: 1、在插入每条数据的时候,查到对应的表找出是否存在对应的数据,如存在就跳过。
2077 0
|
SQL Oracle 关系型数据库
教你使用powerDesigner反向生成oracle数据库模型
教你使用powerDesigner反向生成oracle数据库模型
525 0
教你使用powerDesigner反向生成oracle数据库模型