首页
社区
课程
招聘
[原创]汇编打造最简单的shellcode
发表于: 2008-11-6 23:01 18502

[原创]汇编打造最简单的shellcode

2008-11-6 23:01
18502

最近看到了MS08-67的溢出漏洞的公布,看雪上也发布了一些利用工具。但测试的时候发现telnet链接总是链接不上,不知道是自己机器的毛病,还是其他的什么原因没有深究。发现原有的shellcode的运行很隐蔽。测试的时候很是麻烦,更本不知道溢出是否成功。就学着failwest大师的方法做了MessageBOXA的弹出框,由于地址硬编码很不爽。自己就动手做了一个通用的Shellcode。实际上这样的代码四处都能找到。现在的高手们已经不屑于写这些东西了!多数人都在不断的优化,压缩自己的shellcode。而在雪上也没有找到汇编制作的Shellcode的代码的教程,本人就借鉴其他众多的高手写的SC进行,分析改造做成属于自己的shellcode了。所以技术并非首创,只是研习!
再就是本人技术粗劣,见笑了!
------------------------B.j.H's split-line-----------------------------------
shellcode的流程大致是
1、根据PEB查找kernel32.dll的基地址
2、通过kernel32的基地址找到LoadLibraryA函数入口
3、加载user32.dll得到其基地址
4、根据user32的基地址得到MessageBoxA函数入口(弹出窗口)
5、根据kernel32的基地址获得ExitProcess的函数入口(安全退出程序)
------------------------B.j.H's split-line-----------------------------------

;注意masm编译,注意这个程序不能直接运行,编译完成后od加载code3之前运行一次是加密,运行完dump出来就是shellcode了!
.386p
.model flat, stdcall
.code
start: 
assume fs:flat, gs:flat
;这里直接写代码

;------------------------B.j.H's split-line-----------------------------------
;首先建立一个在shellcode中需要用到的字符表他应该包括
;LoadLibraryA
;user32
;MessageBoxA
;ExitProcess
;B.J.H        当然为了弹出的Msgbox有个性可以添加这样一字符串,用来弹框框
;这些字符统统压入堆栈以便调用!
;所以我们代码的前面是
;------------------------B.j.H's split-line-----------------------------------
push 0048h                        ;B.J.H
push 2e4a2e42h                    ;
push 00737365h                    ;ExitProcess
push 636f7250h
push 74697845h
push 0041786fh                    ;MessageBoxA
push 42656761h
push 7373654dh
push 00323372h                    ;user32
push 65737500h
push 41797261h                    ;LoadLibraryA
push 7262694ch
push 64616f4ch
mov edx,esp                       ;保存字符串首地址
;上面是一个函数表!注意压入的字符格式,注意堆栈是倒过来的!慢慢调试可以感觉出来的!
最后一据代码是保存字符串表的的入口(也就是首地址)
;------------------------B.j.H's split-line-----------------------------------
;获得Kernel的基地址(这一段很多地方都能找到!)不做解释了!
xor eax,eax
mov eax,dword ptr FS:[030h]
mov eax,DWORD PTR [eax+0ch]
mov esi,DWORD PTR [eax+01ch]
lodsd
mov eax,dword ptr [eax+8h]
;------------------------B.j.H's split-line-----------------------------------
;现在eax中存放的就是kernel32的基地址了!
;当处到这里我就不知道该怎么出来了!得到基地址到底有什么用呢!不知道!呵呵!时间久了!
;忙忙的阅读一些资料!自然而然的就懂了!...我不是那种一看资料就明白的那种具有潜质的大
;鸟,只是一点一点体会的小菜鸟!
;下面先写一个调用就是
;------------------------B.j.H's split-line-----------------------------------
push eax                          ;保存kernel32.dll基地址
push edx                          ;传入需要查询函数名地址
push 0ch                          ;函数名长度
call getapi                       ;调用函数得到函数入口
;------------------------B.j.H's split-line-----------------------------------
pop ebx                           ;弹出堆栈里的的函数名地址(是之前建立的)
add ebx,0dh                    ; 加上字符长度得到下一个需要使用的字符串的
push ebx                         ;参数"user32"
call eax                           ;调用LoadLibraryA("user32"),加载
                                       ;user32.dll,且eax中是从函数调用返回的user32的基地址
