首页 > 其他分享 >软件加壳之输入表转储

软件加壳之输入表转储

时间:2023-04-03 17:36:12浏览次数:38  
标签:eax mov 转储 加壳 地址 软件 ebx esi ecx

// EncrpyImport.cpp : 定义控制台应用程序的入口点。

 //


 #include "stdafx.h"

 #include<Windows.h>

 #include<iostream>

 #include<fstream>

 #include<ImageHlp.h>

 using namespace std;

 #pragma comment(lib,"imagehlp.lib")

 void ReaseImportDir(char*srcPath);

 DWORD GetAlign(DWORD size,DWORD align)

 {

     DWORD dwResult=0;

     if(size<align)

         return align;

     if(size%align)

     {

         dwResult=(size/align+1)*align;

     }

     else

     {

         dwResult=(size/align)*align;

     }

 }

 void StorageImportDir(char* srcPath)

 {

     HANDLE hFile=CreateFileA(srcPath,GENERIC_ALL,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

     if(hFile==INVALID_HANDLE_VALUE)

     {

         cout<<"打开文件失败\n";

         return ;

     }

     DWORD dwFileSize=GetFileSize(hFile,NULL);

     HANDLE hMap=CreateFileMappingA(hFile,NULL,PAGE_EXECUTE_READWRITE,0,dwFileSize+4096,NULL);

     if(INVALID_HANDLE_VALUE==hMap)

     {

         cout<<"对不起,创建文件镜像失败\n";

          return;

     }

     LPVOID lpImageBase=(LPVOID)MapViewOfFile(hMap,FILE_MAP_READ|FILE_MAP_WRITE,0,0,0);

     //获得DOS文件头部

     PIMAGE_DOS_HEADER pDosHeader=(PIMAGE_DOS_HEADER)lpImageBase;

     if(pDosHeader->e_magic!=IMAGE_DOS_SIGNATURE)

     {

         cout<<"对不起,非PE文件\n";

         return;

     }

     //获得NT头部

     PIMAGE_NT_HEADERS pNtHeader=(PIMAGE_NT_HEADERS)((DWORD)pDosHeader+pDosHeader->e_lfanew);

     if(pNtHeader->Signature!=IMAGE_NT_SIGNATURE)

     {

         cout<<"对不起,非PE文件\n";

         return;

     }

     //获得可选头部地址

     DWORD dwAddrOfOptionalHeader=(DWORD)&(pNtHeader->OptionalHeader);

     //获得区块数目

     DWORD dwSectionNum=pNtHeader->FileHeader.NumberOfSections;

     //获得原来的入口点

     DWORD dwOldOEP=pNtHeader->OptionalHeader.AddressOfEntryPoint;

     //获得文件对齐值

     DWORD dwFileAlign=pNtHeader->OptionalHeader.FileAlignment;

     //获得内存对齐值

     DWORD dwSectionAlign=pNtHeader->OptionalHeader.SectionAlignment;

     goto shellEnd;

     _asm

     {

 shellCode:

         pushad

         pushfd

         push ebp;

         ;获得PEB地址

         mov eax,fs:[30h]

         ;获得LDR地址

         mov  eax,[eax+0ch];

         ;获得Flink的地址

         mov  eax,[eax+14h];

         ;保存Flink

         mov ecx,eax

         sub  eax,8

         ;入口点,第一个LDR_DATA_TABLE_ENTRY就是程序自身

         mov edx,[eax+18h]

         mov eax,[eax+1ch]


         add eax,10

         ;将起始处的dll名称字符串地址即eax压入堆栈

         push eax

         mov eax,ecx

 searchDll:

         ;保存当前Flink

         mov  ecx,eax

         ;获得LDR_DATA_TABLE_ENTRY地址

         sub  eax,8;        

         ;获得模块的dllBase

         mov  eax,[eax+18h]        

         ;保存基址

         mov ebp,eax

         ;获得PE头部

         mov eax,[eax+3ch]

         ;获得导出表的偏移量

         mov eax,[ebp+eax+78h]

         ;获得导出表地址

         add eax,ebp

         ;保存导出表的地址

         mov edx,eax

         ;模块名称的偏移量

         mov ebx,[edx+0ch]

         ;恢复eax中的Flink

         mov eax,ecx

         ;获得下一个Flink

         mov eax,[eax]        

         ;获得dllname地址

         add ebx,ebp

         mov ecx,4e52454Bh  //;'NREK'

         cmp [ebx],ecx

         jnz searchDll

         mov ecx,32334C45h;'23EL'

         cmp [ebx+4],ecx;

         jnz searchDll

         mov ecx,6c6c642eh;'lld.'

         cmp [ebx+8],ecx

         jnz searchDll;

         ;到此,ebp是Kernel32的基址,edx则是导出表的地址

         ;获得导出表函数名称数组偏移量

         mov ebx,[edx+20h]

         ;获得导出表输出函数名称地址

         add ebx,ebp

         ;获得名称函数个数

         mov ecx,[edx+18h]                

 searchLoadLibraryA:

         dec ecx;

         ;倒序得出输出函数的函数名

         mov esi,[ebx+ecx*4]

         add esi,ebp

         mov eax,64616f4ch;

         cmp [esi],eax

         jnz searchLoadLibraryA;

         mov eax,7262694ch

         cmp [esi+4],eax

         jnz searchLoadLibraryA

         mov eax,41797261h

         cmp [esi+8],eax

         jnz searchLoadLibraryA;    

         ;找到了函数名称,ecx即是函数名称数组中的索引,edx是导出表地址

         ;找到输出表中的函数序号数组地址偏移量

         mov ebx,[edx+24h]

         ;获得函数序号数组地址

         add ebx,ebp;

         mov cx,[ebx+ecx*2]

         ;找到输出表函数地址偏移

         mov ebx,[edx+1ch]

         ;找到输出表函数数组地址

         add ebx,ebp

         mov eax,[ebx+ecx*4]

         ;获得函数地址

         add eax,ebp

         ;找到了LoadLibraryA函数

         ;保存LoadLibraryA函数地址,之前已经压入了模块第一个dll字符串的地址

         push eax

         ;获得PE头部

         MOV eax,ebp

         mov eax,[eax+3ch]

         ;获得导出表的偏移量

         mov eax,[ebp+eax+78h]

         ;获得导出表地址

         add eax,ebp

         ;保存导出表的地址

         mov edx,eax

         //接下来寻找GetProcAddress函数地址        

         ;获得输出函数名称数组地址偏移量

         mov ebx,[edx+20h];

         ;获得输出函数名称数组地址

         add ebx,ebp

         ;获得输出函数个数

         mov ecx,[edx+18h]

 searchGetProc:

         dec ecx

         mov esi,[ebx+ecx*4]

         add esi,ebp

         mov eax,50746547h

         cmp eax,[esi]

         jnz searchGetProc

         mov eax,41636f72h

         cmp eax,[esi+4]

         jnz searchGetProc

         mov eax,65726464h

         cmp eax,[esi+8]

         jnz searchGetProc;

         ;到此已经获得GetProcAddress的ecx值

         ;获得函数序号数组的偏移量

         mov ebx,[edx+24h]

         ;获得函数虚函数组地址

         add ebx,ebp

         mov cx,[ebx+ecx*2]

         ;获得输出函数地址数组偏移地址

         mov ebx,[edx+1ch]

         ;获得输出函数数组的地址

         add ebx,ebp;        

         ;获得GetProcAddress

         mov ebx,[ebx+ecx*4]

         add ebx,ebp;

         ;将GetProcAddress地址压入堆栈,之前已经压入了字符串地址,和LoadLibraryA的地址

         push ebx;

         pop  ebp;是GetProcAddress函数地址

         pop  ebx;是LoadLibraryA函数地址

         pop  edx;是第一个模块字符串地址,堆栈清空


         ;到这里开始获得输入表中的每一个函数地址

         mov esi,edx;保存模块字符串地址

         ;字符串首地址-5是FirstThunk的地址

         sub edx,5

         ;将LoadLibraryA函数地址压入堆栈

         push ebx

 CallLoadLibrary:

         ;获得LoadLibraryA函数地址

         pop ebx



         ;保存FirstThunk的地址对战中只有一项

         push edx

         ;调用LoadLibraryA函数

         push esi

         call ebx;调用LoadLibraryA的地址

         ;保存当前句柄,到此堆栈中只有FirstThunk

         push eax

         ;获得该模块下的输出函数,现有两个数据了,堆栈中

 CalcStrLen:

         ;使得ESI指向输出函数个数

         inc esi

         cmp byte ptr[esi],0

         jnz CalcStrLen;

         inc esi

         ;获得该模块的输出函数个数

         mov ecx,[esi]

         ;esi指向函数名称

         add esi,5



         ;弹出dll的基址到edi中

         pop edi

         ;弹出FirstThunk到edx当中

         ;到此堆栈已空

         pop edx    


         ;ebx中是LoadLibraryA函数地址,暂存,ebx作为他用

         push ebx

         ;ebx作为索引

         xor ebx,ebx

 CallGetProcAddress:

         

         push edx;保存FirstThunk地址

         ;ecx保存暂作他用,执行GetProcAddress会影响ecx

         push ecx

         ;到此对战中有了三项LoadLibraryA,FirstThunk,函数个数

         

         push esi;

         push edi

         call ebp;调用GetProcAddress

         ;FirstThunk的地址

         ;重新获得函数个数

         pop  ecx

         ;获得FirstThunk地址

         pop edx



         ;拼凑FirstThunk的地址,到此堆栈中只有LoadLibraryA的地址

         push edx

         push ebx

         push esi

         push ecx


         ;获得PEB地址

         mov esi,fs:[30h]

         ;获得LDR地址

         mov  esi,[esi+0ch];

         ;获得Flink的地址

         mov  esi,[esi+14h];        

         sub  esi,8

         ;入口点,第一个LDR_DATA_TABLE_ENTRY就是程序自身

         mov esi,[esi+1ch]

         ;基址+1获得偏移量

         add esi,1

         mov ecx,[esi]

         sub esi,1

         add esi,ecx;

         add esi,5

         ;开始检测是否是0

 Exam0:

         inc esi

         mov ecx,[esi]

         cmp ecx,0

         jnz Exam0;

         mov ecx,[esi+4]

         cmp ecx,0

         jnz Exam0;

         sub esi,4;

         mov ecx,[esi];获得偏移量

         ;获得偏移量的补码

         mov edx,[edx]

         not ecx

         sub esi,ecx

         

         and esi,0ffff0000h

         

         and edx,0ffffh

         add edx,esi

         mov [edx+ebx*4],eax

         

         /*

         

         

         add esi,3

         */

         pop ecx        

         pop esi

         pop ebx

         pop edx        

         ;到此堆栈中还是只有LoadLibraryA

 GetNextProcName:

         ;esi跳过函数名称字符串

         inc esi

         cmp byte ptr[esi],0

         jnz GetNextProcName

         inc esi

         inc ebx

         loop CallGetProcAddress

         ;检测是否结束

         mov ecx,[esi]

         cmp ecx,0

         ;输入表便利结束

         jz  End;

         ;开始获得下一个FirstThunk,edx指向FirstThunk,esi指向dll字符串名称

         mov edx,esi

         add esi,5        

         ;esi指向dll名称

 jmp        CallLoadLibrary

 End:

         pop edx;平衡堆栈

         ;将LoadLibraryA弹出堆栈

         pop  ebp

         popfd

         popad

 mpl:


         

     }    

 shellEnd:

     char*pShellCode=NULL;

     DWORD dwShellLen=0;

     _asm

     {

         lea eax,shellCode

         lea ebx,shellEnd;

         sub ebx,eax

         mov dwShellLen,ebx

         mov pShellCode,eax


     }

     

     //获得新的输入表

     PIMAGE_IMPORT_DESCRIPTOR pImportDir=(PIMAGE_IMPORT_DESCRIPTOR)ImageRvaToVa(pNtHeader,lpImageBase,pNtHeader->OptionalHeader.DataDirectory[1].VirtualAddress,                                           NULL);

     BYTE fill=0;

     DWORD dwWriteLen=0;

     //存放变形的输入表

     char* strImportDir=new char[4096];

     char* pCurrent=strImportDir;

     memset(strImportDir,0,4096);

     while(pImportDir->Name!=NULL)

     {

         PIMAGE_THUNK_DATA pThunkData=(PIMAGE_THUNK_DATA)ImageRvaToVa(pNtHeader,lpImageBase,pImportDir->OriginalFirstThunk,NULL);

         //保存FirstThunk的RVA

         DWORD FirstThunk=pImportDir->FirstThunk;

         memcpy(strImportDir,&FirstThunk,4);        

         dwWriteLen+=sizeof(DWORD);

         strImportDir+=sizeof(DWORD);

         //写入0,标志FirstThunk结束

         memcpy(strImportDir,&fill,1);

         dwWriteLen+=1;

         strImportDir+=1;

         //获得dll名称

         char* strDllName=(char*)ImageRvaToVa(pNtHeader,lpImageBase,pImportDir->Name,NULL);

         int nDllNameLen=strlen(strDllName);

         //写入dll名称    

         memcpy(strImportDir,strDllName,nDllNameLen);

         strImportDir+=nDllNameLen;            

         dwWriteLen+=nDllNameLen;

         //写入0        

         memcpy(strImportDir,&fill,1);

         dwWriteLen+=1;

         strImportDir+=1;

         //获得输入函数个数

         int nImportFuncNum=0;

         PIMAGE_THUNK_DATA pThunkData2=pThunkData;

         while(pThunkData->u1.AddressOfData!=NULL)

         {

             PIMAGE_IMPORT_BY_NAME pImportByName=(PIMAGE_IMPORT_BY_NAME)ImageRvaToVa(pNtHeader,lpImageBase,pThunkData->u1.AddressOfData,NULL);

             pThunkData++;        

             nImportFuncNum++;

         }

         //写入输入函数个数        

         memcpy(strImportDir,&nImportFuncNum,4);

         strImportDir+=sizeof(int);

         dwWriteLen+=sizeof(int);

         //填充0

         memcpy(strImportDir,&fill,1);        

         dwWriteLen+=1;

         strImportDir+=1;

         while(pThunkData2->u1.AddressOfData!=NULL)

         {            

             PIMAGE_IMPORT_BY_NAME pImportByName=(PIMAGE_IMPORT_BY_NAME)ImageRvaToVa(pNtHeader,lpImageBase,pThunkData2->u1.AddressOfData,NULL);            

             char* strFuncName=(char*)pImportByName->Name;

             int nFuncNameLen=strlen(strFuncName);

             //写入输入函数的名称            

             memcpy(strImportDir,strFuncName,nFuncNameLen);

             strImportDir+=nFuncNameLen;

             dwWriteLen+=nFuncNameLen;

             //填充0

             memcpy(strImportDir,&fill,1);

             dwWriteLen+=1;    

             strImportDir+=1;

             pThunkData2++;    

         }

         pImportDir++;

     }

     //填充4个0

     memcpy(strImportDir,&fill,1);

     dwWriteLen+=1;    

     strImportDir+=1;

     memcpy(strImportDir,&fill,1);

     dwWriteLen+=1;    

     strImportDir+=1;

     memcpy(strImportDir,&fill,1);

     dwWriteLen+=1;    

     strImportDir+=1;

     memcpy(strImportDir,&fill,1);

     dwWriteLen+=1;    

     strImportDir+=1;

     DWORD dwTmp=0;    

     SetFilePointer(hFile,pDosHeader->e_lfanew+sizeof(IMAGE_FILE_HEADER)+4+pNtHeader->FileHeader.SizeOfOptionalHeader,0,FILE_BEGIN);

     IMAGE_SECTION_HEADER SectionTmp={0};

     //获得最后一个区块的信息

     for(int i=0;i<dwSectionNum;i++)

     {

         ReadFile(hFile,&SectionTmp,sizeof(IMAGE_SECTION_HEADER),&dwTmp,0);

     }

     IMAGE_SECTION_HEADER shellSection={0};    

     //修改区块属性

     shellSection.Characteristics=IMAGE_SCN_MEM_READ|IMAGE_SCN_MEM_EXECUTE|IMAGE_SCN_MEM_WRITE;

     //填充区块的真实大小

     dwShellLen+=dwWriteLen;

     shellSection.Misc.VirtualSize=dwShellLen;

     //填充区块的PointerToRawData

     shellSection.PointerToRawData=SectionTmp.PointerToRawData+SectionTmp.SizeOfRawData;

     //填充区块的SizeOfRawData

     shellSection.SizeOfRawData=GetAlign(dwShellLen,dwFileAlign);

     //填充新区块的VirtualAddress

     shellSection.VirtualAddress=SectionTmp.VirtualAddress+GetAlign(SectionTmp.Misc.VirtualSize,dwSectionAlign);

     //区块数目加1

     pNtHeader->FileHeader.NumberOfSections++;

     //新区块的名称

     strncpy((char*)shellSection.Name,".hehe",5);

     //修改程序入口点

     pNtHeader->OptionalHeader.AddressOfEntryPoint=shellSection.VirtualAddress;

     //写入新的区块信息

     DWORD dwOptionalSize=pNtHeader->FileHeader.SizeOfOptionalHeader;

     //修改镜像大小

     pNtHeader->OptionalHeader.SizeOfImage+=GetAlign(shellSection.Misc.VirtualSize,dwSectionAlign);

     //修改代码区大小

     pNtHeader->OptionalHeader.SizeOfCode+=GetAlign(shellSection.Misc.VirtualSize,dwSectionAlign);    

     //写入新区块信息

     WriteFile(hFile,&shellSection,sizeof(IMAGE_SECTION_HEADER),&dwTmp,0);    

     //移动文件指针    

     SetFilePointer(hFile,shellSection.PointerToRawData,0,FILE_BEGIN);    

     

     //因为该区段开始是变形的输入表,所以要跳转到相应的开始地址

     BYTE jmp=0xe9;

     WriteFile(hFile,&jmp,sizeof(BYTE),&dwTmp,0);

     WriteFile(hFile,&dwWriteLen,sizeof(DWORD),&dwTmp,0);

     WriteFile(hFile,pCurrent,dwWriteLen,&dwTmp,0);

     //移动文件指针    

     //写入shellcode

     dwShellLen-=dwWriteLen;

     WriteFile(hFile,pShellCode,dwShellLen,&dwTmp,0);

     //跳回元入口    

     WriteFile(hFile,&jmp,1,&dwTmp,0);

     //获得入口

     dwShellLen+=dwWriteLen+5;

     dwOldOEP=dwOldOEP-(shellSection.VirtualAddress+dwShellLen)-5;

     WriteFile(hFile,&dwOldOEP,4,&dwTmp,0);    

     ::UnmapViewOfFile(lpImageBase);

     CloseHandle(hMap);

     CloseHandle(hFile);

     ReaseImportDir(srcPath);

 }


 void ReaseImportDir(char*srcPath)

 {

     HANDLE hFile=CreateFileA(srcPath,GENERIC_ALL,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

     if(INVALID_HANDLE_VALUE==hFile)

     {

         cout<<"文件打开失败\n";

         return ;

     }

     DWORD dwFileSize=GetFileSize(hFile,NULL);

     HANDLE hMap=CreateFileMappingA(hFile,NULL,PAGE_EXECUTE_READWRITE,0,dwFileSize,NULL);

     if(INVALID_HANDLE_VALUE==hMap)

     {

         cout<<"创建文件映像失败\n";

         return ;

     }

     LPVOID lpImageBase=(LPVOID)MapViewOfFile(hMap,FILE_MAP_READ|FILE_MAP_WRITE,0,0,0);

     //获得DOS文件头部

     PIMAGE_DOS_HEADER pDosHeader=(PIMAGE_DOS_HEADER)lpImageBase;

     if(pDosHeader->e_magic!=IMAGE_DOS_SIGNATURE)

     {

         cout<<"非PE文件\n";

         return;

     }

     //获得PE文件头部

     PIMAGE_NT_HEADERS pNtHeader=(PIMAGE_NT_HEADERS)((DWORD)pDosHeader+pDosHeader->e_lfanew);

     if(pNtHeader->Signature!=IMAGE_NT_SIGNATURE)

     {

         cout<<"非PE文件\n";

         return;

     }

     //获得可选头部地址

     DWORD dwAddrOfOptionalHeader=(DWORD)&(pNtHeader->OptionalHeader);

     //读出区块地址

     PIMAGE_SECTION_HEADER pSectionHeader=(PIMAGE_SECTION_HEADER)(dwAddrOfOptionalHeader+pNtHeader->FileHeader.SizeOfOptionalHeader);

     //读出区块数目

     DWORD dwSectionNum=pNtHeader->FileHeader.NumberOfSections;

     for(int i=0;i<dwSectionNum;i++)

     {

         cout<<pSectionHeader->Name<<endl;

         pSectionHeader++;

     }

     //读取输入表结构

     PIMAGE_IMPORT_DESCRIPTOR pImportDir=(PIMAGE_IMPORT_DESCRIPTOR)ImageRvaToVa(pNtHeader,lpImageBase,pNtHeader->OptionalHeader.DataDirectory[1].VirtualAddress,NULL);

     while(pImportDir->Name!=NULL)

     {

         char* strDllName=(char*)pImportDir->Name;

         PIMAGE_THUNK_DATA pThunkData=(PIMAGE_THUNK_DATA)ImageRvaToVa(pNtHeader,lpImageBase,pImportDir->OriginalFirstThunk,NULL);

         int nDllNameLen=strlen(strDllName);

         //dll文件名清除

         memset(strDllName,0,nDllNameLen);

         cout<<"当前模块是:"<<strDllName<<endl;

         while(pThunkData->u1.AddressOfData!=NULL)

         {

             PIMAGE_IMPORT_BY_NAME pImportByName=(PIMAGE_IMPORT_BY_NAME)ImageRvaToVa(pNtHeader,lpImageBase,pThunkData->u1.AddressOfData,NULL);

             char* strFuncName=(char*)pImportByName->Name;

             cout<<strFuncName<<endl;

             int nFuncNameLen=strlen(strFuncName);

             //输入函数名清除

             memset(strFuncName,0,nFuncNameLen);

             //IMAGE_THUNK_DATA清除

             memset(pThunkData,0,sizeof(DWORD));

             pThunkData++;

         }

         //IMAGE_IMPORT_DESCRIPTOR清除

         memset(pImportDir,0,sizeof(IMAGE_IMPORT_DESCRIPTOR));

         pImportDir++;

     }

     ::UnmapViewOfFile(lpImageBase);

     CloseHandle(hMap);

     CloseHandle(hFile);    

 }

 int _tmain(int argc, _TCHAR* argv[])

 {

     StorageImportDir("D:\\project\\凯撒加密算法\\Debug\\凯撒加密算法.exe");

     ReaseImportDir("D:\\project\\凯撒加密算法\\Debug\\凯撒加密算法.exe");

     return 0;

 }

标签:eax,mov,转储,加壳,地址,软件,ebx,esi,ecx
From: https://blog.51cto.com/u_15995156/6166928

相关文章

  • 软件加壳输入表处理-解析
    本篇博文说下PE文件中输入表的格式和具体的使用,以及在软件加壳中的注意事项(本人菜鸟),高手飘过IMAGE_IMPORT_DESCRIPTORSTRUC{unionCharacteristicsDWORDOriginalFirstThunkDWORDendsTimeDateStampDWORDForwardChainDWORDNameD......
  • 公司内部通讯软件如何选择?
    随着互联网和通讯软件的普及,让远距离沟通可以随时实现,给人们的生活和工作带来了许多便利,但目前许多公司内部使用的是QQ、微信等社交通讯软件,虽然这些社交通讯软件能够解决公司内部部分沟通需求,但却有着一定的瓶颈,而且也提升不了工作效率。因此,采用公司级的通讯软件自然成为了企业......
  • 软考-软件工程
    目录......
  • Chief Architect Premier X15(建筑和室内设计软件)
    ChiefArchitectPremierX15是一款高级的建筑设计软件,它可以帮助建筑师和设计师创建复杂的建筑和室内设计。该软件具有强大的三维渲染功能,可以帮助用户预览和编辑设计,以及快速创建高质量的建筑图纸和施工图。ChiefArchitectPremierX15还具有大量的库和工具,可以帮助用户快......
  • 联芸mas0902固态使用量产工具的开卡方法,mas0902/mas1102开卡软件教程
    MAS0902是一款固态存储器芯片,一般固态硬盘损坏的情况下才需要量产,在量产过程中,需要从量产部落网下载对应主控型号和闪存颗粒类型的量产工具后,使用量产工具来对芯片进行初始化和测试。以下是MAS0902固态使用量产工具的开卡方法:1.首先,将采用MAS0902芯片的固态硬盘短接ROM孔后,连......
  • 我对软件开发的一些感悟
    从事软件开发也有大概3到4年了,虽然写的都是一些比较小的软件,但是也算理解了其中的一点奥秘。一、C#软件开发我用C#写过比较多的客户端软件,因为C#是微软推出的嘛,因此这些软件都是在windows系列平台上才能使用。使用C#的最大优点是开发快,确定是环境依赖高,运行速度慢。这一点相信......
  • 可重复构建为软件供应链安全保驾护航
    可重复构建(ReproducibleBuilds)是证明软件供应链安全的必要手段,2022已被纳入SupplyChainSecurityCon的topics以及微软的S2C2F(SecureSupplyChainConsumptionFramework)当中,并受到了Google开源安全团队的支持赞助。OpenSSF/SLSA在软件供应链完整性与包管理最佳实践中也对可重......
  • 脑干软件接口实现
    接口分析今天写了一个删除用户运动指数的接口,运动指数包括了两个属性,一个每周运动次数和每次运动时间,我们需要做的就是把这两个字段清空,因为这个字段在user表中,同时我们删除他的时候不能把其他的属性删除,所以我们就不能直接使用delete方法,而是删除再添加再修改。问题与解决重......
  • 软件开发定律:霍夫施塔特定律,为什么项目交付总是会延期?
    hi,我是熵减,见字如面。在软件项目中,你是否遇到过这种情况:一个软件工程师,要开发一个系统功,这个系统需求有点复杂,需要新增多个模块,同时也需要和多个系统交互。工程师会按照自己的经验,做一个粗略的工期评估,同时在加上一点缓冲时间,从而得出一个开发工期的总时长。但最终的结果,可能会......
  • 5款轻量易上手的团队目标管理软件(推荐收藏)
    团队目标管理的核心是团队目标的设定和管理,而在当下的互联网时代想要实现团队目标,一款好的团队目标管理软件是必不可少的,好的团队软件可以提高团队的协作效率,有效地追踪团队目标的推进情况。想要实现团队的高效管理,不妨试一下这五款团队目标管理软件。1、通用团队目标管理:Worktil......