본문 바로가기

Layer7

Layer7 - 리버싱 5차시 과제

 

prob3

 

 

   0x00000000004007f6 <+0>:     push   rbp
   0x00000000004007f7 <+1>:     mov    rbp,rsp
   0x00000000004007fa <+4>:     sub    rsp,0x10
   0x00000000004007fe <+8>:     mov    rax,QWORD PTR fs:0x28
   0x0000000000400807 <+17>:    mov    QWORD PTR [rbp-0x8],rax
   0x000000000040080b <+21>:    xor    eax,eax
   0x000000000040080d <+23>:    mov    edi,0x0
   0x0000000000400812 <+28>:    mov    eax,0x0
   0x0000000000400817 <+33>:    call   0x4005d0 <time@plt>
   0x000000000040081c <+38>:    mov    edi,eax
   0x000000000040081e <+40>:    call   0x4005c0 <srand@plt>
   0x0000000000400823 <+45>:    mov    DWORD PTR [rbp-0xc],0x0
   0x000000000040082a <+52>:    jmp    0x400885 <main+143>
   0x000000000040082c <+54>:    lea    rax,[rbp-0x10]
   0x0000000000400830 <+58>:    mov    rsi,rax
   0x0000000000400833 <+61>:    mov    edi,0x400944
   0x0000000000400838 <+66>:    mov    eax,0x0
   0x000000000040083d <+71>:    call   0x4005f0 <__isoc99_scanf@plt>
   0x0000000000400842 <+76>:    call   0x400600 <rand@plt>
   0x0000000000400847 <+81>:    mov    ecx,eax
   0x0000000000400849 <+83>:    mov    edx,0x66666667
   0x000000000040084e <+88>:    mov    eax,ecx
   0x0000000000400850 <+90>:    imul   edx
   0x0000000000400852 <+92>:    sar    edx,0x2
   0x0000000000400855 <+95>:    mov    eax,ecx
   0x0000000000400857 <+97>:    sar    eax,0x1f
   0x000000000040085a <+100>:   sub    edx,eax
   0x000000000040085c <+102>:   mov    eax,edx
   0x000000000040085e <+104>:   shl    eax,0x2
   0x0000000000400861 <+107>:   add    eax,edx
   0x0000000000400863 <+109>:   add    eax,eax
   0x0000000000400865 <+111>:   sub    ecx,eax
   0x0000000000400867 <+113>:   mov    edx,ecx
   0x0000000000400869 <+115>:   mov    eax,DWORD PTR [rbp-0x10]
   0x000000000040086c <+118>:   cmp    edx,eax
   0x000000000040086e <+120>:   je     0x400881 <main+139>
   0x0000000000400870 <+122>:   mov    edi,0x400947
   0x0000000000400875 <+127>:   call   0x400590 <puts@plt>
   0x000000000040087a <+132>:   mov    eax,0x1
   0x000000000040087f <+137>:   jmp    0x4008a2 <main+172>
   0x0000000000400881 <+139>:   add    DWORD PTR [rbp-0xc],0x1
   0x0000000000400885 <+143>:   cmp    DWORD PTR [rbp-0xc],0x9
   0x0000000000400889 <+147>:   jle    0x40082c <main+54>
   0x000000000040088b <+149>:   mov    eax,0x0
   0x0000000000400890 <+154>:   call   0x400716 <flag_generator>
   0x0000000000400895 <+159>:   mov    rdi,rax
   0x0000000000400898 <+162>:   call   0x400590 <puts@plt>
   0x000000000040089d <+167>:   mov    eax,0x0
   0x00000000004008a2 <+172>:   mov    rsi,QWORD PTR [rbp-0x8]
   0x00000000004008a6 <+176>:   xor    rsi,QWORD PTR fs:0x28
   0x00000000004008af <+185>:   je     0x4008b6 <main+192>
   0x00000000004008b1 <+187>:   call   0x4005a0 <__stack_chk_fail@plt>
   0x00000000004008b6 <+192>:   leave
   0x00000000004008b7 <+193>:   ret

 

srand함수의 인자로 time(NULL)을 전달해서 현재 시간값을 seed로 주는것을 알 수 있다. 그리고 rand함수로 숫자를 생성한 뒤에 특정한 연산을 거쳐서 값을 가공한 후 비교를 하고 있다. 10번 맞으면 flag를 출력해준다. C로 핸드레이 해보면

 