;------------------------B.j.H's split-line-----------------------------------
add ebx,07h                     ; 继续得到下一个需要使用的字符串。下面的几个调用不详细说了... ...
push ebx                          ; 
push 0bh                          ;  
call getapi                        ;调用getaddr函数获取MessageBoxA函数地址.
;------------------------B.j.H's split-line-----------------------------------
pop ebx                           ; 
add ebx,018h                   ; 
push 0h                           ; 
push ebx                          ;
push ebx                          ;
push 0h                           ;
call eax                          ;调用MessageBoxA
;------------------------B.j.H's split-line-----------------------------------
mov edx,0ch                     ; 
pop eax                            ; 
sub ebx,edx                      ; 
push ebx                          ;
push edx                          ;
call getapi                        ;调用getaddr函数获取exitprocess函数地址.
call eax                            ;调用exitprocess
;------------------------B.j.H's split-line-----------------------------------
;下面这个才是重点!(个人认为)
;写一个根据dll的基地址查找API函数入口 的函数方便调用
;函数说明:此函数包括两个传入参数,一个是基地址,另一个是需要查询的函数名字符串首地址
;------------------------B.j.H's split-line-----------------------------------
getapi:
mov ebx,eax                                  ;eax存放的传入的基地址
add eax,03ch                                 ;定位PE头位置地址
mov eax,dword ptr [eax]               ;获得PE头偏移地址
add eax,ebx                                   ;计算PE头VA
cmp dword ptr [eax],00004550h    ;验证PE文件的合法性
jne Err                                            ;不合法跳转
mov eax,dword ptr [eax+078h]     ;导出表地址获得
add eax,ebx                                  ;计算导出表入口VA
push eax                                       ;保存导出表入口VA
mov ecx,eax                                  ;令ecx指向导出表
mov ecx,dword ptr [ecx + 014h]   ;找出导出表中函数个数作为循环值
mov eax,dword ptr [eax + 020h]   ;找出函数名字符串地址表的偏移
add eax,ebx                                  ;计算函数名字符串地址表的VA
push ebp                                        ;保存ebp寄存器,寄存器不够用,借用一下,呵呵!
mov ebp,eax                                  ;ebp保存函数名字符串地址表的VA
xor edx,edx                                   ;edx清0用于存放函数索引

LoopFind:
push ecx;                                       ;保存ecx 循环次数
mov eax,dword ptr[eax]                ;获取函数名字符串偏移
add eax,ebx                                  ;计算函数字符串VA
mov edi,eax                                   ;把VA赋值给edi 准备比较
mov esi,dword ptr [esp + 014h]   ;这个地方要算好了!函数内部有3个push,每个push为4个字节一个12个字节
                                                      ;字符串地址放在call外部倒数第二个push(这个自己用控制好第几个参数是自己定的)
							 ;也就是说从push 字符串地址到这个命令行之间有16个字节被push进入栈中。是不是就是+10H呢
							 ;注意我们这里用了一个函数,不是jmp,函数调用的时候会有一个push eip用于返回!
							 ;所以我们在计算的时候这个push不能忘,最后计算得到esp+14h中所放的就我们要查的函数名字符的地址
mov ecx,dword ptr [esp + 010h]     ; 计算同上,这里是给ecx赋值,函数名的长度。ecx被改变了
                                                        ;(后面,在loop命令之前要恢复的ecx,这个是计数器,硬性规定,呵呵!)
cld                              ;
repz cmpsb                                     ;字符串比较 esi 和 edi 比较
jne FindNext                                   ;比较,如果不是,则取下一个函数

add esp,4                                     ;找到往下执行 相当与pop ecx。无关紧要了!
mov eax,dword ptr [esp + 04h]    ;恢复导出函数表入口
mov eax,dword ptr [eax + 01ch]   ;获得函数地址表入口偏移
                                                     ;我在这里缺少了一步,大多数情况不会有影响,具体的是哪一步,
                                                     ;深究的同志可以看看关于函数导出表的函数查询方式
add eax,ebx                      ;计算函数地址表入口VA
shl edx,2                        ;函数索引*函数地址(默认为4个字节) 鉴戒别人的,很妙的一招相当与乘以4
add eax,edx                      ;函数地址表基址+(函数索引)
mov eax,dword ptr[eax]           ;获得函数入口地址偏移
add eax,ebx                      ;计算VA
jmp Found

FindNext:
inc edx                                ;计数器加一
add ebp,4h                         ;函数名字符串地址表的VA指向下一个函数名地址
mov eax,ebp                       ; 函数名地址的VA赋值到eax
pop ecx                              ;恢复ecx 循环次数
loop LoopFind                     ;loop 继续循环查找

