6.1.17 pwn SECCONCTF2016 jmper

下载文件

题目复现

$ file jmper
jmper: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=9fce8ae11b21c03bf2aade96e1d763be668848fa, not stripped
$ checksec -f jmper
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
Full RELRO      No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   No      0               4       jmper
$ strings libc-2.19.so | grep "GNU C"
GNU C Library (Ubuntu EGLIBC 2.19-0ubuntu6.9) stable release version 2.19, by Roland McGrath et al.
Compiled by GNU CC version 4.8.4.

64 位动态链接程序,但 Full RELRO 表示我们不能修改 GOT 表,然后还开启了 NX 防止注入 shellcode。No canary 表示可能有溢出,not stripped、No PIE 都是好消息。默认开启 ASLR。

在 Ubuntu-14.04 上玩一下:

$ LD_PRELOAD=./libc-2.19.so ./jmper
Welcome to my class.
My class is up to 30 people :)
1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
1
1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
2
ID:0
Input name:AAAA
1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
3
ID:0
Input memo:BBBB
1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
4
ID:0
AAAA1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
5
ID:0
BBBB1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
6

似乎是新建的 student 会对应一个 id,根据 id 可以查看或修改对应的 name 和 memo。

题目解析

程序主要由两部分组成,一个是 main() 函数,另一个是实现了所有功能的 f() 函数。

main

在 main 函数里分配了两块内存空间,一块是包含了 30 个 student 结构体指针的数组,地址放在 my_class0x00602030)。另一块用于存放一个 jmp_buf 结构体,这个结构体中保存当前上下文,结构体的地址放在 jmpbuf0x00602038)。并且这两个符号都在 .bss 段中。

这里就涉及到 setjmp()longjmp() 的使用,它们用于从一个函数跳到另一个函数中的某个点处。函数原型如下:

  • setjmp():将函数在此处的上下文保存到 jmp_buf 结构体,以供 longjmp 从此结构体中恢复上下文

    • env:保存上下文的 jmp_buf 结构体变量

    • 如果直接调用该函数,返回值为 0。如果该函数从 longjmp 调用返回,返回值根据 longjmp 的参数决定。

  • longjmp():从 jmp_buf 结构体中恢复由 setjmp 函数保存的上下文,该函数不返回,而是从 setjmp 函数中返回

    • env:由 setjmp 函数保存的上下文

    • val:传递给 setjmp 函数的返回值,如果 val 值为 0,setjmp 将会返回 1,否则返回 val

longjmp() 执行完之后,程序就回到了 setjmp() 的下一条语句继续执行。

f

接下来我们看一下各功能的实现(程序设计真的要吐槽一下):

首先注意到这个函数没有 return 指令,要想离开只有两种方法,一个是 exit(),另一个是 longjmp() 跳回 main 函数,既然这么设置那当然是有用意的。

通过分析,可以得到 student 结构体和数组 my_class:

漏洞就是在读入 memo 和 name 的时候都存在的 one-byte overflow,其中 memo 会覆盖掉 name 指针的低字节。考虑可以将 name 指针改成其它地址,并利用修改 name 的功能修改地址上的内容。

漏洞利用

所以我们的思路是通过 one-byte overflow,使 my_class[0]->name 指向 my_class[1]->name,从而获得任意地址读写的能力。然后泄漏 system 函数地址和 main 函数的返回地址,将返回地址覆盖以制造 ROP,调用 system('/bin/sh') 获得 shell。

overflow

首先添加两个 student:

然后利用 my_class[0]->memo 的溢出修改 my_class[0]->name,使其指向 my_class[1]->name:

通过 overflow,我们控制了 my_class[1]->name,可以对任意地址(除了GOT表)读或写。

leak

然后我们可以修改 my_class[1]->name 为 libc 中任意符号的地址,从而泄漏出需要的地址信息:

于是我们就得到了 system 函数的地址和 main 函数的返回地址。

这里我们利用了 libc 中的 environ 符号,该符号执行一个栈上的地址,通过计算偏移即可得到返回地址。

overwrite

接下来我们将 student_num 改为 '/bin/sh',这样一方面为 system 提供了参数,另一方面可以触发 longjmp。

pwn

Bingo!!!

exploit

完整的 exp 如下:

参考资料

Last updated

Was this helpful?