#include <stdio.h>
#include <stdlib.h>

char * flag_generator(){
    long long *s = malloc(0x2e);
    s[0] = 0x30783451465a424f;
    s[1] = 0x33324f46444d3755;
    s[2] = 0x2d3228332d305c4d;
    s[3] = 0x304032514b575c33;
    s[4] = 0x5c375c4d3353565c;
    s[5] = 0x37e304e3257;
    
    for (int i = 0; i <= 0x2d; i++){
        ((char *)s+i)[0] = ((char *)s+i)[0] ^ 0x3;
        if(((char *)s+i)[0]&&((char *)s+i)[0] < 0){
            ((char *)s+i)[0] = !(((char *)s+i)[0]);
        }
    }
    return (char *)s;
}

int main(void){
    srand(time(NULL));
    int eax, ecx, edx, input;
    for(int i = 0; i < 10; i++){
        eax = 0; ecx = 0; edx = 0;
        scanf("%d", &input);
        eax = rand();
        ecx = eax;
        edx = ((long long)((long long)0x66666667*eax)&0xffffffff00000000)>>32;
        edx >>= 2;
        eax >>= 0x1f;
        edx -= eax;
        eax = edx;
        eax <<= 2;
        eax += edx;
        eax *= 2;
        ecx -= eax;
        if(input != ecx){
            puts("Wrong!!");
            return 0;
        }
    }
    puts(flag_generator());

    return 0;
}

 

이렇게 나온다. 일단 seed값을 똑같이 주면 rand함수도 똑같은 값을 생성한다. 따라서 프로그램을 실행하고 time(0)불러서 똑같은 seed로 주고 1초가 지나기 전에 입력을 보내야 하므로 pwntools를 사용했다. for문 안의 로직을 완전히 똑같이 짜서 나온 값을 보낸다면 성공적으로 플래그가 출력될 것이다. 

from ctypes import *
from pwn import *

libc=CDLL("/lib/x86_64-linux-gnu/libc.so.6")
libc.srand(libc.time(0))

r = process("./prob3")

for i in range(10):
    eax = libc.rand()
    ecx = eax
    edx = ((0x66666667*eax)&0xffffffff00000000)>>32
    edx >>= 2
    eax >>= 0x1f
    edx -= eax
    eax = edx
    eax <<= 2
    eax += edx
    eax *= 2
    ecx -= eax
    r.sendline(str(ecx))

r.interactive()

 

그래서 이렇게 짜줬고

 

 

 

 

실제로 잘 동작한다. 근데 서준님이 자꾸 그거 그렇게 하는거 아니라고 놀리는 것이다... 그리고 충격적인 사실을 알게 되었다..

 

 

?????? 아니...... ㅋㅋㅋㅋㅋㅋ 놀린 이유가 이거였구나... 알고보니까 div instruction은 엄청나게 느려서 modulo연산을 저렇게 최적화하는거였다.. 그러니까 저렇게 복잡하게 할필요 없고

 

from ctypes import *
from pwn import *

libc=CDLL("/lib/x86_64-linux-gnu/libc.so.6")
libc.srand(libc.time(0))

r = process("./prob3")

for i in range(10):
    eax = libc.rand()
    r.sendline(str(eax%0xa))

r.interactive()

 

이렇게만 해줘도

 

 

잘 된다......

 

 

핸드레이 한걸로 해봐도 똑같이 잘된다.

 

flag : LAYER7{3V4NGEL10N_3.0+1.0_THR1C3_UP0N_4_T1M3}

 

 