Err:
xor eax,eax                       ;没有找到,或者出错,eax清理。

Found: 
pop ebp                              ;恢复ebp
pop ecx                               ;不能再恢复eax了!弹到ecx好了!
ret 4                                    ; 返回 且 add esp 4 
                                           ;这个是因为我们调用完函数时需要利用之前我们自己建立函数名字符表。
end start

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

上传的附件:
收藏
免费 8
支持
分享
最新回复 (36)
雪    币: 2368
活跃值: (81)
能力值: (RANK:300 )
在线值:
发帖
回帖
粉丝
2
写完了,好累啊!睡觉!呵呵!
2008-11-6 23:22
0
雪    币: 213
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
3
进来膜拜,我的水平还看不懂
2008-11-6 23:26
0
雪    币: 111
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
4
顶顶,明日看。。
2008-11-7 00:23
0
雪    币: 1131
活跃值: (4202)
能力值: ( LV5,RANK:69 )
在线值:
发帖
回帖
粉丝
5
只有膜拜的份   
2008-11-7 08:29
0
雪    币: 315
活跃值: (23)
能力值: ( LV9,RANK:220 )
在线值:
发帖
回帖
粉丝
6
首先你加密代码也没有调用什么函数,怎么不用一些更通用的寄存器,例如edx。
加密代码可以在优化下,为何不考虑从后往前开始xor,这样就省了你的cmp判断了。

code2:
                jmp                code4
               
                pop                edx
               
                mov                eax, 090909090h
               
                mov                ecx, $3a -1
        @@:               
                xor                dword [edx+ecx*4], eax
               
                loop                        @b
               
                jmp                _start
               
        code4:
       
                call        code2       
               
        _start:

                             ; shellcode

下面的压入字符串应该考虑采用call的形式

call   @f
   db  '',0
@@:
2008-11-7 16:29
0
雪    币: 334
活跃值: (22)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
7
楼上的名字我喜欢.....
2008-11-7 16:35
0
雪    币: 2368
活跃值: (81)
能力值: (RANK:300 )
在线值:
发帖
回帖
粉丝
8
很好的建议,谢谢了。呵呵!
我只是想写个通俗易懂的shellcode,一是作笔记复习一下!另一个就是锻炼一下汇编。没有考虑到优化之类。顺便让比我更加怎么说呢...的同志们看看shellcode的运行过程。

再次说明,技术拙劣。高手指正!
2008-11-7 20:04
0
雪    币: 4822
活跃值: (3822)
能力值: ( LV12,RANK:230 )
在线值:
发帖
回帖
粉丝
9
你测的哪个版本的Exploit?这个漏洞,据我所知,目前public了的Exploit以HDM的为最佳,就是metasploit里的那个rb脚本。我不知道你所说的原有shellcode运行很隐蔽是指什么?一般就那么几个跳转指令,比如攻击2003的,你在那个jmp esp的地址上下断点,然后esp指向的指令流,就是shellcode了,只不过一般会编编码什么的,没有什么隐蔽的吧。溢出成功这个就更好判断了,你用的是bindshell,那就在victim上查看相应的bindport是否listen中即可。远程连不上的原因太多,未能有效攻击是最大的可能。
2008-11-7 22:02
0
雪    币: 10868
活跃值: (3282)
能力值: (RANK:520 )
在线值:
发帖
回帖
粉丝
10
楼主说的shellcode隐蔽剧猜测可能是指
1.不能亲切地观察到shellcode被执行的过程吧

  api断下后,进去慢慢跟吧
或者
2.shellcode 前面编码函数流程没明白,f8后shellcode执行完了,然后觉得隐蔽吧

连不上的原因比较多,可能exploit没执行成功,可能执行成功了,window防火墙开着,这时你可以试下换个反向连接的shellcode执行下.

另外我发现公布的exploit 好几个版本(metasploit除外没细看)都是
fun(".\\\\x\..\..\xxxxxxxx...",buffer[],1000,L"",&q,1);
这样执行bug函数时 如果L''" 这一项为空字符串,会把.\\\\x\..\..\xxxxxxxx... 连接到栈里的某个地址指向的内容后面,如果这个地址指的内容为空,那么可以顺利进行,如果不为空,就会失败,带有一定的随机性.

如果fun("\\x\..\..\xxxxxxxx...",buffer[],1000,L".",&q,1); 这样利用,则会吧L"."后面加\ 后接在\\x\..\..\xxxxxxxx... 前面,可以保证顺利进行

