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 |