Layer7 동아리 과제

리버싱 5차시 과제

msh1307 2022. 7. 31. 17:55

imul

- imul은 Signed Multiply다.

- 여기선 IMUL r/m32에 해당되니 그것만 보면,

- register/memory 32비트, 즉 4바이트짜리끼리 연산할 때 EAX * r/m doubleword(4바이트)를 EDX:EAX로 넣는다. 

- EDX:EAX notation은 상위 4바이트를 EDX에, EAX에 하위 4바이트를 저장한다는 의미다. 

 

sar, sal, shr, shl

- sar은 shift arithmetic right이다.

- sal은 shift arithmetic left이다.

- shr은 shift right이다. logical shift를 말한다.

- shl은 shift left이다. logical shift를 말한다.

 

- arithmetic shift는 기본적으로 음수를 shift할때도 음수 값이 잘 유지되도록 shift 해주는 것을 의미하고, logical shift는 그냥 밀어버리고 0으로 채우는 것이다.

 

- shr과 sar는 다른 동작을 하지만 shl과 sal은 동일한 동작을 한다. 

 

- 왼쪽으로 shift하면서 굳이 sign bit을 신경 쓰지 않아도 되기 때문이다. 

- signed value에 대한 left shift를 하다가 sign bit를 건들게 되는 것은 undefined behavior에 해당한다.

 

- C에서는 기본적으로 unsigned value를 shift할때shift 할 때 logical shift를 하고, signed value를 shift 할 때 arithmetic shift를 한다.

 

prob3

time(NULL)의 리턴값을 srand에 시드로 넣고 초기화해준다.

그다음 jmp로 루프에 들어간다. 

0x0부터 0x9까지 돌면서 rand 리턴 값에 어떤 처리를 해주고 사용자의 입력을 받아서 비교한다.

 

루프를 다 돌면 main+149로 그대로 rip가 간다.

flag_generator의 리턴값을 출력한다.

flag_generator의 disassembly다.

 

main+118에서 사용자의 입력과 rand 리턴 값을 암호화하고 숫자를 비교하는데, 거기서 틀리면 puts@plt를 호출한다.

입력을 아무렇게나 줘서 틀리면 puts@plt를 호출하는데 그때 string은 Wrong!!이다.

 

time(NULL)에 breakpoint를 걸고 그때 리턴 값을 확인하고 나서 코드를 짜서 main+118에서 비교하는 edx를 알아내거나 jump 하면 된다.

jump는 날먹이라서 사서 고생을 해봤다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
int in(int ecx){
    int mul = 0x66666667;
    long long res = (long long)mul * ecx;
    int eax = *((int *)&res);
    int edx = *((int *)&res+1);
    edx = edx >> 0x2;
    eax = ecx >> 0x1f;
    edx -= eax;
    eax = edx << 0x2
    eax += edx;
    eax += eax;
    ecx -= eax;
    return ecx;
}
 
int main(){
    time_t t; 
    printf("time : ");
    scanf("%ld",&t);
    srand(t);
    for (int i=0; i<=0x9;i++){
        printf("%d ",in(rand()));
    }
    return 0;
}
 
cs
그대로 코드를 짰다.

아무거나 __time_t 들어가게 만들어서 gcc에 --save-temps 옵션을 줘서 .i파일을 보면 __time_t는 long int으로 정의되어있다. 

scanf로 받을때 %ld로 주면 된다.

bp 걸어서 eax를 확인할 것이다.

p/d로 decimal로 eax를 읽었다.

time(NULL)의 리턴값이랑 같으니까 입력으로 주면 된다.

이 값들을 넣어주면 된다.

순서대로 넣어주면 나온다.

 

C로 핸드레이 해봤다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
 
