CTF Challenge - ARM Basic Crackme

說明

這是一個(gè)arm32的程序,ELF ARM - Basic Crackme

你可以直接從這下載程序


分析

看向流程圖,第一個(gè)塊我一開始以為沒什么好看的,后來越發(fā)覺得這里面做了很多工作,忽略這個(gè)塊就會(huì)搞得你看后面的一頭霧水。

就是獲取argc的長度,看是不是小于2,小于2說明沒有輸入password,直接puts信息提示輸入。

一行一行的分析,這里我會(huì)用到畫圖的方式來更好的理解堆棧

1、首先是往棧中放些東西,注意這里用的不是push,因?yàn)閜ush指令只在thumb模式中有,而這個(gè)程序是arm模式的;r11是棧幀指針,lr是鏈接寄存器,具體可以看我之前的文章 ARM數(shù)據(jù)類型;這條指令首先會(huì)減少sp的值,也就是擴(kuò)大堆棧的空間,然后將r4,r11,r14寄存器以從右往左的順序放到分配好的棧里(LR -> R11(FP) -> R4)

STMFD   SP!, {R4,R11,LR}

堆棧情況如圖


2、這條指令就是改變了下fp的位置,即指向剛剛放入堆棧的LR寄存器(記住一個(gè)寄存器32位,也就是32/8=4個(gè)字節(jié))

ADD     R11, SP, #8


3、我們都知道arm有個(gè)函數(shù)調(diào)用約定 (ARM數(shù)據(jù)類型),就是r0-r3用作函數(shù)的前4個(gè)參數(shù),這里用到的是r0和r1;在arm中,有30個(gè)32位的通用寄存器,這意味著每個(gè)寄存器的大小都是4個(gè)字節(jié);
這條指令的主要作用是分配28個(gè)字節(jié)的棧空間來存放局部變量

SUB     SP, SP, #0x1C

所以此時(shí)的堆棧應(yīng)該是這樣的


4、分配了??臻g,那肯定要放東西了;這條指令就是在 r11 + (-0x20) 也就是 0xbefff13c + (-0x20) = 0xbefff11c 處存放參數(shù)1,這個(gè)地址是在sp指針的下面那個(gè)棧

STR     R0, [R11,#var_20]

(因?yàn)檫@里改變不大,我就懶得上堆棧圖了,結(jié)合下面的幾條指令一起上圖)
這條指令是把R1(參數(shù)2)存放到 r11 + (-0x24) = 0xbefff118 也就是棧頂SP處