在metasploit 学会了饶过dep 但不知道那样的地址对同一补丁的操作系统会不会很通用
2008-11-7 23:30
0
雪    币: 4822
活跃值: (3822)
能力值: ( LV12,RANK:230 )
在线值:
发帖
回帖
粉丝
11
这个你看错了吧。你看那个父函数的F5显示,你说的这个形参就是prefix,代码是有判断的

if ( NULL == prefix || !*prefix )
{
    wcscat(...);
}
wcscpy(...);

如果prefix是空串或空指针,就不拼接了,后面还有显式的wcscpy()确保初始化局部变量。



对确定了的SP及语言版本的,还是很通用的。
2008-11-8 13:04
0
雪    币: 2368
活跃值: (81)
能力值: (RANK:300 )
在线值:
发帖
回帖
粉丝
12
实际上不打算深究为什么shellcode没有能够连接上了!我重视的是shellcode是如何运行的!漏洞如何被利用了!我现在发现原理懂起来不难,但是经验很重要。要有足够是耐心调试程序,分析代码!我基础缺经验就更缺!
但是,楼上几位的讨论我很长见识!谢谢指教了!

我上面说的是很隐蔽是测试是不是成功我看不见!没有弹框来的简洁!
2008-11-9 19:01
0
雪    币: 255
活跃值: (207)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
13
如果求不隐蔽的话,直接 0xcc 好了。
2008-11-9 21:30
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
看一下,哈哈,**啊
2008-11-9 22:17
0
雪    币: 2056
活跃值: (13)
能力值: ( LV13,RANK:250 )
在线值:
发帖
回帖
粉丝
15
不知scz的MSF是怎么更新的?我的是Windows平台,MSF不能自动更新,用SVN手动更新也不能把MS08-067.rb自动下载下来。
2008-11-12 18:30
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
膜拜一下,受教育了
2008-12-11 15:29
0
雪    币: 204
活跃值: (38)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
支持一下。。。初学的好文章
2008-12-11 19:54
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
[quote=netwind;532210]
如果fun("\\x\..\..\xxxxxxxx...",buffer[],1000,L".",&q,1); 这样利用,则会吧L"."后面加\ 后接在\\x\..\..\xxxxxxxx... 前面,可以保证顺利进行
...[/quote]
部分转自LittleWallE的博客http://blog.csdn.net/LittleWallE/archive/2009/01/14/3772791.aspx
我想是像他说的这样吧。
在windows2000和windows xp中,CanonPathName这个函数有一个另外的问题。 可以简单把流程想象成这样:
CanonPathName(WCHAR *A, WCHAR B)
{
       WCHAR buffer[414];
       if(A!=0)
       {
              if(wcslen(A)!=0)
              {
                     wcscpy(buffer,A);
              }
       }else
       {
              buffer[0]=0;
       }
       wcscat(buffer,B);
    ……….
}

如果参数A不是0,而是一个指向空字符串的指针,则该函数的作用就是直接将B连接到缓冲buffer上,而buffer是一片没有初始化的区域,从而造成后面的字符串出错。

相应的,如果以下面的方式调用函数 (第四个参数是空字符串)
NetpwPathCanonicalize(“LittleWallE”,(unsigned short *)buffer,(unsigned char *)arg2, arg3,L"",(long *)Buff3,1);
则偶尔会利用不成功。
解决方法也很简单,将第四个参数设置为L"."即可.
2009-2-7 22:10
0
雪    币: 173
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
这帖子适合新手看,支持
2009-2-8 14:09
0
雪    币: 2067
活跃值: (82)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
21
疑...狐狸的精
得顶二下
没实践过shellcode, 不过我大概知道在玩什么
2009-2-8 14:37
0
雪    币: 247
活跃值: (10)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
22
支持北极狐狸精~~~
2009-2-8 14:51
0
雪    币: 202
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
进来膜拜下~~~~
2009-2-11 10:25
0
雪    币: 285
活跃值: (16)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
24
windows xp下,kernel32.dll的起始地址应该是固定的 0x7c800000,还有LoadLibrary和GetProcAddress函数的入口地址也是固定的,具体可参考upx加壳的记事本,看它的解压过程
2010-2-10 17:54
0
雪    币: 458
活跃值: (421)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
25
顶!!!!!!多谢分享~~
2010-2-10 18:04
0
游客
登录 | 注册 方可回帖
返回
//