char * flag_generator(){
    void * ptr = malloc(0x2e);
    *(long long * )ptr = 0x30783451465a424f//0x0
    *((long long * )ptr+1= 0x33324f46444d3755//0x8
    *((long long * )ptr+2= 0x2d3228332d305c4d//0x10
    *((long long * )ptr+3= 0x304032514b575c33//0x18
    *((long long *)ptr+4= 0x5c375c4d3353565c;  //0x20
    *((int *)ptr+10= 0x304e3257//0x28
    *((short *)ptr+21= 0x37e;
    *((char *)ptr+46= 0x00;
    
    for (int i=0;i<=0x2d;i++){
        ((char *)ptr+i)[0= ((char *)ptr+i)[0] ^ 0x3;
        if(((char *)ptr+i)[0]&&((char *)ptr+i)[0< 0){
            ((char *)ptr+i)[0= !(((char *)ptr+i)[0]);
        }
    }
    return (char *)ptr;
}
int main(){
    time_t t = time(NULL);
    int in,ran,mul,eax,edx,ecx;
    long long res;
    srand(t);
    for (int i=0;i<=0x9;i++){
        scanf("%d",&in);
        ecx = rand();
        mul = 0x66666667;
        res = (long long)mul * ecx;
        eax = *((int *)&res);
        edx = *((int *)&res+1);
        edx = edx >> 0x2;
        eax = ecx >> 0x1f;
        edx -= eax;
        eax = edx << 0x2
        eax += edx;
        eax += eax;
        ecx -= eax;
        if(ecx == in){
            puts(flag_generator());
        }
        else {
            puts("Wrong!!\n");
            return 1;
        }
    }
    return 0;
 
}
 
cs

 

prob4

main 함수의 disassembly다.

 

무언가를 fopen으로 열고 fread로 읽는다.

fopen은 file pointer를 리턴하고, 그걸 받아서 fread가 읽는다.

rbp-0x1c에 저장된다.

그다음 fclose로 닫는다. 

 

rbp-0x1c에 저장된 것을 srand에 넣는다.

그다음 main+214로 jmp 한다. 

 

0x270f랑 비교해서 작거나 같으면 main+102로 점프한다.

 

main+102에서는 rbp-0x14를 1 증가시킨다.

rbp-0x14가 카운팅 변수 역할을 해준다.

그다음 printf가 0x400a63을 출력한다.

scanf로 입력을 받고 rand를 호출한다.

그다음 rand의 리턴값을 받아서 암호화한다.

main+189에서 사용자의 입력과 변형된 값이 들어간 edx를 비교한다.

 

맞으면 main+210으로 jmp 한다.

거기서 조건 비교하고 루프를 빠져나오면 flag_generator 리턴 값이 출력된다.

 

틀리면 puts로 출력하고 eax를 1로 세팅하고 main+246으로 jmp 한다.

초반 fopen, fread, fclose, srand에 bp를 걸었다.

나머지 printf나 puts는 신경 안 써도 될 것 같아서 안 걸었다.

/dev/urandom을 연다.

urandom이 보인다.

읽었더니 이상한 값들이 끝도 없이 나온다.

찾아보니 /dev/urandom은 리눅스에서 유사 난수 발생기의 역할을 수행하는 특수 파일이라고 한다.

continue로 다음 bp로 이동하면 fread가 보인다.

1
2
3
4
5
6
size_t fread(
   void *buffer,
   size_t size,
   size_t count,
   FILE *stream
);
cs

rdi가 rbp-0x1c이다.

rsi가 size고

rdx가 count다.

rcx가 _IO_FILE을 가리키는 file pointer다.

 

다음 bp로 이동하면 file pointer를 닫는 것을 볼 수 있다.

srand에 들어간 시드 값은 0x922e08ea로 4바이트가 들어간다.

그냥 breakpoint 걸어놓고 jump * flag_generator 하고 리턴 값을 보면 된다.

flag_generator의 disassembly다.

 

보면 main+188에서 루프 조건문이 있는 것을 볼 수 있다.

flag_generator+190에 bp를 건다.

이때 rbp-0x8을 보면 flag가 있다.

아까 핸드레이보다 더 다듬어서 C로 핸드레이를 해보았다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include<stdio.h>
#include<stdlib.h>
 
char * flag_generator(){
    long long * ptr = malloc(0x1e);
    ptr[0= 0x8276b2e39253d10//0x0
    ptr[1= 0x6c0318126f036f14//0x8
    ptr[2= 0x6f1b12680a6f031a//0x10
    ptr[3= 0x05c2112136d10;
    char * a;
    for (int i=0;i<0x1d;i++){
        a = (char *)ptr + i;
        a[0= a[0] ^ 0x5c;
        if(a[0&& a[0< 0){
            a[0= !a[0];
        }
    }
    return (char *)ptr;
}
int main(){
    char buf[4];
    int input, ran,res;
    FILE * ptr = fopen("/dev/urandom","r");
    fread(buf, 4,1,ptr);
    fclose(ptr);
    srand(*(int *)buf);
    for (int i=0;i<0x270f;i++){
        printf("step %d: ",i+1);
        scanf("%d",&input);
        ran = rand();
        res = ran * 0x66666667;
        res = ((*((int *)&(res)+1)>>0x2)-(ran>>0x1f));
        res = res + res<<0x2;
        res+= res;
        res = ran - res;
        
        if(res != input){
            puts("Wrong!!");
            return 1;
        }
    }
    puts(flag_generator());
 
    return 0;
}
 
cs

 

 

 

prob5

main disassembly다.

 

read가 stdin(0)에서 입력을 받고 rbp-0xd0에 저장한다. 

그리고 calculator라는 함수에 인자로 넘겨준다. 

1
int strncmp(const char *str1, const char *str2, size_t n)
cs

edx에 0x20이 들어가니 rbp-0xd0과 0x601080을 0x20만큼 비교하는 것을 알 수 있다.

그리고 리턴 값이 0이 아니면 main+107로 점프하고 puts로 무언가를 출력하고 꺼진다.

 

리턴 값이 0이면 test에서 ZF가 set되니 puts로 무언가를 출력하고 main+117로 점프하고 꺼진다.

calculator의 리턴값이 0이 되면 Congratulations. You solved the problem. The input is a flag. 를 출력한다.

 

그리고 strncmp에서 저 0x601080와 비교를 해준다.

calculator disassembly다.

유저 인풋 스트링이 0x601080과 같아지게 하면 되는 것 같다.

 

유저 인풋 스트링 주소는 rbp-0x18에 QWORD로 저장된다.

스트링의 길이를 strlen을 통해서 구한다.

그리고 스트링 길이가 0x1f가 넘어가면 조건문이 통과된다.

아까 main에서 0x20만큼 strncmp를 했었으니 널 문자 제외 0x20이 되도록 검증해주는 것 같다.

 

조건문 틀리면 

이걸 출력하고 exit 한다.

 

맞으면 rbp-0x4에 0x0 4바이트 넣고 calculator+150으로 점프한다.

0x1f랑 비교해서 rbp-0x4가 작거나 같으면 calculator+59로 점프해서 0x0 ~ 0x1f까지 총 0x20만큼 루프를 돌도록 한다.

루프를 돌면서 입력한 스트링이 잘 바뀐다. 

 

0x601080를 아까 calculator에서 해주는 변형을 거꾸로 해주면 flag를 얻을 수 있다.

 

암호화할 때 xor 하는 키 값에 따라서 양수 음수가 갈린다. 

여기서 xor 하는 키를 보면 sign bit이 1이다. 

sign bit이 1이니 키 값 1바이트가 양수면 sign bit이 1이 돼서 음수가 된다.

만약 키값 1바이트가 음수면 sign bit이 0이 되서 양수가 된다.

읽을 수 있는 1byte char는 모두 sign bit이 0인 양수다.

그래서 xor 하면 항상 sign bit는 1이 될 수밖에 없고 그래서 jns가 Not taken 된다.

역 연산할 때 neg를 해주고 xor을 다시 해주면 flag를 얻을 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
#include<stdlib.h>
int main(){
    long long * ptr = malloc(0x20); //neg taken
    ptr[0= 0x1f2c6803160a121d;
    ptr[1= 0x641f6418101f6164;
    ptr[2= 0x1f65076410666266;
    ptr[3= 0x512e1f61621d6418;
    for (int i=0;i<=0x1f;i++){
        ((char *)ptr+i)[0]= -(((char *)ptr+i)[0]); //neg 
        ((char *)ptr+i)[0= (((char *)ptr+i)[0]) ^ 0xaf;
    }
    printf("%s",(char *)ptr);
    free(ptr);
    return 0;
}
 
cs

이렇게 해주고 돌리면 된다.

C로 핸드레이 해보면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
void calculator(char * buf){
    if(strlen(buf) > 0x1e){
        for(int i=0;i<=0x1e;i++){
            (buf+i)[0= (buf+i)[0] ^ 0xaf;
            if((buf+i)[0< 0){
                (buf+i)[0= -(buf+i)[0];
            }
        }
    }
    else {
        puts("Wrong input!!!");
        exit(0);
    }
}
 
char flag[0x20];
 
int main(){
    ((long long *)flag)[0= 0x1f2c6803160a121d;
    ((long long *)flag)[1= 0x641f6418101f6164;
    ((long long *)flag)[2= 0x1f65076410666266;
    ((long long *)flag)[3= 0x002e1f61621d6418;
    char buf[0xc8];
    read(0,buf,0xc8);
    calculator(buf);
    if(strncmp(buf, flag, 0x1f== 0){
        puts("Congratulations. You solved the problem. The input is a flag.");
    }
    else { 
        puts("Wrong flag!!!!");
    }
    return 0;
}
 
cs

많이 다듬어졌다.

'Layer7 동아리 과제' 카테고리의 다른 글

리버싱 7차시 과제  (0) 2022.08.08
리버싱 6차시 과제  (0) 2022.08.02
리버싱 4차시 과제  (0) 2022.07.26
리버싱 3차시 과제  (0) 2022.07.23
리버싱 2차시 과제  (0) 2022.07.20