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 |