Catalog
- Overflow
- Find string in gdb
- Binary Service
- Find specific function offset in libc
- Find ‘/bin/sh’ or ‘sh’ in library
- Leak stack address
- Fork problem in gdb
- Secret of a mysterious section - .tls
- Predictable RNG(Random Number Generator)
- Make stack executable
- Use one-gadget-RCE instead of system
- Hijack hook function
- Use printf to trigger malloc and free
- Use execveat to open a shell
Overflow
例如:1
2char buf[40]
signed int num
scanf
1 | scanf("%s", buf) |
%s
没有边界检查- pwnable
1 | scanf("%39s", buf) |
39s
只从输入中获取39个字节,并将 NULL 字节放在输入的末尾。- useless
1 | scanf("%40s", buf) |
- 乍一看,这似乎是合理的。
- 它从输入中获取40字节,但它也将NULL字节放在输入的末尾。
- 但是,它有one-byte-overflow
- pwnable
1 | scanf("%d", &num) |
* 由于`alloca`从调用者的堆栈框架分配内存,因此有一条` sub esp, eax `指令来实现这一点。
* 如果我们使num为负,它将有重叠的堆栈帧。
* E.g. [Seccon CTF quals 2016 cheer_msg](https://github.com/ctfs/write-ups-2016/tree/master/seccon-ctf-quals-2016/exploit/cheer-msg-100)
- 使用num访问一些数据结构
- 大多数情况下,程序只检查较高的边界,忘记使num无符号。
- 使num为负可以让我们覆盖一些重要的数据
gets
1 | gets(buf) |
- 没有检查边界
- pwnable
1 | fgets(buf, 40, stdin) |
- 它只从输入中获取39个字节,并将NULL字节放在输入的末尾。
- useless
read
1 | read(stdin, buf, 40) |
- 它从输入中获取40字节,并且不将NULL字节放在输入的末尾。
- 这看起来很安全,但可能会有 information leak.
- leakable
E.g.
memory layout1
2
30x7fffffffdd00: 0x4141414141414141 0x4141414141414141
0x7fffffffdd10: 0x4141414141414141 0x4141414141414141
0x7fffffffdd20: 0x4141414141414141 0x00007fffffffe1cd
- 如果现在用
printf
或者puts
来输出这个buf,它将一直输出到NULL byte. - 在这种情况下,我们可以:
1
'A'*40 + '\xcd\xe1\xff\xff\xff\x7f'
1 | fread(buf, 1, 40, stdin) |
- 几乎和
read
一样 - leakable
strcpy
假设还有另一个缓冲区:1
char buf2[60]
1 | strcpy(buf, buf2) |
- 没有边界检查
- buf2的大小可能比buf大
- 所以可能会buffer overflow
- pwnable
1 | strncpy(buf, buf2, 40) |
- 这个操作会把buf2前40 bytes复制到buf,但是不会再末尾添加NULL byte
- 因为结尾没有NULL byte, 也许可以 information leak.
- leakable
strcat
假设有:1
char buf2[60]
1 | strcat(buf, buf2) |
- strcat是将两个char类型连接。
- 如果buf不够大,可能会导致溢出。
- 它将NULL byte放在末尾,这可能会导致one-byte-overflow.
- 在某些情况下,我们可以使用这个NULL byte来更改堆栈地址或堆地址。
- pwnable
1 | strncat(buf, buf2, n) |
- 和
strcat
相似, 但是有大小n限制 - pwnable
- E.g. Seccon CTF quals 2016 jmper
Find string in gdb
在 SSP中, 我们需要找出argv[0]和输入缓冲区之间的偏移量。
gdb
- Use
p/x ((char **)environ)
in gdb, and the address of argv[0] will be theoutput - 0x10
- 在gdb中使用
p/x ((char **)environ)
, argv[0]的地址将是output - 0x10
E.g.
1 | (gdb) p/x (char **)environ |
gdb peda
- 使用
searchmem
搜索内存1
2
3Usage:
searchmem pattern start end
searchmem pattern mapname
1 | gdb-peda$ searchmem "/home/naetw/CTF/seccon2016/check/checker" |
Binary Service
不需要使用共享库的binary:
1 | ncat -vc ./binary -kl $ip $port |
需要使用共享库的binary:
1 | ncat -vc 'LD_PRELOAD=/path/to/libc.so ./binary' -kl $ip $port |
在此之后,您可以通过nc连接到二进制服务1
nc $ip $port
Find specific function offset in libc
如果我们成功地泄漏了某个函数的libc地址,我们可以通过用函数在libc地址减去该函数的偏移量来得到libc的基地址。
Manually
1 | readelf -s libc名 | grep 函数名@ |
E.g.
1 | $ readelf -s libc-2.19.so | grep system@ |
Automatically
- 使用 pwntools
E.g.
1 | from pwn import * |
Find ‘/bin/sh’ or ‘sh’ in library
先得到libc的基地址
Manually
1 | objdump -s libc.so | less (search 'sh') |
Automatically
- 使用 pwntools
E.g.
1 | from pwn import * |
译者注 : next(libc.search(some_characters
)) 在libc找到包含 some_characters(字符串,汇编代码或者某个数值)的地址
Leak stack address
constraints:
- libc的基地地址已经泄露了吗
- 可以泄露任意地址的内容吗
有一个 symbol environ
在libc中,它的值与main
函数的第三个参数char **envp
相同。
char **envp ‘的值在堆栈上,因此我们可以使用这个 symbol泄漏堆栈地址。
1 | (gdb) list 1 |
- 当前的libc基地址是
0x7ffff7a0e000
environ
在libc的偏移量为0x3c5f38
这本 手册 详细解释了 environ
Fork problem in gdb
当您使用gdb
调试一个带有fork()
函数的二进制文件时,您可以使用以下命令来确定要执行哪个进程(原始gdb
的默认设置是父进程,而gdb-peda
的默认设置是子进程):
1 | set follow-fork-mode parent |
Alternatively, using set detach-on-fork off
, we can then control both sides of each fork. Using inferior X
where X
is any of the numbers that show up for info inferiors
will switch to that side of the fork. This is useful if both sides of the fork are necessary to attack a challenge, and the simple follow
ones above aren’t sufficient.
或者,使用set detach-on-fork off
,我们可以控制每个fork的两边。使用inferior X
,其中X
是出现在info inferiors
中的任何一个数字,都会切换到fork的那一边。如果分支的两端都是攻击某个挑战所必需的,并且上面简单的follow
操作还不够,那么这种方法非常有用。1
2
3
4gdb-peda$ info inferiors
Num Description Executable
* 1 xxxx xxxxx
2 xxxx xxxxx
Secret of a mysterious section - .tls
constraints:
- Need
malloc
function and you can malloc with arbitrary size - Arbitrary address leaking
We make malloc
use mmap
to allocate memory(size 0x21000 is enough). In general, these pages will be placed at the address just before .tls
section.
There is some useful information on .tls
, such as the address of main_arena
, canary
(value of stack guard), and a strange stack address
which points to somewhere on the stack but with a fixed offset.
Before calling mmap:
1 | 7fecbfe4d000-7fecbfe51000 r--p 001bd000 fd:00 131210 /lib/x86_64-linux-gnu/libc-2.24.so |
After call mmap:
1 | 7fecbfe4d000-7fecbfe51000 r--p 001bd000 fd:00 131210 /lib/x86_64-linux-gnu/libc-2.24.so |
Predictable RNG(Random Number Generator)
When the binary uses the RNG to make the address of important information or sth, we can guess the same value if it’s predictable.
Assuming that it’s predictable, we can use ctypes which is a build-in module in Python.
ctypes allows calling a function in DLL(Dynamic-Link Library) or Shared Library.
Therefore, if binary has an init_proc like this:
1 | srand(time(NULL)); |
Then we can use ctypes to get the same value of addr.
1 | import ctypes |
Make stack executable
Use one-gadget-RCE instead of system
constraints:
- 有libc的基本地址
- 任意地址写
几乎每个pwnable挑战都需要在攻击结束时调用system("/bin/sh")
,但是如果我们想调用它,我们必须操纵参数,当然,还需要劫持一些函数来调用’system
。如果我们不能操纵参数怎么办?
使用 one-gadget-RCE!
用 one-gadget-RCE, 我们就劫持 .got.plt
或者我们可以用它来控制eip 使程序跳转到 one-gadget, 但是在使用它之前需要满足一些constraint
libc中有很多one-gadgets. 每个都有不同的constraints,但它们是相似的。每个constraints都与寄存器的状态有关。
E.g.
- ebx 在libc里的地址是
rw-p
的 [esp+0x34] == NULL
我们如何得到这些constraints? 这里有一个有效的工具 one_gadget !!!!
如果我们能满足这些constraints, 我们就可以轻易getshell
Hijack hook function
constraints:
- 有libc基地址
- 任意地址写
- 程序有用到
malloc
,free
orrealloc
By manual:
原文:
The GNU C Library lets you modify the behavior ofmalloc
,realloc
, andfree
by specifying appropriate hook functions. You can use these hooks to help you debug programs that use dynamic memory allocation, for example.
译文:
GNU C库允许您通过指定适当的hook函数来修改malloc
、realloc
和free
的行为。例如,您可以使用这些hook来帮助调试使用动态内存分配的程序。
malloc.h中声明了一些hook变量。它们的默认值是0x0
1 | __malloc_hook |
因为它们用于帮助用户调试程序,所以它们在执行的时候是可写的。
1 | 0xf77228e0 <__free_hook>: 0x00000000 |
这是malloc.c的源码.下面我使用 __libc_free
来演示
1 | void (*hook) (void *, const void *) = atomic_forced_read (__free_hook); |
它检查__free_hook
的值。如果不是NULL,它将首先调用hook函数。在这里,我们希望使用one-gadget-RCE。由于hook函数是在libc中调用的,所以通常满足one-gadget的constraints。
Use printf to trigger malloc and free
查看printf
函数的源码,有几个地方可能会触发malloc
. 拿 vfprintf.c line 1470 来做例子:
1 | #define EXTSIZ 32 |
We can find that malloc
will be triggered if the width field is large enough.(Of course, free
will also be triggered at the end of printf if malloc
has been triggered.) However, WORK_BUFFER_SIZE is not large enough, since we need to go to else block. Let’s take a look at __libc_use_alloca
and see what exactly the minimum size of width we should give.
我们可以发现,如果width field足够大,将触发malloc
。(当然,如果malloc
被触发,那么free
也会在printf的末尾被触发。)但是,WORK_BUFFER_SIZE不够大,所以我们需要转到else块从而触发mallco。让我们看看__libc_use_alloca
,看看应该给出的最小宽度是多少。
1 |
|
我们必须确保:
1 | size > PTHREAD_STACK_MIN / 4 |
- 我并没有完全理解函数THREAD_GETMEM的作用,但它似乎主要返回0。
- 第二个条件通常是
size > 65536
More details:
conclusion
- 触发
malloc
和free
的最小宽度通常是65537。 - If there is a Format String Vulnerability and the program ends right after calling
printf(buf)
, we can hijack__malloc_hook
or__free_hook
withone-gadget
and use the trick mentioned above to triggermalloc
&free
then we can still get the shell even there is no more function call or sth afterprintf(buf)
.(如果有一个格式字符串漏洞,程序结束后调用printf(buf),我们可以用one-gadget劫持__malloc_hook
或__free_hook
,使用上面提到的技巧来触发malloc
&free
然后我们仍然可以得到shell后没有更多的函数调用printf(buf)
。)
Use execveat to open a shell
提到用系统调用getshell时,我们会想到execve
。但是,由于缺少gadgets或其他的constraints,通常并不是很容易就能办到。
有一个系统调用execveat
,原型如下:
1 | int execveat(int dirfd, const char *pathname, |
According to its man page, it operates in the same way as execve
. As for the additional arguments, it mentions that:
根据其手册得知,它的运作方式与execve相同。至于其他不同点,它提到:
If pathname is absolute, then dirfd is ignored.
如果路径名是绝对的,那么dirfd将被忽略。
Hence, if we make pathname
point to /bin/sh
, and set argv
, envp
and flags
to 0, we can still get a shell whatever the value of dirfd
.
因此,如果我们让pathname
为/bin/sh
,并将argv
、‘envp’和‘flags’设置为0,那么无论dirfd
的值是多少,我们仍然可以getshell。