prob4

 

   0x00000000004008ba <+0>:     push   rbp
   0x00000000004008bb <+1>:     mov    rbp,rsp
   0x00000000004008be <+4>:     sub    rsp,0x20
   0x00000000004008c2 <+8>:     mov    rax,QWORD PTR fs:0x28
   0x00000000004008cb <+17>:    mov    QWORD PTR [rbp-0x8],rax
   0x00000000004008cf <+21>:    xor    eax,eax
   0x00000000004008d1 <+23>:    mov    esi,0x400a54
   0x00000000004008d6 <+28>:    mov    edi,0x400a56
   0x00000000004008db <+33>:    call   0x4006c0 <fopen@plt>
   0x00000000004008e0 <+38>:    mov    QWORD PTR [rbp-0x10],rax
   0x00000000004008e4 <+42>:    mov    rdx,QWORD PTR [rbp-0x10]
   0x00000000004008e8 <+46>:    lea    rax,[rbp-0x1c]
   0x00000000004008ec <+50>:    mov    rcx,rdx
   0x00000000004008ef <+53>:    mov    edx,0x1
   0x00000000004008f4 <+58>:    mov    esi,0x4
   0x00000000004008f9 <+63>:    mov    rdi,rax
   0x00000000004008fc <+66>:    call   0x400650 <fread@plt>
   0x0000000000400901 <+71>:    mov    rax,QWORD PTR [rbp-0x10]
   0x0000000000400905 <+75>:    mov    rdi,rax
   0x0000000000400908 <+78>:    call   0x400660 <fclose@plt>
   0x000000000040090d <+83>:    mov    eax,DWORD PTR [rbp-0x1c]
   0x0000000000400910 <+86>:    mov    edi,eax
   0x0000000000400912 <+88>:    call   0x4006a0 <srand@plt>
   0x0000000000400917 <+93>:    mov    DWORD PTR [rbp-0x14],0x0
   0x000000000040091e <+100>:   jmp    0x400990 <main+214>
   0x0000000000400920 <+102>:   mov    eax,DWORD PTR [rbp-0x14]
   0x0000000000400923 <+105>:   add    eax,0x1
   0x0000000000400926 <+108>:   mov    esi,eax
   0x0000000000400928 <+110>:   mov    edi,0x400a63
   0x000000000040092d <+115>:   mov    eax,0x0
   0x0000000000400932 <+120>:   call   0x400680 <printf@plt>
   0x0000000000400937 <+125>:   lea    rax,[rbp-0x18]
   0x000000000040093b <+129>:   mov    rsi,rax
   0x000000000040093e <+132>:   mov    edi,0x400a6d
   0x0000000000400943 <+137>:   mov    eax,0x0
   0x0000000000400948 <+142>:   call   0x4006d0 <__isoc99_scanf@plt>
   0x000000000040094d <+147>:   call   0x4006e0 <rand@plt>
   0x0000000000400952 <+152>:   mov    ecx,eax
   0x0000000000400954 <+154>:   mov    edx,0x66666667
   0x0000000000400959 <+159>:   mov    eax,ecx
   0x000000000040095b <+161>:   imul   edx
   0x000000000040095d <+163>:   sar    edx,0x2
   0x0000000000400960 <+166>:   mov    eax,ecx
   0x0000000000400962 <+168>:   sar    eax,0x1f
   0x0000000000400965 <+171>:   sub    edx,eax
   0x0000000000400967 <+173>:   mov    eax,edx
   0x0000000000400969 <+175>:   shl    eax,0x2
   0x000000000040096c <+178>:   add    eax,edx
   0x000000000040096e <+180>:   add    eax,eax
   0x0000000000400970 <+182>:   sub    ecx,eax
   0x0000000000400972 <+184>:   mov    edx,ecx
   0x0000000000400974 <+186>:   mov    eax,DWORD PTR [rbp-0x18]
   0x0000000000400977 <+189>:   cmp    edx,eax
   0x0000000000400979 <+191>:   je     0x40098c <main+210>
   0x000000000040097b <+193>:   mov    edi,0x400a70
   0x0000000000400980 <+198>:   call   0x400640 <puts@plt>
   0x0000000000400985 <+203>:   mov    eax,0x1
   0x000000000040098a <+208>:   jmp    0x4009b0 <main+246>
   0x000000000040098c <+210>:   add    DWORD PTR [rbp-0x14],0x1
   0x0000000000400990 <+214>:   cmp    DWORD PTR [rbp-0x14],0x270f
   0x0000000000400997 <+221>:   jle    0x400920 <main+102>
   0x0000000000400999 <+223>:   mov    eax,0x0
   0x000000000040099e <+228>:   call   0x4007f6 <flag_generator>
   0x00000000004009a3 <+233>:   mov    rdi,rax
   0x00000000004009a6 <+236>:   call   0x400640 <puts@plt>
   0x00000000004009ab <+241>:   mov    eax,0x0
   0x00000000004009b0 <+246>:   mov    rsi,QWORD PTR [rbp-0x8]
   0x00000000004009b4 <+250>:   xor    rsi,QWORD PTR fs:0x28
   0x00000000004009bd <+259>:   je     0x4009c4 <main+266>
   0x00000000004009bf <+261>:   call   0x400670 <__stack_chk_fail@plt>
   0x00000000004009c4 <+266>:   leave
   0x00000000004009c5 <+267>:   ret

 