STR     R1, [R11,#var_24]

然后分別存放0x6、0x0到0xbefff12c、0xbefff128處

MOV     R3, #6
STR     R3, [R11,#var_10]
MOV     R3, #0
STR     R3, [R11,#var_14]

假設(shè)我們什么也沒輸入,此時(shí)R0=1,如果要問為什么等于1,那你需要去補(bǔ)充下C語言的知識(shí),因?yàn)閙ain方法的第一個(gè)參數(shù)是第二參數(shù)的長度,第二個(gè)參數(shù)是從命令行接收的用戶輸入;結(jié)合上面的多條指令,所以此時(shí)堆棧應(yīng)該長這樣



5、接著就是取出參數(shù)1,然后比較是否等于2,如果等于就跳轉(zhuǎn)到0x84B0,否則就輸入loser...,然后exit 并傳個(gè)1

LDR     R3, [R11,#var_20]
CMP     R3, #2
BEQ     loc_84B0

直接看true執(zhí)行的塊

來一行一行分析

首先是從fp+0x24處獲取數(shù)據(jù)到r3寄存器,這個(gè)地址存的是參數(shù)2,自己看上面的代碼就知道了

LDR     R3, [R11,#var_24]

然后是從fp+0x24+0x4處獲取數(shù)據(jù)到r3寄存器,這個(gè)地址是我們?cè)诿钚羞\(yùn)行該程序輸入的內(nèi)容;因?yàn)閞3的地址就是參數(shù)2,再因?yàn)檫@是32位的程序,char*的大小為4個(gè)字節(jié),所以偏移量是+0x4

LDR     R3, [R3,#4]

接著看,這行的意思是把r3又存到 fp+0x18 地址處

STR     R3, [R11,#s]

然后是加載格式化字符串,放在r3寄存器

LDR     R3, =aCheckingSForPa

然后又移動(dòng)到r0寄存器來用作printf的第一個(gè)參數(shù)

MOV     R0, R3

接著取出輸入的內(nèi)容,放在r1寄存器用作printf的第二參數(shù)

LDR     R1, [R11,#s]

然后就是調(diào)用printf

BL      printf

接著又從 fp+0x18 取出輸入內(nèi)容,因?yàn)閯倓傉{(diào)用了printf

LDR     R0, [R11,#s]

然后調(diào)用strlen,參數(shù)是r0,獲取輸入內(nèi)容的長度并把長度放在r3寄存器,記住r0在每個(gè)方法執(zhí)行結(jié)束后就是該方法的返回值

BL      strlen
MOV     R3, R0

然后把長度r3存到 fp+0x1c 地址,接著又取出來放在r3寄存器。。。

STR     R3, [R11,#status]
LDR     R3, [R11,#status]

然后判斷長度是否小于6,如果小于就跳轉(zhuǎn)執(zhí)行puts輸出"Loser..." (受到了作者的嘲諷

CMP     R3, #6
BEQ     loc_84F8

接著看 fasle 塊

還是一行一行分析

我們知道r11至始至終都沒變過,所以這里還是取 0xbefff13c+(-0x10)=0xbefff12c 處的數(shù)據(jù),這個(gè)地址在第一個(gè)塊也就是我們最開始分析的時(shí)候存過一個(gè)數(shù)據(jù),那就是0x6,方便更好的理解,我把上面的堆棧圖再上一次

LDR     R4, [R11,#var_10]

然后又是獲取輸入的字符串,并獲取它的長度放在R3

LDR     R0, [R11,#s] 
BL      strlen
MOV     R3, R0

緊接著這里有個(gè)反轉(zhuǎn)減法運(yùn)算操作,反轉(zhuǎn)減法就是把兩個(gè)要算的數(shù)調(diào)換位置算,比如這條指令就是 r3 = r4 - r3,這里開始我們需要仔細(xì)分析,因?yàn)檫@里到了算法部分,這的反轉(zhuǎn)減法是用 6 - 字符串長度

RSB     R3, R3, R4

接著把運(yùn)算結(jié)果存到剛剛?cè)〕?x6的地址

STR     R3, [R11,#var_10]

然后又把輸入的字符串取出來,我先在這說明一下,這里 r11+#s 的地址的數(shù)據(jù)還是一個(gè)地址,在這個(gè)地址中的數(shù)據(jù)才是輸入的字符串,我也覺得繞,所以我有個(gè)辦法能幫助我們更好理解

LDR     R3, [R11,#s]

首先寫個(gè)仿照這部分簡單寫個(gè)程序,關(guān)于字符串定義啥玩意兒的可以看這里String definition directives

.global main               ;程序入口
main:                      
0x00000000    MOV        R1,#0xa         ; E3A0100A
0x00000004    MOV        R2,#5           ; E30A2005
0x00000008    ADD        R3,R1,R2        ; E0813002
0x0000000c    LDR        R3,=pass        ; E59F3008
0x00000010    LDRB       R2,[R3]         ; E5D32000
0x00000014    LDR        R3,=pass        ; E51F3000
0x00000018    ADD        R3,R3,#5        ; E2833005
0x0000001c    LDRB       R3, [R3]        ; E5D33000

.text
0x00000001c   pass: .asciz "122923"      ; 39323231

編譯后用十六進(jìn)制打開差不多是這樣,我這里只獲取了代碼和字符串部分,其他的比如格式啥的就不看了;這里字符串存儲(chǔ)用的小端序。


當(dāng)程序運(yùn)行,pc會(huì)指向0x00000000,也就是第一條指令 mov r1,#10 的位置,然后直到0xc處,這里其實(shí)寫成這樣 ldr r3, [pc, #16],就是當(dāng)pc指向這條指令,那么就直接用pc的地址偏移16個(gè)字節(jié),直接到字符串的地址,這里的r3的內(nèi)容就是0x00000001c;接著就是ldrb,這個(gè)指令和ldr的最大區(qū)別就是它只獲取8個(gè)字節(jié)的數(shù)據(jù),比如這里就是獲取的r3的地址所指向的字符串的8個(gè)字節(jié)數(shù)據(jù),也就是0x31;然后這里的ldr可以這樣寫 ldr r3, [pc,#8],接著就是r3在原地址上偏移0x5,然后再用ldrb獲取8個(gè)字節(jié)的數(shù)據(jù),那就是最后一個(gè)字符 “3” (0x33)

回到crackme,接下來是ldrb指令,這里就是獲取第一個(gè)字符

LDRB    R2, [R3]

然后是獲取最后一個(gè)字符

LDR     R3, [R11,#s]
ADD     R3, R3, #5
LDRB    R3, [R3]

接著就是比較,用cmp來使兩個(gè)字符的ascii碼相減,根據(jù)結(jié)果改變cspr flag,beq是根據(jù)z flag來決定是否跳轉(zhuǎn)的,z flag的條件是兩個(gè)數(shù)相減為0時(shí)置1,也就是相等就跳轉(zhuǎn)

CMP     R2, R3
BEQ     loc_8538

來看看如果相等會(huì)怎樣;首先從 r11+(-10) 地址處取數(shù)據(jù),這個(gè)地址存的是之前 6-字符串長度 的結(jié)果,然后自增1,接著存到原始地址;所以這里也就是自增1的作用

LDR     R3, [R11,#var_10]
ADD     R3, R3, #1
STR     R3, [R11,#var_10]

從下圖可以看出每個(gè)塊的最后都有一次比較來看,這里應(yīng)該是多個(gè)if else,然后再自增一個(gè)int變量,暫時(shí)不清楚這個(gè)變量有什么用。



直接看下一個(gè)判斷塊

LDR     R3, [R11,#s]  ;取字符串
LDRB    R3, [R3]      ;獲取第一個(gè)字符
ADD     R2, R3, #1    ;將字符ascii碼加1
LDR     R3, [R11,#s]  ;取字符串
ADD     R3, R3, #1    ;將字符串地址偏移0x1;str: 0x0  34 33 32 31,r3最開始在0x0,偏移一個(gè)字節(jié)就到了0x1,也就是33
LDRB    R3, [R3]      ;從偏移后的地址處取第一個(gè)字符
CMP     R2, R3        ;然后用第一個(gè)字符的ascii+1與第二個(gè)字符的ascii比較
BEQ     loc_8564      ;如果相等就自增

然后看第三個(gè)判斷塊

LDR     R3, [R11,#s]  ;取字符串
ADD     R3, R3, #3    ;字符串地址偏移3個(gè)字節(jié),可以寫成r3[3]
LDRB    R3, [R3]      ;取偏移后的第一個(gè)字符
ADD     R2, R3, #1    ;字符ascii+1
LDR     R3, [R11,#s]  ;取字符串
LDRB    R3, [R3]      ;取第一個(gè)字符
CMP     R2, R3        ;用字符的第四個(gè)字符ascii與第一個(gè)字符ascii比較
BEQ     loc_8590      ;相等就自增

第四個(gè)判斷塊

LDR     R3, [R11,#s]  ;取字符串
ADD     R3, R3, #2    ;將字符串地址便宜2個(gè)字節(jié)
LDRB    R3, [R3]      ;取偏移后第一個(gè)字符
ADD     R2, R3, #4    ;字符ascii+4
LDR     R3, [R11,#s]  ;取字符
ADD     R3, R3, #5    ;字符串偏移0x5個(gè)字節(jié),就到了`輸入的`最后一個(gè)字符,要知道字符串最后一個(gè)字符是`\0`
LDRB    R3, [R3]      ;取偏移后第一個(gè)字符
CMP     R2, R3        ;用輸入的字符串中的第三個(gè)字符的ascii+4與輸入的字符串中的最后一個(gè)字符的ascii比較
BEQ     loc_85C0      ;相等自增

if else塊到這就完了,根據(jù)上面的邏輯我寫了個(gè)c代碼

#include <stdio.h>
#include <string.h>

int main(int args, char **argv)
{
    int len = 0;
    char *pass = argv[1];
 
    if (args != 2) {
        puts("Loser...");
        exit(1);
    }
    printf("Checking %s for password...\n", pass);
    len = strlen(pass)
    if (len < tmp) {
        puts("Loser...");
        exit(len);
    }

    len = 6 - len;
    if (pass[0] != pass[5]) len++;
    if (pass[0]+1 != pass[1]) len++;
    if (pass[3]+1 != pass[0]) len++;
    if (pass[2]+4 != pass[5]) len++;
    if (pass[4]+2 != pass[2]) len++;

}

然后分析最后一個(gè)判斷塊

LDR     R3, [R11,#s]      ;取字符串
ADD     R3, R3, #3        ;字符串地址偏移3個(gè)字節(jié)
LDRB    R3, [R3]          ;取r3[3],第四個(gè)字符串
EOR     R3, R3, #0x72     ;邏輯異或,就是二進(jìn)制相同的為0;這里是第四個(gè)字符ascii與0x72邏輯異或運(yùn)算
AND     R3, R3, #0xFF     ;邏輯與,和異或相反;這里是將異或后的內(nèi)容與0xff邏輯與運(yùn)算
LDR     R2, [R11,#var_10] ;應(yīng)該沒忘記,這個(gè)地址存的是那個(gè)自增的數(shù)
ADD     R3, R2, R3        ;自增的數(shù)據(jù)與運(yùn)算后的R3相加,結(jié)果還是存在r3
STR     R3, [R11,#var_10] ;把結(jié)果存到自增變量的地址
LDR     R3, [R11,#s]      ;取字符串
ADD     R3, R3, #6        ;偏移6個(gè)字節(jié)
LDRB    R3, [R3]          ;取最后一個(gè)字符終止符`\0`
LDR     R2, [R11,#var_10] ;取運(yùn)算結(jié)果
ADD     R3, R2, R3        ;相加,運(yùn)算結(jié)果+0
STR     R3, [R11,#var_10] ;存
LDR     R3, [R11,#var_10] ;取
CMP     R3, #0            ;比較
BNE     loc_8644          ;不相等跳轉(zhuǎn)輸出loser,相等輸出Success, you rocks

現(xiàn)在可以明確我們需要最后的r3等于0,而r3=與或運(yùn)算+0,所以我們需要之前的與或運(yùn)算結(jié)果為0;

首先是字符串的第四個(gè)字符與0x72('r')異或,然后是與0xff邏輯與
邏輯與的運(yùn)算是兩個(gè)數(shù)的位為1結(jié)果才為1,那么我們就要兩個(gè)數(shù)的沒位都為0就行了,而能達(dá)成這個(gè)條件的的只有數(shù)字0;
而這里的邏輯與運(yùn)算的第二個(gè)數(shù)0xff我們沒法讓它變化,所以從邏輯異或中入手
想要異或的結(jié)果為0,那么就需要兩個(gè)數(shù)相等,也就是說字符串中的第四個(gè)字符串為r
然后后面還有一次運(yùn)算,這個(gè)結(jié)果會(huì)與之前自增的那個(gè)數(shù)相加,所以我們還需要讓那個(gè)自增數(shù)為0,也就是那些判斷都為false

為了方便分析,我更新了下c代碼

#include <stdio.h>
#include <string.h>
 
void main(int args, char **argv)
{
    int tmp;
    int len = 0;
    char *pass = argv[1];
 
    if (args != 2) {
        puts("Loser...");
        exit(1);
    }
    printf("Checking %s for password...\n", pass);
    len = strlen(pass);
    if (len < tmp) {
        puts("Loser...");
        exit(len);
    }

    len = 6 - len;
    if (pass[0] != pass[5]) len++;
    if (pass[0]+1 != pass[1]) len++;
    if (pass[3]+1 != pass[0]) len++;
    if (pass[2]+4 != pass[5]) len++;
    if (pass[4]+2 != pass[2]) len++;
    tmp = ((pass[3]^0x72)&0xFF) + len + pass[6];
    if (tmp) {
        puts("Loser...");
        exit(len);
    }
    puts("Success, you rocks!");
    exit(0);
}

emm...寫了兩種

#include <stdio.h>
#include <string.h>

int main(int args, char **argv)
{
    int len = 0;
    char *pass = argv[1];
    if (args != 2) {
        puts("Loser...");
        exit(1);
    }
    printf("Checking %s for password...\n", pass);
    len = strlen(pass);
    if (len < 6) {
        puts("Loser...");
        exit(len);
    }
    len = 6 - len;
    if (pass[0] == pass[5]) {
        if (pass[0]+1 == pass[1]) {
            if (pass[3]+1 == pass[0]) {
                if (pass[2]+4 == pass[5]) {
                    if (pass[4]+2 == pass[2]) {
                        if (pass[3] == 0x72) {
                            puts("Success, you rocks!");
                            exit(len);
                        }
                    }
                }
            }
        }
    }
    puts("Loser...");
    exit(len);
}

已知password長度為6,第四個(gè)字符為0x72,然后來分析判斷,初始化 pass = ['', '', '', '0x72', '', '']

從已知的地方開始,第四個(gè)字符+1等于第一個(gè)字符,那么 pass = ['0x73', '', '', '0x72', '', '']
接著第一個(gè)字符等于第六個(gè)字符,pass = ['0x73', '', '', '0x72', '', '0x73']
然后第一個(gè)字符+1等于第二個(gè)字符,pass = ['0x73', '0x74', '', '0x72', '', '0x73']
第三個(gè)字符+4等于第六個(gè)字符,pass = ['0x73', '0x74', '', '0x72', '0x67', '0x73']
最后第五個(gè)字符+2等于第三個(gè)字符,pass = ['0x73', '0x74', '0x6f', '0x72', '0x6d', '0x73']

所以結(jié)果為 storms

''.join([chr(int(i,16)) for i in ['0x73', '0x74', '0x6f', '0x72', '0x6d', '0x73']]) # storms

最后

原文:CTF Challenge - ARM Basic Crackme
微信公眾號(hào):the2h0Ng

期間查的資料以及所用的到網(wǎng)頁工具
arm_instruction_set_reference_guide_100076_0100_00_en
ARM數(shù)據(jù)類型
armclang_reference_guide_100067_0611_00_en
armconverter
onlinedisassembler

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容