먼저 fopen함수의 인자로 들어가는 2개의 인자를 보면

 

 

/dev/urandom을 읽기모드로 연다는 것을 알 수 있다. 그 이후에 fread로 값을 읽어서 버퍼에 저장한 후 srand함수의 인자로 전달하는 것을 알 수 있다. 이후 루틴은 prob3과 비슷하다. C로 핸드레이를 해보면

 

#include <stdio.h>
#include <stdlib.h>

char *flag_generator(){
    long long *s = malloc(0x1e);
    s[0] = 0x8276b2e39253d10;
    s[1] = 0x6c0318126f036f14;
    s[2] = 0x6f1b12680a6f031a;
    s[3] = 0x05c2112136d10;
    char *t;
    for (int i = 0; i < 0x1d; i++){
        t = (char *)s + i;
        t[0] = t[0] ^ 0x5c;
        if(t[0] && t[0] < 0){
            t[0] = !t[0];
        }
    }
    return (char *)s;
}

int main(void){
    FILE *f = fopen("/dev/urandom", "r");
    char buf[4];
    fread(buf, 4, 1, f);
    srand(*(int *)buf);
    int eax, ecx, edx, input;
    for(int i = 0; i < 10; i++){
        eax = 0; ecx = 0; edx = 0;
        scanf("%d", &input);
        eax = rand();
        ecx = eax;
        edx = ((long long)((long long)0x66666667*eax)&0xffffffff00000000)>>32;
        edx >>= 2;
        eax >>= 0x1f;
        edx -= eax;
        eax = edx;
        eax <<= 2;
        eax += edx;
        eax *= 2;
        ecx -= eax;
        if(input != ecx){
            puts("Wrong!!");
            return 0;
        }
    }
    puts(flag_generator());

    return 0;
}

 

이렇게 된다. 근데 /dev/urandom은 진짜 완벽에 가까운 난수 값이다. 일반적인 방법으론 추측할 수 없다. 그래서 어떻게 익스할지 고민을 좀 하다가 이 과제가 전 차시의 과제와 다르게 익스짜는 과제가 아니라 그냥 핸드레이 하고 플래그 얻는것임을 깨달았다. 그래서 그냥 flag_generator함수 핸드레이한거 실행시켜줬다.

 

#include <stdio.h>
#include <stdlib.h>

char *flag_generator(){
    long long *s = malloc(0x1e);
    s[0] = 0x8276b2e39253d10;
    s[1] = 0x6c0318126f036f14;
    s[2] = 0x6f1b12680a6f031a;
    s[3] = 0x5c2112136d10;
    char *t;
    for (int i = 0; i <= 0x1d; i++){
        t = (char *)s + i;
        t[0] = t[0] ^ 0x5c;
        if(t[0] && t[0] < 0){
            t[0] = !t[0];
        }
    }
    return (char *)s;
}

int main(void){
    puts(flag_generator());
    /*FILE *f = fopen("/dev/urandom", "r");
    char buf[4];
    fread(buf, 4, 1, f);
    srand(*(int *)buf);
    int eax, ecx, edx, input;
    for(int i = 0; i < 10; i++){
        eax = 0; ecx = 0; edx = 0;
        scanf("%d", &input);
        eax = rand();
        ecx = eax;
        edx = ((long long)((long long)0x66666667*eax)&0xffffffff00000000)>>32;
        edx >>= 2;
        eax >>= 0x1f;
        edx -= eax;
        eax = edx;
        eax <<= 2;
        eax += edx;
        eax *= 2;
        ecx -= eax;
        if(input != ecx){
            puts("Wrong!!");
            return 0;
        }
    }
    puts(flag_generator());*/

    return 0;
}

 

 

아니면 gdb의 jump명령어로 flag_generator함수 불러줘도 된다.

 

 

flag : Layer7{TH3_3ND_0F_3V4NG3L1ON}

 

 

prob5

 

 

 

main은 간단하다. read함수로 rbp-0xd0에 입력받고 calculator함수 불러준다. 그리고 rbp-0xd0과 0x601080의 메모리 값을 0x1f만큼 비교해줘서 일치할 경우에는 맞았다 출력해주고 아닐 경우에는 틀렸다 출력해준다.

 

 

그러면 여기 있는 값들은 calculator함수로 인해 플래그가 인코딩 된 값이라고 추측할 수 있다. 즉 디코딩 함수를 작성해서 이 값들을 원본 값으로 돌리면 그게 플래그가 되는것이다.

 

   0x00000000004006b6 <+0>:     push   rbp
   0x00000000004006b7 <+1>:     mov    rbp,rsp
   0x00000000004006ba <+4>:     sub    rsp,0x20
   0x00000000004006be <+8>:     mov    QWORD PTR [rbp-0x18],rdi
   0x00000000004006c2 <+12>:    mov    rax,QWORD PTR [rbp-0x18]
   0x00000000004006c6 <+16>:    mov    rdi,rax
   0x00000000004006c9 <+19>:    call   0x400560 <strlen@plt>
   0x00000000004006ce <+24>:    cmp    rax,0x1e
   0x00000000004006d2 <+28>:    ja     0x4006e8 <calculator+50>
   0x00000000004006d4 <+30>:    mov    edi,0x400878
   0x00000000004006d9 <+35>:    call   0x400550 <puts@plt>
   0x00000000004006de <+40>:    mov    edi,0x0
   0x00000000004006e3 <+45>:    call   0x4005a0 <exit@plt>
   0x00000000004006e8 <+50>:    mov    DWORD PTR [rbp-0x4],0x0
   0x00000000004006ef <+57>:    jmp    0x40074c <calculator+150>
   0x00000000004006f1 <+59>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004006f4 <+62>:    movsxd rdx,eax
   0x00000000004006f7 <+65>:    mov    rax,QWORD PTR [rbp-0x18]
   0x00000000004006fb <+69>:    add    rax,rdx
   0x00000000004006fe <+72>:    mov    edx,DWORD PTR [rbp-0x4]
   0x0000000000400701 <+75>:    movsxd rcx,edx
   0x0000000000400704 <+78>:    mov    rdx,QWORD PTR [rbp-0x18]
   0x0000000000400708 <+82>:    add    rdx,rcx
   0x000000000040070b <+85>:    movzx  edx,BYTE PTR [rdx]
   0x000000000040070e <+88>:    xor    edx,0xffffffaf
   0x0000000000400711 <+91>:    mov    BYTE PTR [rax],dl
   0x0000000000400713 <+93>:    mov    eax,DWORD PTR [rbp-0x4]
   0x0000000000400716 <+96>:    movsxd rdx,eax
   0x0000000000400719 <+99>:    mov    rax,QWORD PTR [rbp-0x18]
   0x000000000040071d <+103>:   add    rax,rdx
   0x0000000000400720 <+106>:   movzx  eax,BYTE PTR [rax]
   0x0000000000400723 <+109>:   test   al,al
   0x0000000000400725 <+111>:   jns    0x400748 <calculator+146>
   0x0000000000400727 <+113>:   mov    eax,DWORD PTR [rbp-0x4]
   0x000000000040072a <+116>:   movsxd rdx,eax
   0x000000000040072d <+119>:   mov    rax,QWORD PTR [rbp-0x18]
   0x0000000000400731 <+123>:   add    rax,rdx
   0x0000000000400734 <+126>:   mov    edx,DWORD PTR [rbp-0x4]
   0x0000000000400737 <+129>:   movsxd rcx,edx
   0x000000000040073a <+132>:   mov    rdx,QWORD PTR [rbp-0x18]
   0x000000000040073e <+136>:   add    rdx,rcx
   0x0000000000400741 <+139>:   movzx  edx,BYTE PTR [rdx]
   0x0000000000400744 <+142>:   neg    edx
   0x0000000000400746 <+144>:   mov    BYTE PTR [rax],dl
   0x0000000000400748 <+146>:   add    DWORD PTR [rbp-0x4],0x1
   0x000000000040074c <+150>:   cmp    DWORD PTR [rbp-0x4],0x1e
   0x0000000000400750 <+154>:   jle    0x4006f1 <calculator+59>
   0x0000000000400752 <+156>:   nop
   0x0000000000400753 <+157>:   leave
   0x0000000000400754 <+158>:   ret

 

핵심이 되는 calculator함수를 보면 분량이 되게 많다. 일단 flag의 길이는 0x1e여야 하고 그 뒤로 특정한 연산들을 수행해준다. cmp와 jle가 아래에 있으므로 반복문 같아 보인다. 어셈만 보면 상당히 와닫지 않으므로 하나하나 핸드레이 하면서 C언어로 옮겨줬다.

 

 

이런 식으로 for문의 인덱스를 가져온 후에 버퍼 주소에 더해주는 패턴은 buf[i]이다. 상당히 자주 보이는 패턴이니 이정도는 기억해두면 좋다. 사실 핸드레이는 그냥 어셈 많이 볼수록 실력이 는다.

 

buf[i] = ((int)buf[i]^(-81))&0xff;
if(buf[i] < 0) buf[i] = -buf[i];

 

반복문 안에서 돌아가는 핵심 루틴은 이거다. 어셈만 봐서는 감이 잘 안오고 과제 마감까지 시간도 별로 없기도 해서 동적분석 한번 해주면서 한번에 흐름 파악해줬다.

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

long long flag[4] = {0x1f2c6803160a121d, 0x641f6418101f6164, 0x1f65076410666266, 0x002e1f61621d6418};

void calculator(char *buf){
    if(strlen(buf) <= 0x1e){
        puts("Wrong input!!!");
        exit(0);
    }
    for(int i = 0; i <= 0x1e; i++){
        buf[i] = ((int)buf[i]^(-81))&0xff;
        if(buf[i] < 0) buf[i] = -buf[i];
    }
}

int main(void){
    char buf[0xc8];
    read(0, buf, 0xc8);
    calculator(buf);
    if(!strncmp(buf, (char *)flag, 0x1f))
        puts("Congratulations. You solved the problem. The input is a flag.");
    else
        puts("Wrong flag!!!!");

    return 0;
}

 

전체를 핸드레이 한 결과물이다. 이제 저 calculator함수를 역연산 짜주면 되는데 방법은 간단하다. 그냥 역으로 거슬러 올라가면 되는데 문제는 xor결과가 음수일 경우에 전부 양수로 바꿔주니까 어떤 값은 처음부터 양수이고 어떤 값은 음수였다가 양수가 된다는 것이다. 어...라...? 논리적으로 역산을 못짠다. brute force를 해야되나..? 하지만 모든 경우의 수를 고려할 경우에 음수, 양수를 배정할 수 있는 경우의 수를 구하려면 1 <= r <= n에서 r에 들어갈 수 있는 모든 수에 대한 순열을 돌아야 하니까 엄청나게 오래걸릴것 같았다. 사실 1byte씩 인코딩 하는거라서 역연산 함수 없이도 0~255를 하나씩 인코딩 해보면서 1byte씩 인코딩 결과 비교하는 식으로 brute force해서 풀어도 된다. 그런데 조금만 더 생각을 해보면 인풋값은 ascii일 것이니까 항상 양수이고 양수에 음수를 xor하면 양수의 부호비트는 0이고 음수의 부호비트는 1이므로 항상 음수가 된다. 따라서 if문이 항상 실행된다고 볼 수 있다. 위 사실에 근거하여 역산을 짜보면

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void decrypt(){
    long long flag1[4] = {0x1f2c6803160a121d, 0x641f6418101f6164, 0x1f65076410666266, 0x002e1f61621d6418};
    char *flag = flag1;
    for(int i = 0; i <= 0x1e; i++){
        flag[i] *= -1;
        flag[i] = ((int)flag[i]^(-81))&0xff;
    }
    puts(flag);
}

int main(void){
    decrypt();

    return 0;
}

 

이렇게 decrypt함수를 만들 수 있고

 

 

플래그가 잘 나온다

 

flag : LAYER7{N30N_G3N3515_3V4NG3L10N}

'Layer7' 카테고리의 다른 글

Layer7 - 리버싱 7차시 과제 rev-basic-2  (0) 2022.10.04
Layer7 - 리버싱 6차시 과제  (0) 2022.10.04
Layer7 - 리버싱 4차시 과제  (0) 2022.10.04
Layer7 - 리버싱 3차시 과제  (1) 2022.10.04
Layer7 - 리버싱 2차시 과제  (0) 2022.10.04